all in docker

This commit is contained in:
2025-09-01 12:24:37 +03:00
parent ce228d9756
commit 9459196804
8 changed files with 637 additions and 122 deletions

View File

@@ -4,7 +4,19 @@ API для парсинга Excel отчетов нефтеперерабаты
## 🚀 Быстрый запуск ## 🚀 Быстрый запуск
### **Вариант 1: Только MinIO в Docker + FastAPI локально** ### **Вариант 1: Все сервисы в Docker (рекомендуется)**
```bash
# Запуск всех сервисов: MinIO + FastAPI + Streamlit
docker-compose up -d
# Доступ:
# - MinIO Console: http://localhost:9001
# - FastAPI: http://localhost:8000
# - Streamlit: http://localhost:8501
# - API Docs: http://localhost:8000/docs
```
### **Вариант 2: Только MinIO в Docker + FastAPI локально**
```bash ```bash
# Запуск MinIO в Docker # Запуск MinIO в Docker
docker-compose up -d minio docker-compose up -d minio
@@ -13,16 +25,8 @@ docker-compose up -d minio
python run_dev.py python run_dev.py
# В отдельном терминале запуск Streamlit # В отдельном терминале запуск Streamlit
python run_streamlit.py cd streamlit_app
``` streamlit run app.py
### **Вариант 2: MinIO + FastAPI в Docker + Streamlit локально**
```bash
# Запуск MinIO и FastAPI в Docker
docker-compose up -d
# В отдельном терминале запуск Streamlit
python run_streamlit.py
``` ```
### **Вариант 3: Только MinIO в Docker** ### **Вариант 3: Только MinIO в Docker**
@@ -37,13 +41,6 @@ docker-compose up -d minio
- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов - **FastAPI** (порт 8000): API сервер для парсинга Excel файлов
- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API - **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API
## 🔧 Диагностика
Для проверки состояния всех сервисов:
```bash
python check_services.py
```
## 🛑 Остановка ## 🛑 Остановка
### Остановка Docker сервисов: ### Остановка Docker сервисов:
@@ -55,9 +52,9 @@ docker-compose down
docker-compose stop minio docker-compose stop minio
``` ```
### Остановка Streamlit: ### Остановка локальных сервисов:
```bash ```bash
# Нажмите Ctrl+C в терминале с Streamlit # Нажмите Ctrl+C в терминале с FastAPI/Streamlit
``` ```
## 📁 Структура проекта ## 📁 Структура проекта
@@ -74,124 +71,73 @@ python_parser/
├── adapters/ # Адаптеры для внешних систем ├── adapters/ # Адаптеры для внешних систем
│ ├── storage.py # MinIO адаптер │ ├── storage.py # MinIO адаптер
│ └── parsers/ # Парсеры Excel файлов │ └── parsers/ # Парсеры Excel файлов
├── streamlit_app/ # Изолированный Streamlit пакет
│ ├── app.py # Основное Streamlit приложение
│ ├── requirements.txt # Зависимости Streamlit
│ ├── Dockerfile # Docker образ для Streamlit
│ └── .streamlit/ # Конфигурация Streamlit
├── data/ # Тестовые данные ├── data/ # Тестовые данные
├── docker-compose.yml # Docker Compose конфигурация ├── docker-compose.yml # Docker Compose конфигурация
├── Dockerfile # Docker образ для FastAPI ├── Dockerfile # Docker образ для FastAPI
── run_dev.py # Запуск FastAPI локально ── run_dev.py # Запуск FastAPI локально
├── run_streamlit.py # Запуск Streamlit
└── check_services.py # Диагностика сервисов
``` ```
## 🔍 Доступные эндпоинты ## 🔍 Доступные эндпоинты
- **GET /** - Информация об API - **GET /** - Информация об API
- **GET /docs** - Swagger документация - **GET /docs** - Swagger документация
- **GET /parsers** - Список доступных парсеров
- **GET /parsers/{parser_name}/getters** - Информация о геттерах парсера
- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ - **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ
- **POST /svodka_ca/upload-zip** - Загрузка сводок ЦА - **POST /svodka_ca/upload** - Загрузка сводок ЦА
- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива - **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива
- **GET /svodka_pm/data** - Получение данных сводок ПМ - **POST /svodka_pm/get_data** - Получение данных сводок ПМ
- **GET /svodka_ca/data** - Получение данных сводок ЦА - **POST /svodka_ca/get_data** - Получение данных сводок ЦА
- **GET /monitoring_fuel/data** - Получение данных мониторинга топлива - **POST /monitoring_fuel/get_data** - Получение данных мониторинга топлива
## 📊 Поддерживаемые типы отчетов ## 📊 Поддерживаемые типы отчетов
1. **svodka_pm** - Сводки по переработке нефти (ПМ) 1. **svodka_pm** - Сводки по переработке нефти (ПМ)
- Геттеры: `single_og`, `total_ogs`
2. **svodka_ca** - Сводки по переработке нефти (ЦА) 2. **svodka_ca** - Сводки по переработке нефти (ЦА)
- Геттеры: `get_data`
3. **monitoring_fuel** - Мониторинг топлива 3. **monitoring_fuel** - Мониторинг топлива
- Геттеры: `total_by_columns`, `month_by_code`
## 🐳 Docker команды ## 🏗️ Архитектура
### Сборка и запуск: Проект использует **Hexagonal Architecture (Ports and Adapters)**:
- **Порты (Ports)**: Интерфейсы для бизнес-логики
- **Адаптеры (Adapters)**: Реализации для внешних систем
- **Сервисы (Services)**: Бизнес-логика приложения
### Система геттеров парсеров
Каждый парсер может иметь несколько методов получения данных (геттеров):
- Регистрация геттеров в словаре с метаданными
- Валидация параметров для каждого геттера
- Единый интерфейс `get_value(getter_name, params)`
## 🐳 Docker
### Сборка образов:
```bash ```bash
# Все сервисы # FastAPI
docker-compose up -d --build docker build -t nin-fastapi .
# Streamlit
docker build -t nin-streamlit ./streamlit_app
```
### Запуск отдельных сервисов:
```bash
# Только MinIO # Только MinIO
docker-compose up -d minio docker-compose up -d minio
# Только FastAPI (требует MinIO) # MinIO + FastAPI
docker-compose up -d fastapi docker-compose up -d minio fastapi
```
### Просмотр логов:
```bash
# Все сервисы # Все сервисы
docker-compose logs docker-compose up -d
# Конкретный сервис
docker-compose logs fastapi
docker-compose logs minio
``` ```
### Остановка:
```bash
docker-compose down
```
## 🔧 Устранение неполадок
### Проблема: "Streamlit не может подключиться к FastAPI"
**Симптомы:**
- Streamlit открывается, но показывает "API недоступен по адресу http://localhost:8000"
- FastAPI не отвечает на порту 8000
**Решения:**
1. **Проверьте порты:**
```bash
# Windows
netstat -an | findstr :8000
# Linux/Mac
netstat -an | grep :8000
```
2. **Перезапустите FastAPI:**
```bash
# Остановите текущий процесс (Ctrl+C)
python run_dev.py
```
3. **Проверьте логи Docker:**
```bash
docker-compose logs fastapi
```
### Проблема: "MinIO недоступен"
**Решения:**
1. Запустите Docker Desktop
2. Проверьте статус контейнера: `docker ps`
3. Перезапустите MinIO: `docker-compose restart minio`
### Проблема: "Порт уже занят"
**Решения:**
1. Найдите процесс: `netstat -ano | findstr :8000`
2. Остановите процесс: `taskkill /PID <номер_процесса>`
3. Или используйте другой порт в конфигурации
## 🚀 Разработка
### Добавление нового парсера:
1. Создайте файл в `adapters/parsers/`
2. Реализуйте интерфейс `ParserPort`
3. Добавьте в `core/services.py`
4. Создайте схемы в `app/schemas/`
5. Добавьте эндпоинты в `app/main.py`
### Тестирование:
```bash
# Запуск тестов
pytest
# Запуск с покрытием
pytest --cov=.
```
## 📝 Лицензия
Проект разработан для внутреннего использования НИН.

View File

@@ -28,5 +28,16 @@ services:
- minio - minio
restart: unless-stopped restart: unless-stopped
streamlit:
build: ./streamlit_app
container_name: svodka_streamlit
ports:
- "8501:8501"
environment:
- API_BASE_URL=http://fastapi:8000
depends_on:
- fastapi
restart: unless-stopped
volumes: volumes:
minio_data: minio_data:

View File

@@ -1,19 +1,28 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Запуск Streamlit интерфейса для NIN Excel Parsers API Запуск Streamlit интерфейса локально из изолированного пакета
""" """
import subprocess import subprocess
import sys import sys
import webbrowser import webbrowser
import time import os
def main(): def main():
"""Основная функция""" """Основная функция"""
print("🚀 ЗАПУСК STREAMLIT ИНТЕРФЕЙСА") print("🚀 ЗАПУСК STREAMLIT ИЗ ИЗОЛИРОВАННОГО ПАКЕТА")
print("=" * 50) print("=" * 60)
print("Убедитесь, что FastAPI сервер запущен на порту 8000") print("Убедитесь, что FastAPI сервер запущен на порту 8000")
print("=" * 50) print("=" * 60)
# Проверяем, существует ли папка streamlit_app
if not os.path.exists("streamlit_app"):
print("❌ Папка streamlit_app не найдена")
print("Создайте изолированный пакет или используйте docker-compose up -d")
return
# Переходим в папку streamlit_app
os.chdir("streamlit_app")
# Проверяем, установлен ли Streamlit # Проверяем, установлен ли Streamlit
try: try:
@@ -21,7 +30,7 @@ def main():
print(f"✅ Streamlit {streamlit.__version__} установлен") print(f"✅ Streamlit {streamlit.__version__} установлен")
except ImportError: except ImportError:
print("❌ Streamlit не установлен") print("❌ Streamlit не установлен")
print("Установите: pip install streamlit") print("Установите: pip install -r requirements.txt")
return return
print("\n🚀 Запускаю Streamlit...") print("\n🚀 Запускаю Streamlit...")
@@ -38,7 +47,7 @@ def main():
# Запускаем Streamlit # Запускаем Streamlit
try: try:
subprocess.run([ subprocess.run([
sys.executable, "-m", "streamlit", "run", "streamlit_app.py", sys.executable, "-m", "streamlit", "run", "app.py",
"--server.port", "8501", "--server.port", "8501",
"--server.address", "localhost", "--server.address", "localhost",
"--server.headless", "false", "--server.headless", "false",

View File

@@ -0,0 +1,31 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.mypy_cache
.pytest_cache
.hypothesis
.DS_Store
.env
.venv
venv/
ENV/
env/
.idea/
.vscode/
*.swp
*.swo
*~

View File

@@ -0,0 +1,23 @@
FROM python:3.11-slim
WORKDIR /app
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Копируем файлы зависимостей
COPY requirements.txt .
# Устанавливаем Python зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем код приложения
COPY . .
# Открываем порт
EXPOSE 8501
# Команда запуска
CMD ["streamlit", "run", "app.py", "--server.port", "8501", "--server.address", "0.0.0.0"]

View File

@@ -0,0 +1,44 @@
# 📊 Streamlit App - NIN Excel Parsers API
Изолированное Streamlit приложение для демонстрации работы NIN Excel Parsers API.
## 🚀 Запуск
### Локально:
```bash
cd streamlit_app
pip install -r requirements.txt
streamlit run app.py
```
### В Docker:
```bash
docker build -t streamlit-app .
docker run -p 8501:8501 streamlit-app
```
## 🔧 Конфигурация
### Переменные окружения:
- `API_BASE_URL` - адрес FastAPI сервера (по умолчанию: `http://fastapi:8000`)
### Параметры Streamlit:
- Порт: 8501
- Адрес: 0.0.0.0 (для Docker)
- Режим: headless (для Docker)
## 📁 Структура
```
streamlit_app/
├── app.py # Основное приложение
├── requirements.txt # Зависимости Python
├── Dockerfile # Docker образ
├── .streamlit/ # Конфигурация Streamlit
│ └── config.toml # Настройки
└── README.md # Документация
```
## 🌐 Доступ
После запуска приложение доступно по адресу: **http://localhost:8501**

View File

@@ -0,0 +1,447 @@
import streamlit as st
import requests
import json
import pandas as pd
import io
import zipfile
from typing import Dict, Any
import os
# Конфигурация страницы
st.set_page_config(
page_title="NIN Excel Parsers API Demo",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded"
)
# Конфигурация API - используем переменную окружения или значение по умолчанию
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000")
def check_api_health():
"""Проверка доступности API"""
try:
response = requests.get(f"{API_BASE_URL}/", timeout=5)
return response.status_code == 200
except:
return False
def get_available_parsers():
"""Получение списка доступных парсеров"""
try:
response = requests.get(f"{API_BASE_URL}/parsers")
if response.status_code == 200:
return response.json()["parsers"]
return []
except:
return []
def get_parser_getters(parser_name: str):
"""Получение информации о геттерах парсера"""
try:
response = requests.get(f"{API_BASE_URL}/parsers/{parser_name}/getters")
if response.status_code == 200:
return response.json()
return {}
except:
return {}
def get_server_info():
"""Получение информации о сервере"""
try:
response = requests.get(f"{API_BASE_URL}/server-info")
if response.status_code == 200:
return response.json()
return {}
except:
return {}
def upload_file_to_api(endpoint: str, file_data: bytes, filename: str):
"""Загрузка файла на API"""
try:
files = {"zip_file": (filename, file_data, "application/zip")}
response = requests.post(f"{API_BASE_URL}{endpoint}", files=files)
return response.json(), response.status_code
except Exception as e:
return {"error": str(e)}, 500
def make_api_request(endpoint: str, data: Dict[str, Any]):
"""Выполнение API запроса"""
try:
response = requests.post(f"{API_BASE_URL}{endpoint}", json=data)
return response.json(), response.status_code
except Exception as e:
return {"error": str(e)}, 500
def main():
st.title("🚀 NIN Excel Parsers API - Демонстрация")
st.markdown("---")
# Проверка доступности API
if not check_api_health():
st.error(f"❌ API недоступен по адресу {API_BASE_URL}")
st.info("Убедитесь, что FastAPI сервер запущен")
return
st.success(f"✅ API доступен по адресу {API_BASE_URL}")
# Боковая панель с информацией
with st.sidebar:
st.header(" Информация")
# Информация о сервере
server_info = get_server_info()
if server_info:
st.subheader("Сервер")
st.write(f"PID: {server_info.get('process_id', 'N/A')}")
st.write(f"CPU ядер: {server_info.get('cpu_cores', 'N/A')}")
st.write(f"Память: {server_info.get('memory_mb', 'N/A'):.1f} MB")
# Доступные парсеры
parsers = get_available_parsers()
if parsers:
st.subheader("Доступные парсеры")
for parser in parsers:
st.write(f"{parser}")
# Основные вкладки - по одной на каждый парсер
tab1, tab2, tab3 = st.tabs([
"📊 Сводки ПМ",
"🏭 Сводки СА",
"⛽ Мониторинг топлива"
])
# Вкладка 1: Сводки ПМ - полный функционал
with tab1:
st.header("📊 Сводки ПМ - Полный функционал")
# Получаем информацию о геттерах
getters_info = get_parser_getters("svodka_pm")
# Секция загрузки файлов
st.subheader("📤 Загрузка файлов")
uploaded_pm = st.file_uploader(
"Выберите ZIP архив со сводками ПМ",
type=['zip'],
key="pm_upload"
)
if uploaded_pm is not None:
if st.button("📤 Загрузить сводки ПМ", key="upload_pm_btn"):
with st.spinner("Загружаю файл..."):
result, status = upload_file_to_api(
"/svodka_pm/upload-zip",
uploaded_pm.read(),
uploaded_pm.name
)
if status == 200:
st.success(f"{result.get('message', 'Файл загружен')}")
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
st.markdown("---")
# Секция получения данных
st.subheader("🔍 Получение данных")
# Показываем доступные геттеры
if getters_info and "getters" in getters_info:
st.info("📋 Доступные геттеры:")
for getter_name, getter_info in getters_info["getters"].items():
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
if getter_info.get('optional_params'):
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
col1, col2 = st.columns(2)
with col1:
st.subheader("Данные по одному ОГ")
og_id = st.selectbox(
"Выберите ОГ",
["SNPZ", "KNPZ", "ANHK", "AchNPZ", "UNPZ", "UNH", "NOV",
"NovKuybNPZ", "KuybNPZ", "CyzNPZ", "TuapsNPZ", "RNPK",
"NVNPO", "KLNPZ", "PurNP", "YANOS"],
key="pm_single_og"
)
codes = st.multiselect(
"Выберите коды строк",
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
default=[78, 79],
key="pm_single_codes"
)
columns = st.multiselect(
"Выберите столбцы",
["БП", "ПП", "СЭБ", "Факт", "План"],
default=["БП", "ПП"],
key="pm_single_columns"
)
if st.button("🔍 Получить данные по ОГ", key="pm_single_btn"):
if codes and columns:
with st.spinner("Получаю данные..."):
data = {
"getter": "single_og",
"id": og_id,
"codes": codes,
"columns": columns
}
result, status = make_api_request("/svodka_pm/get_data", data)
if status == 200:
st.success("✅ Данные получены")
st.json(result)
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
else:
st.warning("⚠️ Выберите коды и столбцы")
with col2:
st.subheader("Данные по всем ОГ")
codes_total = st.multiselect(
"Выберите коды строк",
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
default=[78, 79, 394, 395],
key="pm_total_codes"
)
columns_total = st.multiselect(
"Выберите столбцы",
["БП", "ПП", "СЭБ", "Факт", "План"],
default=["БП", "ПП", "СЭБ"],
key="pm_total_columns"
)
if st.button("🔍 Получить данные по всем ОГ", key="pm_total_btn"):
if codes_total and columns_total:
with st.spinner("Получаю данные..."):
data = {
"getter": "total_ogs",
"codes": codes_total,
"columns": columns_total
}
result, status = make_api_request("/svodka_pm/get_data", data)
if status == 200:
st.success("✅ Данные получены")
st.json(result)
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
else:
st.warning("⚠️ Выберите коды и столбцы")
# Вкладка 2: Сводки СА - полный функционал
with tab2:
st.header("🏭 Сводки СА - Полный функционал")
# Получаем информацию о геттерах
getters_info = get_parser_getters("svodka_ca")
# Секция загрузки файлов
st.subheader("📤 Загрузка файлов")
uploaded_ca = st.file_uploader(
"Выберите Excel файл сводки СА",
type=['xlsx', 'xlsm', 'xls'],
key="ca_upload"
)
if uploaded_ca is not None:
if st.button("📤 Загрузить сводку СА", key="upload_ca_btn"):
with st.spinner("Загружаю файл..."):
try:
files = {"file": (uploaded_ca.name, uploaded_ca.read(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
response = requests.post(f"{API_BASE_URL}/svodka_ca/upload", files=files)
result = response.json()
if response.status_code == 200:
st.success(f"{result.get('message', 'Файл загружен')}")
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
except Exception as e:
st.error(f"❌ Ошибка: {str(e)}")
st.markdown("---")
# Секция получения данных
st.subheader("🔍 Получение данных")
# Показываем доступные геттеры
if getters_info and "getters" in getters_info:
st.info("📋 Доступные геттеры:")
for getter_name, getter_info in getters_info["getters"].items():
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
if getter_info.get('optional_params'):
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
col1, col2 = st.columns(2)
with col1:
st.subheader("Параметры запроса")
modes = st.multiselect(
"Выберите режимы",
["План", "Факт", "Норматив"],
default=["План", "Факт"],
key="ca_modes"
)
tables = st.multiselect(
"Выберите таблицы",
["ТиП", "Топливо", "Потери"],
default=["ТиП", "Топливо"],
key="ca_tables"
)
with col2:
st.subheader("Результат")
if st.button("🔍 Получить данные СА", key="ca_btn"):
if modes and tables:
with st.spinner("Получаю данные..."):
data = {
"getter": "get_data",
"modes": modes,
"tables": tables
}
result, status = make_api_request("/svodka_ca/get_data", data)
if status == 200:
st.success("✅ Данные получены")
st.json(result)
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
else:
st.warning("⚠️ Выберите режимы и таблицы")
# Вкладка 3: Мониторинг топлива - полный функционал
with tab3:
st.header("⛽ Мониторинг топлива - Полный функционал")
# Получаем информацию о геттерах
getters_info = get_parser_getters("monitoring_fuel")
# Секция загрузки файлов
st.subheader("📤 Загрузка файлов")
uploaded_fuel = st.file_uploader(
"Выберите ZIP архив с мониторингом топлива",
type=['zip'],
key="fuel_upload"
)
if uploaded_fuel is not None:
if st.button("📤 Загрузить мониторинг топлива", key="upload_fuel_btn"):
with st.spinner("Загружаю файл..."):
result, status = upload_file_to_api(
"/monitoring_fuel/upload-zip",
uploaded_fuel.read(),
uploaded_fuel.name
)
if status == 200:
st.success(f"{result.get('message', 'Файл загружен')}")
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
st.markdown("---")
# Секция получения данных
st.subheader("🔍 Получение данных")
# Показываем доступные геттеры
if getters_info and "getters" in getters_info:
st.info("📋 Доступные геттеры:")
for getter_name, getter_info in getters_info["getters"].items():
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
if getter_info.get('optional_params'):
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
col1, col2 = st.columns(2)
with col1:
st.subheader("Агрегация по колонкам")
columns_fuel = st.multiselect(
"Выберите столбцы",
["normativ", "total", "total_1"],
default=["normativ", "total"],
key="fuel_columns"
)
if st.button("🔍 Получить агрегированные данные", key="fuel_total_btn"):
if columns_fuel:
with st.spinner("Получаю данные..."):
data = {
"getter": "total_by_columns",
"columns": columns_fuel
}
result, status = make_api_request("/monitoring_fuel/get_data", data)
if status == 200:
st.success("✅ Данные получены")
st.json(result)
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
else:
st.warning("⚠️ Выберите столбцы")
with col2:
st.subheader("Данные за месяц")
month = st.selectbox(
"Выберите месяц",
[f"{i:02d}" for i in range(1, 13)],
key="fuel_month"
)
if st.button("🔍 Получить данные за месяц", key="fuel_month_btn"):
with st.spinner("Получаю данные..."):
data = {
"getter": "month_by_code",
"month": month
}
result, status = make_api_request("/monitoring_fuel/get_data", data)
if status == 200:
st.success("✅ Данные получены")
st.json(result)
else:
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
# Футер
st.markdown("---")
st.markdown("### 📚 Документация API")
st.markdown(f"Полная документация доступна по адресу: {API_BASE_URL}/docs")
# Информация о проекте
with st.expander(" О проекте"):
st.markdown("""
**NIN Excel Parsers API** - это веб-сервис для парсинга и обработки Excel-файлов нефтеперерабатывающих заводов.
**Возможности:**
- 📊 Парсинг сводок ПМ (план и факт)
- 🏭 Парсинг сводок СА
- ⛽ Мониторинг топлива
**Технологии:**
- FastAPI
- Pandas
- MinIO (S3-совместимое хранилище)
- Streamlit (веб-интерфейс)
""")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,4 @@
streamlit>=1.28.0
requests>=2.31.0
pandas>=1.5.0
numpy>=1.24.0