runs
This commit is contained in:
28
python_parser/.streamlit/config.toml
Normal file
28
python_parser/.streamlit/config.toml
Normal file
@@ -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
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
FROM repo-dev.predix.rosneft.ru/python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
WORKDIR /app
|
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 .
|
COPY requirements.txt .
|
||||||
|
|
||||||
RUN mkdir -p vendor
|
# Устанавливаем Python зависимости
|
||||||
RUN pip download -r /app/requirements.txt --no-binary=:none: -d /app/vendor
|
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
|
EXPOSE 8000
|
||||||
# ENV KAFKA_CLIENT_USERNAME=cf-service
|
|
||||||
|
|
||||||
# CMD ["python", "/app/run_dev.py"]
|
# Команда запуска
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
66
python_parser/QUICK_START.md
Normal file
66
python_parser/QUICK_START.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
197
python_parser/README.md
Normal file
197
python_parser/README.md
Normal file
@@ -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=.
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
Проект разработан для внутреннего использования НИН.
|
||||||
186
python_parser/README_STREAMLIT.md
Normal file
186
python_parser/README_STREAMLIT.md
Normal file
@@ -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
|
||||||
BIN
python_parser/adapters/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
python_parser/adapters/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/adapters/__pycache__/pconfig.cpython-313.pyc
Normal file
BIN
python_parser/adapters/__pycache__/pconfig.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/adapters/__pycache__/storage.cpython-313.pyc
Normal file
BIN
python_parser/adapters/__pycache__/storage.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,24 +15,53 @@ class MinIOStorageAdapter(StoragePort):
|
|||||||
"""Адаптер для MinIO хранилища"""
|
"""Адаптер для MinIO хранилища"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.client = Minio(
|
self._client = None
|
||||||
os.getenv("MINIO_ENDPOINT", "localhost:9000"),
|
self._bucket_name = os.getenv("MINIO_BUCKET", "svodka-data")
|
||||||
access_key=os.getenv("MINIO_ACCESS_KEY", "minioadmin"),
|
self._endpoint = os.getenv("MINIO_ENDPOINT", "localhost:9000")
|
||||||
secret_key=os.getenv("MINIO_SECRET_KEY", "minioadmin"),
|
self._access_key = os.getenv("MINIO_ACCESS_KEY", "minioadmin")
|
||||||
secure=os.getenv("MINIO_SECURE", "false").lower() == "true",
|
self._secret_key = os.getenv("MINIO_SECRET_KEY", "minioadmin")
|
||||||
cert_check=False
|
self._secure = os.getenv("MINIO_SECURE", "false").lower() == "true"
|
||||||
)
|
|
||||||
self.bucket_name = os.getenv("MINIO_BUCKET", "svodka-data")
|
@property
|
||||||
self._ensure_bucket_exists()
|
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):
|
def _ensure_bucket_exists(self):
|
||||||
"""Проверка существования bucket и создание при необходимости"""
|
"""Проверка существования bucket и создание при необходимости"""
|
||||||
if not self.client.bucket_exists(self.bucket_name):
|
if self.client is None:
|
||||||
self.client.make_bucket(self.bucket_name)
|
return False
|
||||||
print(f"Bucket '{self.bucket_name}' создан")
|
|
||||||
|
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:
|
def save_dataframe(self, df: pd.DataFrame, object_id: str) -> bool:
|
||||||
"""Сохранение DataFrame в MinIO"""
|
"""Сохранение DataFrame в MinIO"""
|
||||||
|
if self.client is None:
|
||||||
|
print("⚠️ MinIO недоступен, данные не сохранены")
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Сериализуем DataFrame
|
# Сериализуем DataFrame
|
||||||
data = pickle.dumps(df)
|
data = pickle.dumps(df)
|
||||||
@@ -42,24 +71,28 @@ class MinIOStorageAdapter(StoragePort):
|
|||||||
|
|
||||||
# Загружаем в MinIO
|
# Загружаем в MinIO
|
||||||
self.client.put_object(
|
self.client.put_object(
|
||||||
self.bucket_name,
|
self._bucket_name,
|
||||||
object_id,
|
object_id,
|
||||||
data_stream,
|
data_stream,
|
||||||
length=len(data),
|
length=len(data),
|
||||||
content_type='application/octet-stream'
|
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
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при сохранении в MinIO: {e}")
|
print(f"❌ Ошибка при сохранении в MinIO: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def load_dataframe(self, object_id: str) -> Optional[pd.DataFrame]:
|
def load_dataframe(self, object_id: str) -> Optional[pd.DataFrame]:
|
||||||
"""Загрузка DataFrame из MinIO"""
|
"""Загрузка DataFrame из MinIO"""
|
||||||
|
if self.client is None:
|
||||||
|
print("⚠️ MinIO недоступен, данные не загружены")
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получаем объект из MinIO
|
# Получаем объект из MinIO
|
||||||
response = self.client.get_object(self.bucket_name, object_id)
|
response = self.client.get_object(self._bucket_name, object_id)
|
||||||
|
|
||||||
# Читаем данные
|
# Читаем данные
|
||||||
data = response.read()
|
data = response.read()
|
||||||
@@ -69,7 +102,7 @@ class MinIOStorageAdapter(StoragePort):
|
|||||||
|
|
||||||
return df
|
return df
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при загрузке данных из MinIO: {e}")
|
print(f"❌ Ошибка при загрузке данных из MinIO: {e}")
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
if 'response' in locals():
|
if 'response' in locals():
|
||||||
@@ -78,18 +111,25 @@ class MinIOStorageAdapter(StoragePort):
|
|||||||
|
|
||||||
def delete_object(self, object_id: str) -> bool:
|
def delete_object(self, object_id: str) -> bool:
|
||||||
"""Удаление объекта из MinIO"""
|
"""Удаление объекта из MinIO"""
|
||||||
|
if self.client is None:
|
||||||
|
print("⚠️ MinIO недоступен, объект не удален")
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client.remove_object(self.bucket_name, object_id)
|
self.client.remove_object(self._bucket_name, object_id)
|
||||||
print(f"Объект успешно удален из MinIO: {self.bucket_name}/{object_id}")
|
print(f"✅ Объект успешно удален из MinIO: {self._bucket_name}/{object_id}")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при удалении объекта из MinIO: {e}")
|
print(f"❌ Ошибка при удалении объекта из MinIO: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def object_exists(self, object_id: str) -> bool:
|
def object_exists(self, object_id: str) -> bool:
|
||||||
"""Проверка существования объекта в MinIO"""
|
"""Проверка существования объекта в MinIO"""
|
||||||
|
if self.client is None:
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client.stat_object(self.bucket_name, object_id)
|
self.client.stat_object(self._bucket_name, object_id)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|||||||
BIN
python_parser/app/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
python_parser/app/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/app/__pycache__/main.cpython-313.pyc
Normal file
BIN
python_parser/app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
python_parser/app/schemas/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
python_parser/app/schemas/__pycache__/server.cpython-313.pyc
Normal file
BIN
python_parser/app/schemas/__pycache__/server.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/app/schemas/__pycache__/svodka_ca.cpython-313.pyc
Normal file
BIN
python_parser/app/schemas/__pycache__/svodka_ca.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/app/schemas/__pycache__/svodka_pm.cpython-313.pyc
Normal file
BIN
python_parser/app/schemas/__pycache__/svodka_pm.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/app/schemas/__pycache__/upload.cpython-313.pyc
Normal file
BIN
python_parser/app/schemas/__pycache__/upload.cpython-313.pyc
Normal file
Binary file not shown.
185
python_parser/check_services.py
Normal file
185
python_parser/check_services.py
Normal file
@@ -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()
|
||||||
BIN
python_parser/core/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
python_parser/core/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/core/__pycache__/models.cpython-313.pyc
Normal file
BIN
python_parser/core/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/core/__pycache__/ports.cpython-313.pyc
Normal file
BIN
python_parser/core/__pycache__/ports.cpython-313.pyc
Normal file
Binary file not shown.
BIN
python_parser/core/__pycache__/services.cpython-313.pyc
Normal file
BIN
python_parser/core/__pycache__/services.cpython-313.pyc
Normal file
Binary file not shown.
@@ -48,7 +48,7 @@ class ReportService:
|
|||||||
# Генерируем object_id
|
# Генерируем object_id
|
||||||
object_id = f"nin_excel_data_{request.report_type}"
|
object_id = f"nin_excel_data_{request.report_type}"
|
||||||
|
|
||||||
# Удаляем старый объект, если он существует
|
# Удаляем старый объект, если он существует и хранилище доступно
|
||||||
if self.storage.object_exists(object_id):
|
if self.storage.object_exists(object_id):
|
||||||
self.storage.delete_object(object_id)
|
self.storage.delete_object(object_id)
|
||||||
print(f"Старый объект удален: {object_id}")
|
print(f"Старый объект удален: {object_id}")
|
||||||
@@ -63,7 +63,7 @@ class ReportService:
|
|||||||
else:
|
else:
|
||||||
return UploadResult(
|
return UploadResult(
|
||||||
success=False,
|
success=False,
|
||||||
message="Ошибка при сохранении в хранилище"
|
message="Ошибка при сохранении в хранилище. Возможно, MinIO недоступен."
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
@@ -86,7 +86,7 @@ class ReportService:
|
|||||||
if not self.storage.object_exists(object_id):
|
if not self.storage.object_exists(object_id):
|
||||||
return DataResult(
|
return DataResult(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"Отчет типа '{request.report_type}' не найден"
|
message=f"Отчет типа '{request.report_type}' не найден. Возможно, MinIO недоступен или отчет не был загружен."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Загружаем DataFrame из хранилища
|
# Загружаем DataFrame из хранилища
|
||||||
@@ -94,7 +94,7 @@ class ReportService:
|
|||||||
if df is None:
|
if df is None:
|
||||||
return DataResult(
|
return DataResult(
|
||||||
success=False,
|
success=False,
|
||||||
message="Ошибка при загрузке данных из хранилища"
|
message="Ошибка при загрузке данных из хранилища. Возможно, MinIO недоступен."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Получаем парсер
|
# Получаем парсер
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:latest
|
image: minio/minio:latest
|
||||||
container_name: svodka_minio
|
container_name: svodka_minio
|
||||||
|
ports:
|
||||||
|
- "9000:9000" # API порт
|
||||||
|
- "9001:9001" # Консоль порт
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: minioadmin
|
MINIO_ROOT_USER: minioadmin
|
||||||
MINIO_ROOT_PASSWORD: minioadmin
|
MINIO_ROOT_PASSWORD: minioadmin
|
||||||
ports:
|
|
||||||
- "9000:9000"
|
|
||||||
- "9001:9001"
|
|
||||||
volumes:
|
|
||||||
- ./minio:/data
|
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
restart: unless-stopped
|
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:
|
||||||
Binary file not shown.
BIN
python_parser/minio/.minio.sys/buckets/.heal/mrf/list.bin
Normal file
BIN
python_parser/minio/.minio.sys/buckets/.heal/mrf/list.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,4 +11,5 @@ requests>=2.31.0
|
|||||||
# pytest-cov>=4.0.0
|
# pytest-cov>=4.0.0
|
||||||
# pytest-mock>=3.10.0
|
# pytest-mock>=3.10.0
|
||||||
httpx>=0.24.0
|
httpx>=0.24.0
|
||||||
numpy
|
numpy
|
||||||
|
streamlit>=1.28.0
|
||||||
@@ -4,7 +4,7 @@ if __name__ == "__main__":
|
|||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"app.main:app",
|
"app.main:app",
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=8080,
|
port=8000, # Исправляем порт с 8080 на 8000
|
||||||
reload=True,
|
reload=True,
|
||||||
reload_dirs=["app", "core", "adapters"], # Папки для отслеживания изменений
|
reload_dirs=["app", "core", "adapters"], # Папки для отслеживания изменений
|
||||||
reload_excludes=["*.pyc", "__pycache__", "*.log"], # Исключения
|
reload_excludes=["*.pyc", "__pycache__", "*.log"], # Исключения
|
||||||
|
|||||||
51
python_parser/run_streamlit.py
Normal file
51
python_parser/run_streamlit.py
Normal file
@@ -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()
|
||||||
402
python_parser/streamlit_app.py
Normal file
402
python_parser/streamlit_app.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user