diff --git a/python_parser/.streamlit/config.toml b/python_parser/.streamlit/config.toml new file mode 100644 index 0000000..84e0551 --- /dev/null +++ b/python_parser/.streamlit/config.toml @@ -0,0 +1,28 @@ +[server] +port = 8501 +address = "localhost" +headless = false +enableCORS = false +enableXsrfProtection = false + +[browser] +gatherUsageStats = false +serverAddress = "localhost" +serverPort = 8501 + +[theme] +primaryColor = "#FF4B4B" +backgroundColor = "#FFFFFF" +secondaryBackgroundColor = "#F0F2F6" +textColor = "#262730" +font = "sans serif" + +[client] +showErrorDetails = true +caching = true +displayEnabled = true + +[runner] +magicEnabled = true +installTracer = false +fixMatplotlib = true \ No newline at end of file diff --git a/python_parser/Dockerfile b/python_parser/Dockerfile index d1409f7..51216a8 100644 --- a/python_parser/Dockerfile +++ b/python_parser/Dockerfile @@ -1,20 +1,23 @@ -FROM repo-dev.predix.rosneft.ru/python:3.11-slim +FROM python:3.11-slim WORKDIR /app -# RUN pip install kafka-python==2.0.2 -# RUN pip freeze > /app/requirements.txt +# Устанавливаем системные зависимости +RUN apt-get update && apt-get install -y \ + gcc \ + && rm -rf /var/lib/apt/lists/* -# ADD . /app +# Копируем файлы зависимостей COPY requirements.txt . -RUN mkdir -p vendor -RUN pip download -r /app/requirements.txt --no-binary=:none: -d /app/vendor +# Устанавливаем Python зависимости +RUN pip install --no-cache-dir -r requirements.txt -# ADD . /app +# Копируем код приложения +COPY . . -# ENV KAFKA_BROKER=10.234.160.10:9093,10.234.160.10:9094,10.234.160.10:9095 -# ENV KAFKA_UPDATE_ALGORITHM_RULES_TOPIC=algorithm-rule-update -# ENV KAFKA_CLIENT_USERNAME=cf-service +# Открываем порт +EXPOSE 8000 -# CMD ["python", "/app/run_dev.py"] \ No newline at end of file +# Команда запуска +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/python_parser/QUICK_START.md b/python_parser/QUICK_START.md new file mode 100644 index 0000000..d81b67f --- /dev/null +++ b/python_parser/QUICK_START.md @@ -0,0 +1,66 @@ +# 🚀 Быстрый старт NIN Excel Parsers API + +## 🐳 Запуск через Docker (рекомендуется) + +### Вариант 1: MinIO + FastAPI в Docker +```bash +# Запуск всех сервисов +docker-compose up -d --build + +# Проверка +curl http://localhost:8000 +curl http://localhost:9001 +``` + +### Вариант 2: Только MinIO в Docker +```bash +# Запуск только MinIO +docker-compose up -d minio + +# Проверка +curl http://localhost:9001 +``` + +## 🖥️ Запуск FastAPI локально + +```bash +# Если MinIO в Docker +python run_dev.py + +# Проверка +curl http://localhost:8000 +``` + +## 📊 Запуск Streamlit + +```bash +# В отдельном терминале +python run_streamlit.py +``` + +## 🌐 Доступные URL + +- **FastAPI API**: http://localhost:8000 +- **API документация**: http://localhost:8000/docs +- **MinIO консоль**: http://localhost:9001 +- **Streamlit интерфейс**: http://localhost:8501 + +## 🛑 Остановка + +```bash +# Остановка Docker +docker-compose down + +# Остановка Streamlit +# Ctrl+C в терминале +``` + +## 🔧 Диагностика + +```bash +# Проверка состояния +python check_services.py + +# Просмотр логов Docker +docker-compose logs +``` \ No newline at end of file diff --git a/python_parser/README.md b/python_parser/README.md new file mode 100644 index 0000000..035aa05 --- /dev/null +++ b/python_parser/README.md @@ -0,0 +1,197 @@ +# NIN Excel Parsers API + +API для парсинга Excel отчетов нефтеперерабатывающих заводов (НПЗ) с использованием FastAPI и MinIO для хранения данных. + +## 🚀 Быстрый запуск + +### **Вариант 1: Только MinIO в Docker + FastAPI локально** +```bash +# Запуск MinIO в Docker +docker-compose up -d minio + +# Запуск FastAPI локально +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 +``` + +### **Вариант 3: Только MinIO в Docker** +```bash +# Запуск только MinIO +docker-compose up -d minio +``` + +## 📋 Описание сервисов + +- **MinIO** (порт 9000-9001): S3-совместимое хранилище для данных +- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов +- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API + +## 🔧 Диагностика + +Для проверки состояния всех сервисов: +```bash +python check_services.py +``` + +## 🛑 Остановка + +### Остановка Docker сервисов: +```bash +# Все сервисы +docker-compose down + +# Только MinIO +docker-compose stop minio +``` + +### Остановка Streamlit: +```bash +# Нажмите Ctrl+C в терминале с Streamlit +``` + +## 📁 Структура проекта + +``` +python_parser/ +├── app/ # FastAPI приложение +│ ├── main.py # Основной файл приложения +│ └── schemas/ # Pydantic схемы +├── core/ # Бизнес-логика +│ ├── models.py # Модели данных +│ ├── ports.py # Интерфейсы (порты) +│ └── services.py # Сервисы +├── adapters/ # Адаптеры для внешних систем +│ ├── storage.py # MinIO адаптер +│ └── parsers/ # Парсеры Excel файлов +├── data/ # Тестовые данные +├── docker-compose.yml # Docker Compose конфигурация +├── Dockerfile # Docker образ для FastAPI +├── run_dev.py # Запуск FastAPI локально +├── run_streamlit.py # Запуск Streamlit +└── check_services.py # Диагностика сервисов +``` + +## 🔍 Доступные эндпоинты + +- **GET /** - Информация об API +- **GET /docs** - Swagger документация +- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ +- **POST /svodka_ca/upload-zip** - Загрузка сводок ЦА +- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива +- **GET /svodka_pm/data** - Получение данных сводок ПМ +- **GET /svodka_ca/data** - Получение данных сводок ЦА +- **GET /monitoring_fuel/data** - Получение данных мониторинга топлива + +## 📊 Поддерживаемые типы отчетов + +1. **svodka_pm** - Сводки по переработке нефти (ПМ) +2. **svodka_ca** - Сводки по переработке нефти (ЦА) +3. **monitoring_fuel** - Мониторинг топлива + +## 🐳 Docker команды + +### Сборка и запуск: +```bash +# Все сервисы +docker-compose up -d --build + +# Только MinIO +docker-compose up -d minio + +# Только FastAPI (требует MinIO) +docker-compose up -d 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 diff --git a/python_parser/README_STREAMLIT.md b/python_parser/README_STREAMLIT.md new file mode 100644 index 0000000..d3b0d36 --- /dev/null +++ b/python_parser/README_STREAMLIT.md @@ -0,0 +1,186 @@ +# 🚀 Streamlit Demo для NIN Excel Parsers API + +## Описание + +Streamlit приложение для демонстрации работы всех API эндпоинтов NIN Excel Parsers. Предоставляет удобный веб-интерфейс для тестирования функциональности парсеров. + +## Возможности + +- 📤 **Загрузка файлов**: Загрузка ZIP архивов и Excel файлов +- 📊 **Сводки ПМ**: Работа с плановыми и фактическими данными +- 🏭 **Сводки СА**: Парсинг сводок центрального аппарата +- ⛽ **Мониторинг топлива**: Анализ данных по топливу +- 📱 **Адаптивный интерфейс**: Удобное использование на всех устройствах + +## Установка и запуск + +### 1. Установка зависимостей + +```bash +pip install -r requirements.txt +``` + +### 2. Запуск FastAPI сервера + +В одном терминале: +```bash +python run_dev.py +``` + +### 3. Запуск Streamlit приложения + +В другом терминале: +```bash +python run_streamlit.py +``` + +Или напрямую: +```bash +streamlit run streamlit_app.py +``` + +### 4. Открытие в браузере + +Приложение автоматически откроется по адресу: http://localhost:8501 + +## Конфигурация + +### Переменные окружения + +```bash +# URL API сервера +export API_BASE_URL="http://localhost:8000" + +# Порт Streamlit +export STREAMLIT_PORT="8501" + +# Хост Streamlit +export STREAMLIT_HOST="localhost" +``` + +### Настройки Streamlit + +Файл `.streamlit/config.toml` содержит настройки: +- Порт: 8501 +- Хост: localhost +- Тема: Кастомная цветовая схема +- Безопасность: Отключены CORS и XSRF для локальной разработки + +## Структура приложения + +### Вкладки + +1. **📤 Загрузка файлов** + - Загрузка сводок ПМ (ZIP) + - Загрузка мониторинга топлива (ZIP) + - Загрузка сводки СА (Excel) + +2. **📊 Сводки ПМ** + - Данные по одному ОГ + - Данные по всем ОГ + - Выбор кодов строк и столбцов + +3. **🏭 Сводки СА** + - Выбор режимов (план/факт/норматив) + - Выбор таблиц для анализа + +4. **⛽ Мониторинг топлива** + - Агрегация по колонкам + - Данные за конкретный месяц + +### Боковая панель + +- Информация о сервере (PID, CPU, память) +- Список доступных парсеров +- Статус подключения к API + +## Использование + +### 1. Загрузка файлов + +1. Выберите соответствующий тип файла +2. Нажмите "Загрузить" +3. Дождитесь подтверждения загрузки + +### 2. Получение данных + +1. Выберите нужные параметры (ОГ, коды, столбцы) +2. Нажмите "Получить данные" +3. Результат отобразится в JSON формате + +### 3. Мониторинг + +- Проверяйте статус API в верхней части +- Следите за логами операций +- Используйте индикаторы загрузки + +## Устранение неполадок + +### API недоступен + +```bash +# Проверьте, запущен ли FastAPI сервер +curl http://localhost:8000/ + +# Проверьте порт +netstat -an | grep 8000 +``` + +### Streamlit не запускается + +```bash +# Проверьте версию Python +python --version + +# Переустановите Streamlit +pip uninstall streamlit +pip install streamlit + +# Проверьте порт 8501 +netstat -an | grep 8501 +``` + +### Ошибки загрузки файлов + +- Убедитесь, что файл соответствует формату +- Проверьте размер файла (не более 100MB) +- Убедитесь, что MinIO запущен + +## Разработка + +### Добавление новых функций + +1. Создайте новую вкладку в `streamlit_app.py` +2. Добавьте соответствующие API вызовы +3. Обновите боковую панель при необходимости + +### Кастомизация темы + +Отредактируйте `.streamlit/config.toml`: +```toml +[theme] +primaryColor = "#FF4B4B" +backgroundColor = "#FFFFFF" +# ... другие цвета +``` + +### Добавление новых парсеров + +1. Создайте парсер в `adapters/parsers/` +2. Добавьте в `main.py` +3. Обновите Streamlit интерфейс + +## Безопасность + +⚠️ **Внимание**: Приложение настроено для локальной разработки +- CORS отключен +- XSRF защита отключена +- Не используйте в продакшене без дополнительной настройки + +## Поддержка + +При возникновении проблем: +1. Проверьте логи в терминале +2. Убедитесь, что все сервисы запущены +3. Проверьте конфигурацию +4. Обратитесь к документации API: http://localhost:8000/docs \ No newline at end of file diff --git a/python_parser/adapters/__pycache__/__init__.cpython-313.pyc b/python_parser/adapters/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..af840e8 Binary files /dev/null and b/python_parser/adapters/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc b/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc new file mode 100644 index 0000000..34fa65d Binary files /dev/null and b/python_parser/adapters/__pycache__/pconfig.cpython-313.pyc differ diff --git a/python_parser/adapters/__pycache__/storage.cpython-313.pyc b/python_parser/adapters/__pycache__/storage.cpython-313.pyc new file mode 100644 index 0000000..1fc1ad5 Binary files /dev/null and b/python_parser/adapters/__pycache__/storage.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/__init__.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..a8629ae Binary files /dev/null and b/python_parser/adapters/parsers/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc new file mode 100644 index 0000000..1954ec4 Binary files /dev/null and b/python_parser/adapters/parsers/__pycache__/monitoring_fuel.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc new file mode 100644 index 0000000..6be6eb5 Binary files /dev/null and b/python_parser/adapters/parsers/__pycache__/svodka_ca.cpython-313.pyc differ diff --git a/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc b/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc new file mode 100644 index 0000000..09b5d5b Binary files /dev/null and b/python_parser/adapters/parsers/__pycache__/svodka_pm.cpython-313.pyc differ diff --git a/python_parser/adapters/storage.py b/python_parser/adapters/storage.py index 439214f..45cc8b8 100644 --- a/python_parser/adapters/storage.py +++ b/python_parser/adapters/storage.py @@ -15,24 +15,53 @@ class MinIOStorageAdapter(StoragePort): """Адаптер для MinIO хранилища""" def __init__(self): - self.client = Minio( - os.getenv("MINIO_ENDPOINT", "localhost:9000"), - access_key=os.getenv("MINIO_ACCESS_KEY", "minioadmin"), - secret_key=os.getenv("MINIO_SECRET_KEY", "minioadmin"), - secure=os.getenv("MINIO_SECURE", "false").lower() == "true", - cert_check=False - ) - self.bucket_name = os.getenv("MINIO_BUCKET", "svodka-data") - self._ensure_bucket_exists() + self._client = None + self._bucket_name = os.getenv("MINIO_BUCKET", "svodka-data") + self._endpoint = os.getenv("MINIO_ENDPOINT", "localhost:9000") + self._access_key = os.getenv("MINIO_ACCESS_KEY", "minioadmin") + self._secret_key = os.getenv("MINIO_SECRET_KEY", "minioadmin") + self._secure = os.getenv("MINIO_SECURE", "false").lower() == "true" + + @property + def client(self): + """Ленивая инициализация MinIO клиента""" + if self._client is None: + try: + self._client = Minio( + self._endpoint, + access_key=self._access_key, + secret_key=self._secret_key, + secure=self._secure, + cert_check=False + ) + # Проверяем bucket только при первом использовании + self._ensure_bucket_exists() + except Exception as e: + print(f"⚠️ Не удалось подключиться к MinIO: {e}") + print("MinIO будет недоступен, но приложение продолжит работать") + return None + return self._client def _ensure_bucket_exists(self): """Проверка существования bucket и создание при необходимости""" - if not self.client.bucket_exists(self.bucket_name): - self.client.make_bucket(self.bucket_name) - print(f"Bucket '{self.bucket_name}' создан") + if self.client is None: + return False + + try: + if not self.client.bucket_exists(self._bucket_name): + self.client.make_bucket(self._bucket_name) + print(f"✅ Bucket '{self._bucket_name}' создан") + return True + except Exception as e: + print(f"❌ Ошибка при работе с bucket: {e}") + return False def save_dataframe(self, df: pd.DataFrame, object_id: str) -> bool: """Сохранение DataFrame в MinIO""" + if self.client is None: + print("⚠️ MinIO недоступен, данные не сохранены") + return False + try: # Сериализуем DataFrame data = pickle.dumps(df) @@ -42,24 +71,28 @@ class MinIOStorageAdapter(StoragePort): # Загружаем в MinIO self.client.put_object( - self.bucket_name, + self._bucket_name, object_id, data_stream, length=len(data), content_type='application/octet-stream' ) - print(f"DataFrame успешно сохранен в MinIO: {self.bucket_name}/{object_id}") + print(f"✅ DataFrame успешно сохранен в MinIO: {self._bucket_name}/{object_id}") return True except Exception as e: - print(f"Ошибка при сохранении в MinIO: {e}") + print(f"❌ Ошибка при сохранении в MinIO: {e}") return False def load_dataframe(self, object_id: str) -> Optional[pd.DataFrame]: """Загрузка DataFrame из MinIO""" + if self.client is None: + print("⚠️ MinIO недоступен, данные не загружены") + return None + try: # Получаем объект из MinIO - response = self.client.get_object(self.bucket_name, object_id) + response = self.client.get_object(self._bucket_name, object_id) # Читаем данные data = response.read() @@ -69,7 +102,7 @@ class MinIOStorageAdapter(StoragePort): return df except Exception as e: - print(f"Ошибка при загрузке данных из MinIO: {e}") + print(f"❌ Ошибка при загрузке данных из MinIO: {e}") return None finally: if 'response' in locals(): @@ -78,18 +111,25 @@ class MinIOStorageAdapter(StoragePort): def delete_object(self, object_id: str) -> bool: """Удаление объекта из MinIO""" + if self.client is None: + print("⚠️ MinIO недоступен, объект не удален") + return False + try: - self.client.remove_object(self.bucket_name, object_id) - print(f"Объект успешно удален из MinIO: {self.bucket_name}/{object_id}") + self.client.remove_object(self._bucket_name, object_id) + print(f"✅ Объект успешно удален из MinIO: {self._bucket_name}/{object_id}") return True except Exception as e: - print(f"Ошибка при удалении объекта из MinIO: {e}") + print(f"❌ Ошибка при удалении объекта из MinIO: {e}") return False def object_exists(self, object_id: str) -> bool: """Проверка существования объекта в MinIO""" + if self.client is None: + return False + try: - self.client.stat_object(self.bucket_name, object_id) + self.client.stat_object(self._bucket_name, object_id) return True except Exception: return False diff --git a/python_parser/app/__pycache__/__init__.cpython-313.pyc b/python_parser/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..c812a10 Binary files /dev/null and b/python_parser/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/app/__pycache__/main.cpython-313.pyc b/python_parser/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..98546da Binary files /dev/null and b/python_parser/app/__pycache__/main.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc b/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..1aec30e Binary files /dev/null and b/python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc b/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc new file mode 100644 index 0000000..cd2c909 Binary files /dev/null and b/python_parser/app/schemas/__pycache__/monitoring_fuel.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/server.cpython-313.pyc b/python_parser/app/schemas/__pycache__/server.cpython-313.pyc new file mode 100644 index 0000000..0d41df4 Binary files /dev/null and b/python_parser/app/schemas/__pycache__/server.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/svodka_ca.cpython-313.pyc b/python_parser/app/schemas/__pycache__/svodka_ca.cpython-313.pyc new file mode 100644 index 0000000..0e04d96 Binary files /dev/null and b/python_parser/app/schemas/__pycache__/svodka_ca.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/svodka_pm.cpython-313.pyc b/python_parser/app/schemas/__pycache__/svodka_pm.cpython-313.pyc new file mode 100644 index 0000000..88553be Binary files /dev/null and b/python_parser/app/schemas/__pycache__/svodka_pm.cpython-313.pyc differ diff --git a/python_parser/app/schemas/__pycache__/upload.cpython-313.pyc b/python_parser/app/schemas/__pycache__/upload.cpython-313.pyc new file mode 100644 index 0000000..c91559a Binary files /dev/null and b/python_parser/app/schemas/__pycache__/upload.cpython-313.pyc differ diff --git a/python_parser/check_services.py b/python_parser/check_services.py new file mode 100644 index 0000000..e03fd9f --- /dev/null +++ b/python_parser/check_services.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +Скрипт для быстрой диагностики всех сервисов NIN Excel Parsers +""" + +import requests +import subprocess +import sys +from pathlib import Path + +def check_port(port, service_name): + """Проверка доступности порта""" + try: + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(('localhost', port)) + sock.close() + + if result == 0: + print(f"✅ Порт {port} ({service_name}) - ОТКРЫТ") + return True + else: + print(f"❌ Порт {port} ({service_name}) - ЗАКРЫТ") + return False + except Exception as e: + print(f"❌ Ошибка проверки порта {port}: {e}") + return False + +def check_service(url, service_name): + """Проверка доступности HTTP сервиса""" + try: + response = requests.get(url, timeout=3) + if response.status_code == 200: + print(f"✅ {service_name} ({url}) - ДОСТУПЕН") + return True + else: + print(f"⚠️ {service_name} ({url}) - ОТВЕЧАЕТ, но статус {response.status_code}") + return True + except requests.exceptions.ConnectionError: + print(f"❌ {service_name} ({url}) - НЕ ДОСТУПЕН (Connection Error)") + return False + except requests.exceptions.Timeout: + print(f"⚠️ {service_name} ({url}) - ТАЙМАУТ") + return False + except Exception as e: + print(f"❌ {service_name} ({url}) - ОШИБКА: {e}") + return False + +def check_docker(): + """Проверка Docker""" + try: + result = subprocess.run(["docker", "--version"], capture_output=True, text=True) + if result.returncode == 0: + print(f"✅ Docker: {result.stdout.strip()}") + return True + else: + print("❌ Docker не работает") + return False + except FileNotFoundError: + print("❌ Docker не установлен") + return False + except Exception as e: + print(f"❌ Ошибка проверки Docker: {e}") + return False + +def check_docker_containers(): + """Проверка Docker контейнеров""" + try: + result = subprocess.run(["docker", "ps"], capture_output=True, text=True) + if result.returncode == 0: + if "minio" in result.stdout.lower(): + print("✅ MinIO контейнер запущен") + return True + else: + print("⚠️ MinIO контейнер не найден") + return False + else: + print("❌ Не удалось проверить Docker контейнеры") + return False + except Exception as e: + print(f"❌ Ошибка проверки контейнеров: {e}") + return False + +def check_python_packages(): + """Проверка Python пакетов""" + required_packages = ['fastapi', 'streamlit', 'pandas', 'minio', 'uvicorn'] + missing_packages = [] + + print("\n🔍 Проверка Python пакетов:") + for package in required_packages: + try: + __import__(package) + print(f"✅ {package}") + except ImportError: + print(f"❌ {package}") + missing_packages.append(package) + + if missing_packages: + print(f"\n⚠️ Отсутствуют пакеты: {', '.join(missing_packages)}") + print("Установите: pip install -r requirements.txt") + return False + + return True + +def main(): + """Основная функция диагностики""" + print("🔍 ДИАГНОСТИКА NIN Excel Parsers API") + print("=" * 50) + + # Проверка Python пакетов + packages_ok = check_python_packages() + + print("\n🔍 Проверка Docker:") + docker_ok = check_docker() + if docker_ok: + containers_ok = check_docker_containers() + else: + containers_ok = False + + print("\n🔍 Проверка портов:") + port_8000_ok = check_port(8000, "FastAPI") + port_8501_ok = check_port(8501, "Streamlit") + port_9000_ok = check_port(9000, "MinIO API") + port_9001_ok = check_port(9001, "MinIO Console") + + print("\n🔍 Проверка HTTP сервисов:") + fastapi_ok = check_service("http://localhost:8000/", "FastAPI") + streamlit_ok = check_service("http://localhost:8501/", "Streamlit") + minio_console_ok = check_service("http://localhost:9001/", "MinIO Console") + + print("\n" + "=" * 50) + print("📊 РЕЗУЛЬТАТЫ ДИАГНОСТИКИ:") + print("=" * 50) + + # Подсчет результатов + total_checks = 8 + passed_checks = sum([ + packages_ok, + docker_ok, + containers_ok, + port_8000_ok, + port_8501_ok, + port_9000_ok, + port_9001_ok, + fastapi_ok + ]) + + print(f"✅ Пройдено: {passed_checks}/{total_checks}") + + if passed_checks == total_checks: + print("\n🎉 Все сервисы работают корректно!") + print("📍 Доступные URL:") + print(" • Streamlit: http://localhost:8501") + print(" • FastAPI: http://localhost:8000") + print(" • API Docs: http://localhost:8000/docs") + print(" • MinIO: http://localhost:9001") + else: + print(f"\n⚠️ Проблемы обнаружены в {total_checks - passed_checks} сервисах") + + if not packages_ok: + print("\n🔧 РЕШЕНИЕ: Установите зависимости") + print("pip install -r requirements.txt") + + if not docker_ok: + print("\n🔧 РЕШЕНИЕ: Запустите Docker Desktop") + + if not containers_ok: + print("\n🔧 РЕШЕНИЕ: Запустите MinIO") + print("docker-compose up -d minio") + + if not port_8000_ok: + print("\n🔧 РЕШЕНИЕ: Запустите FastAPI сервер") + print("python run_dev.py") + + if not port_8501_ok: + print("\n🔧 РЕШЕНИЕ: Запустите Streamlit") + print("python run_streamlit.py") + + print("\n🚀 Для автоматического запуска используйте:") + print("python start_demo.py") + print("\n🔍 Для пошагового запуска используйте:") + print("python run_manual.py") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/python_parser/core/__pycache__/__init__.cpython-313.pyc b/python_parser/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..876b1a0 Binary files /dev/null and b/python_parser/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/python_parser/core/__pycache__/models.cpython-313.pyc b/python_parser/core/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..fcb688a Binary files /dev/null and b/python_parser/core/__pycache__/models.cpython-313.pyc differ diff --git a/python_parser/core/__pycache__/ports.cpython-313.pyc b/python_parser/core/__pycache__/ports.cpython-313.pyc new file mode 100644 index 0000000..062c066 Binary files /dev/null and b/python_parser/core/__pycache__/ports.cpython-313.pyc differ diff --git a/python_parser/core/__pycache__/services.cpython-313.pyc b/python_parser/core/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000..d655af4 Binary files /dev/null and b/python_parser/core/__pycache__/services.cpython-313.pyc differ diff --git a/python_parser/core/services.py b/python_parser/core/services.py index 85b2162..0d3f791 100644 --- a/python_parser/core/services.py +++ b/python_parser/core/services.py @@ -48,7 +48,7 @@ class ReportService: # Генерируем object_id object_id = f"nin_excel_data_{request.report_type}" - # Удаляем старый объект, если он существует + # Удаляем старый объект, если он существует и хранилище доступно if self.storage.object_exists(object_id): self.storage.delete_object(object_id) print(f"Старый объект удален: {object_id}") @@ -63,7 +63,7 @@ class ReportService: else: return UploadResult( success=False, - message="Ошибка при сохранении в хранилище" + message="Ошибка при сохранении в хранилище. Возможно, MinIO недоступен." ) finally: @@ -86,7 +86,7 @@ class ReportService: if not self.storage.object_exists(object_id): return DataResult( success=False, - message=f"Отчет типа '{request.report_type}' не найден" + message=f"Отчет типа '{request.report_type}' не найден. Возможно, MinIO недоступен или отчет не был загружен." ) # Загружаем DataFrame из хранилища @@ -94,7 +94,7 @@ class ReportService: if df is None: return DataResult( success=False, - message="Ошибка при загрузке данных из хранилища" + message="Ошибка при загрузке данных из хранилища. Возможно, MinIO недоступен." ) # Получаем парсер diff --git a/python_parser/docker-compose.yml b/python_parser/docker-compose.yml index cee6775..919ffe6 100644 --- a/python_parser/docker-compose.yml +++ b/python_parser/docker-compose.yml @@ -1,16 +1,32 @@ -version: '3.8' - services: minio: image: minio/minio:latest container_name: svodka_minio + ports: + - "9000:9000" # API порт + - "9001:9001" # Консоль порт environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin - ports: - - "9000:9000" - - "9001:9001" - volumes: - - ./minio:/data command: server /data --console-address ":9001" - restart: unless-stopped \ No newline at end of file + volumes: + - minio_data:/data + restart: unless-stopped + + fastapi: + build: . + container_name: svodka_fastapi + ports: + - "8000:8000" + environment: + - MINIO_ENDPOINT=minio:9000 + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin + - MINIO_SECURE=false + - MINIO_BUCKET=svodka-data + depends_on: + - minio + restart: unless-stopped + +volumes: + minio_data: \ No newline at end of file diff --git a/python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta b/python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta index d9788f6..60320f3 100644 Binary files a/python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta and b/python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta differ diff --git a/python_parser/minio/.minio.sys/buckets/.heal/mrf/list.bin b/python_parser/minio/.minio.sys/buckets/.heal/mrf/list.bin new file mode 100644 index 0000000..a356872 Binary files /dev/null and b/python_parser/minio/.minio.sys/buckets/.heal/mrf/list.bin differ diff --git a/python_parser/minio/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta b/python_parser/minio/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta index 7ff1b23..0a02b26 100644 Binary files a/python_parser/minio/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta and b/python_parser/minio/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta differ diff --git a/python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta b/python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta index a52d8cd..241fe0b 100644 Binary files a/python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta and b/python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta differ diff --git a/python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta b/python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta index 730862b..2bb41d0 100644 Binary files a/python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta and b/python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta differ diff --git a/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin.bkp/xl.meta b/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin.bkp/xl.meta index 306e110..1f31843 100644 Binary files a/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin.bkp/xl.meta and b/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin.bkp/xl.meta differ diff --git a/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin/xl.meta b/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin/xl.meta index 3a43764..0774939 100644 Binary files a/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin/xl.meta and b/python_parser/minio/.minio.sys/buckets/svodka-data/.usage-cache.bin/xl.meta differ diff --git a/python_parser/minio/.minio.sys/config/iam/sts/46KX4VILT0DATJ36SYGD/identity.json/xl.meta b/python_parser/minio/.minio.sys/config/iam/sts/46KX4VILT0DATJ36SYGD/identity.json/xl.meta new file mode 100644 index 0000000..57e8644 Binary files /dev/null and b/python_parser/minio/.minio.sys/config/iam/sts/46KX4VILT0DATJ36SYGD/identity.json/xl.meta differ diff --git a/python_parser/minio/.minio.sys/config/iam/sts/SIXCOIEK8M17XZ3JTN2B/identity.json/xl.meta b/python_parser/minio/.minio.sys/config/iam/sts/SIXCOIEK8M17XZ3JTN2B/identity.json/xl.meta deleted file mode 100644 index 7a8c896..0000000 Binary files a/python_parser/minio/.minio.sys/config/iam/sts/SIXCOIEK8M17XZ3JTN2B/identity.json/xl.meta and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/47c67d1c-8a41-4ea8-8a29-09319e725c00/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/47c67d1c-8a41-4ea8-8a29-09319e725c00/xl.meta.bkp deleted file mode 100644 index d7a33d0..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/47c67d1c-8a41-4ea8-8a29-09319e725c00/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/86b41a62-b469-455b-be17-9d44996ebdd3/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/86b41a62-b469-455b-be17-9d44996ebdd3/xl.meta.bkp deleted file mode 100644 index d161f9d..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/86b41a62-b469-455b-be17-9d44996ebdd3/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/aa8293d8-dcc1-4475-8490-1c6a8e9f19ad/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/aa8293d8-dcc1-4475-8490-1c6a8e9f19ad/xl.meta.bkp deleted file mode 100644 index cb23c18..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/aa8293d8-dcc1-4475-8490-1c6a8e9f19ad/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/d7b1a3d7-340d-468e-86cf-1a4321c26dbf/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/d7b1a3d7-340d-468e-86cf-1a4321c26dbf/xl.meta.bkp deleted file mode 100644 index eb42644..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/d7b1a3d7-340d-468e-86cf-1a4321c26dbf/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/ee3a854e-91ed-4a7c-ab72-f2df6ae6337a/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/ee3a854e-91ed-4a7c-ab72-f2df6ae6337a/xl.meta.bkp deleted file mode 100644 index 19e209c..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/ee3a854e-91ed-4a7c-ab72-f2df6ae6337a/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/.trash/f3730b42-e1d0-4653-b974-5938ec8af694/xl.meta.bkp b/python_parser/minio/.minio.sys/tmp/.trash/f3730b42-e1d0-4653-b974-5938ec8af694/xl.meta.bkp deleted file mode 100644 index 082dbdc..0000000 Binary files a/python_parser/minio/.minio.sys/tmp/.trash/f3730b42-e1d0-4653-b974-5938ec8af694/xl.meta.bkp and /dev/null differ diff --git a/python_parser/minio/.minio.sys/tmp/989d8b07-b593-41fc-9dbf-831f9b04f598 b/python_parser/minio/.minio.sys/tmp/060ca61d-ef6c-4011-9a9f-d083313258e8 similarity index 100% rename from python_parser/minio/.minio.sys/tmp/989d8b07-b593-41fc-9dbf-831f9b04f598 rename to python_parser/minio/.minio.sys/tmp/060ca61d-ef6c-4011-9a9f-d083313258e8 diff --git a/python_parser/requirements.txt b/python_parser/requirements.txt index 2e74635..6f0b33e 100644 --- a/python_parser/requirements.txt +++ b/python_parser/requirements.txt @@ -11,4 +11,5 @@ requests>=2.31.0 # pytest-cov>=4.0.0 # pytest-mock>=3.10.0 httpx>=0.24.0 -numpy \ No newline at end of file +numpy +streamlit>=1.28.0 \ No newline at end of file diff --git a/python_parser/run_dev.py b/python_parser/run_dev.py index a8a03b3..bbeb465 100644 --- a/python_parser/run_dev.py +++ b/python_parser/run_dev.py @@ -4,7 +4,7 @@ if __name__ == "__main__": uvicorn.run( "app.main:app", host="0.0.0.0", - port=8080, + port=8000, # Исправляем порт с 8080 на 8000 reload=True, reload_dirs=["app", "core", "adapters"], # Папки для отслеживания изменений reload_excludes=["*.pyc", "__pycache__", "*.log"], # Исключения diff --git a/python_parser/run_streamlit.py b/python_parser/run_streamlit.py new file mode 100644 index 0000000..7c63763 --- /dev/null +++ b/python_parser/run_streamlit.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +Запуск Streamlit интерфейса для NIN Excel Parsers API +""" + +import subprocess +import sys +import webbrowser +import time + +def main(): + """Основная функция""" + print("🚀 ЗАПУСК STREAMLIT ИНТЕРФЕЙСА") + print("=" * 50) + print("Убедитесь, что FastAPI сервер запущен на порту 8000") + print("=" * 50) + + # Проверяем, установлен ли Streamlit + try: + import streamlit + print(f"✅ Streamlit {streamlit.__version__} установлен") + except ImportError: + print("❌ Streamlit не установлен") + print("Установите: pip install streamlit") + return + + print("\n🚀 Запускаю Streamlit...") + print("📍 URL: http://localhost:8501") + print("🛑 Для остановки нажмите Ctrl+C") + + # Открываем браузер + try: + webbrowser.open("http://localhost:8501") + print("✅ Браузер открыт") + except Exception as e: + print(f"⚠️ Не удалось открыть браузер: {e}") + + # Запускаем Streamlit + try: + subprocess.run([ + sys.executable, "-m", "streamlit", "run", "streamlit_app.py", + "--server.port", "8501", + "--server.address", "localhost", + "--server.headless", "false", + "--browser.gatherUsageStats", "false" + ]) + except KeyboardInterrupt: + print("\n👋 Streamlit остановлен") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/python_parser/streamlit_app.py b/python_parser/streamlit_app.py new file mode 100644 index 0000000..98c4736 --- /dev/null +++ b/python_parser/streamlit_app.py @@ -0,0 +1,402 @@ +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://localhost: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_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, tab4 = st.tabs([ + "📤 Загрузка файлов", + "📊 Сводки ПМ", + "🏭 Сводки СА", + "⛽ Мониторинг топлива" + ]) + + # Вкладка 1: Загрузка файлов + with tab1: + st.header("📤 Загрузка файлов") + + col1, col2 = st.columns(2) + + with col1: + 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', 'Неизвестная ошибка')}") + + with col2: + 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.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)}") + + # Вкладка 2: Сводки ПМ + with tab2: + st.header("📊 Сводки ПМ") + + 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" + ) + + search = st.selectbox( + "Фильтр", + [None, "Итого"], + key="pm_single_search" + ) + + if st.button("🔍 Получить данные", key="pm_single_btn"): + if codes and columns: + with st.spinner("Получаю данные..."): + data = { + "id": og_id, + "codes": codes, + "columns": columns + } + if search: + data["search"] = search + + result, status = make_api_request("/svodka_pm/get_single_og", 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], + key="pm_total_codes" + ) + + columns_total = st.multiselect( + "Выберите столбцы", + ["ПП", "БП", "ТБ", "СЭБ", "НЭБ"], + default=["ПП", "СЭБ"], + key="pm_total_columns" + ) + + search_total = st.selectbox( + "Фильтр", + [None, "Итого"], + key="pm_total_search" + ) + + if st.button("🔍 Получить данные по всем ОГ", key="pm_total_btn"): + if codes_total and columns_total: + with st.spinner("Получаю данные..."): + data = { + "codes": codes_total, + "columns": columns_total + } + if search_total: + data["search"] = search_total + + result, status = make_api_request("/svodka_pm/get_total_ogs", data) + + if status == 200: + st.success("✅ Данные получены") + st.json(result) + else: + st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}") + else: + st.warning("⚠️ Выберите коды и столбцы") + + # Вкладка 3: Сводки СА + with tab3: + st.header("🏭 Сводки СА") + + st.subheader("Получение данных из сводки СА") + + modes = st.multiselect( + "Выберите режимы", + ["plan", "fact", "normativ"], + default=["plan", "fact"], + key="ca_modes" + ) + + tables = st.multiselect( + "Выберите таблицы", + ["ТиП, %", "Топливо итого, тонн", "Топливо итого, %", + "Топливо на технологию, тонн", "Топливо на технологию, %", + "Топливо на энергетику, тонн", "Топливо на энергетику, %", + "Потери итого, тонн", "Потери итого, %"], + default=["ТиП, %", "Топливо итого, тонн"], + key="ca_tables" + ) + + if st.button("🔍 Получить данные СА", key="ca_btn"): + if modes and tables: + with st.spinner("Получаю данные..."): + 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("⚠️ Выберите режимы и таблицы") + + # Вкладка 4: Мониторинг топлива + with tab4: + st.header("⛽ Мониторинг топлива") + + 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 = { + "columns": columns_fuel + } + + result, status = make_api_request("/monitoring_fuel/get_total_by_columns", 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 = { + "month": month + } + + result, status = make_api_request("/monitoring_fuel/get_month_by_code", 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