diff --git a/python_parser/README.md b/python_parser/README.md index 035aa05..8e9ae99 100644 --- a/python_parser/README.md +++ b/python_parser/README.md @@ -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 # Запуск MinIO в Docker docker-compose up -d minio @@ -13,16 +25,8 @@ docker-compose up -d minio python run_dev.py # В отдельном терминале запуск Streamlit -python run_streamlit.py -``` - -### **Вариант 2: MinIO + FastAPI в Docker + Streamlit локально** -```bash -# Запуск MinIO и FastAPI в Docker -docker-compose up -d - -# В отдельном терминале запуск Streamlit -python run_streamlit.py +cd streamlit_app +streamlit run app.py ``` ### **Вариант 3: Только MinIO в Docker** @@ -37,13 +41,6 @@ docker-compose up -d minio - **FastAPI** (порт 8000): API сервер для парсинга Excel файлов - **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API -## 🔧 Диагностика - -Для проверки состояния всех сервисов: -```bash -python check_services.py -``` - ## 🛑 Остановка ### Остановка Docker сервисов: @@ -55,9 +52,9 @@ docker-compose down docker-compose stop minio ``` -### Остановка Streamlit: +### Остановка локальных сервисов: ```bash -# Нажмите Ctrl+C в терминале с Streamlit +# Нажмите Ctrl+C в терминале с FastAPI/Streamlit ``` ## 📁 Структура проекта @@ -74,124 +71,73 @@ python_parser/ ├── adapters/ # Адаптеры для внешних систем │ ├── storage.py # MinIO адаптер │ └── parsers/ # Парсеры Excel файлов +├── streamlit_app/ # Изолированный Streamlit пакет +│ ├── app.py # Основное Streamlit приложение +│ ├── requirements.txt # Зависимости Streamlit +│ ├── Dockerfile # Docker образ для Streamlit +│ └── .streamlit/ # Конфигурация Streamlit ├── data/ # Тестовые данные ├── docker-compose.yml # Docker Compose конфигурация ├── Dockerfile # Docker образ для FastAPI -├── run_dev.py # Запуск FastAPI локально -├── run_streamlit.py # Запуск Streamlit -└── check_services.py # Диагностика сервисов +└── run_dev.py # Запуск FastAPI локально ``` ## 🔍 Доступные эндпоинты - **GET /** - Информация об API - **GET /docs** - Swagger документация +- **GET /parsers** - Список доступных парсеров +- **GET /parsers/{parser_name}/getters** - Информация о геттерах парсера - **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ -- **POST /svodka_ca/upload-zip** - Загрузка сводок ЦА +- **POST /svodka_ca/upload** - Загрузка сводок ЦА - **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива -- **GET /svodka_pm/data** - Получение данных сводок ПМ -- **GET /svodka_ca/data** - Получение данных сводок ЦА -- **GET /monitoring_fuel/data** - Получение данных мониторинга топлива +- **POST /svodka_pm/get_data** - Получение данных сводок ПМ +- **POST /svodka_ca/get_data** - Получение данных сводок ЦА +- **POST /monitoring_fuel/get_data** - Получение данных мониторинга топлива ## 📊 Поддерживаемые типы отчетов 1. **svodka_pm** - Сводки по переработке нефти (ПМ) + - Геттеры: `single_og`, `total_ogs` 2. **svodka_ca** - Сводки по переработке нефти (ЦА) + - Геттеры: `get_data` 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 -# Все сервисы -docker-compose up -d --build +# FastAPI +docker build -t nin-fastapi . +# Streamlit +docker build -t nin-streamlit ./streamlit_app +``` + +### Запуск отдельных сервисов: +```bash # Только MinIO docker-compose up -d minio -# Только FastAPI (требует MinIO) -docker-compose up -d fastapi -``` +# MinIO + FastAPI +docker-compose up -d minio fastapi -### Просмотр логов: -```bash # Все сервисы -docker-compose logs - -# Конкретный сервис -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=. -``` - -## 📝 Лицензия - -Проект разработан для внутреннего использования НИН. \ No newline at end of file +docker-compose up -d +``` \ No newline at end of file diff --git a/python_parser/docker-compose.yml b/python_parser/docker-compose.yml index 919ffe6..572b64e 100644 --- a/python_parser/docker-compose.yml +++ b/python_parser/docker-compose.yml @@ -28,5 +28,16 @@ services: - minio 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: minio_data: \ No newline at end of file diff --git a/python_parser/run_streamlit.py b/python_parser/run_streamlit_local.py similarity index 62% rename from python_parser/run_streamlit.py rename to python_parser/run_streamlit_local.py index 7c63763..beef3e8 100644 --- a/python_parser/run_streamlit.py +++ b/python_parser/run_streamlit_local.py @@ -1,19 +1,28 @@ #!/usr/bin/env python3 """ -Запуск Streamlit интерфейса для NIN Excel Parsers API +Запуск Streamlit интерфейса локально из изолированного пакета """ import subprocess import sys import webbrowser -import time +import os def main(): """Основная функция""" - print("🚀 ЗАПУСК STREAMLIT ИНТЕРФЕЙСА") - print("=" * 50) + print("🚀 ЗАПУСК STREAMLIT ИЗ ИЗОЛИРОВАННОГО ПАКЕТА") + print("=" * 60) 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 try: @@ -21,7 +30,7 @@ def main(): print(f"✅ Streamlit {streamlit.__version__} установлен") except ImportError: print("❌ Streamlit не установлен") - print("Установите: pip install streamlit") + print("Установите: pip install -r requirements.txt") return print("\n🚀 Запускаю Streamlit...") @@ -38,7 +47,7 @@ def main(): # Запускаем Streamlit try: subprocess.run([ - sys.executable, "-m", "streamlit", "run", "streamlit_app.py", + sys.executable, "-m", "streamlit", "run", "app.py", "--server.port", "8501", "--server.address", "localhost", "--server.headless", "false", diff --git a/python_parser/streamlit_app/.dockerignore b/python_parser/streamlit_app/.dockerignore new file mode 100644 index 0000000..147555a --- /dev/null +++ b/python_parser/streamlit_app/.dockerignore @@ -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 +*~ \ No newline at end of file diff --git a/python_parser/streamlit_app/Dockerfile b/python_parser/streamlit_app/Dockerfile new file mode 100644 index 0000000..754c417 --- /dev/null +++ b/python_parser/streamlit_app/Dockerfile @@ -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"] \ No newline at end of file diff --git a/python_parser/streamlit_app/README.md b/python_parser/streamlit_app/README.md new file mode 100644 index 0000000..e98ef60 --- /dev/null +++ b/python_parser/streamlit_app/README.md @@ -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** \ No newline at end of file diff --git a/python_parser/streamlit_app/app.py b/python_parser/streamlit_app/app.py new file mode 100644 index 0000000..12cc93d --- /dev/null +++ b/python_parser/streamlit_app/app.py @@ -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() \ No newline at end of file diff --git a/python_parser/streamlit_app/requirements.txt b/python_parser/streamlit_app/requirements.txt new file mode 100644 index 0000000..8af7ec3 --- /dev/null +++ b/python_parser/streamlit_app/requirements.txt @@ -0,0 +1,4 @@ +streamlit>=1.28.0 +requests>=2.31.0 +pandas>=1.5.0 +numpy>=1.24.0 \ No newline at end of file