all in docker
This commit is contained in:
@@ -4,7 +4,19 @@ API для парсинга Excel отчетов нефтеперерабаты
|
|||||||
|
|
||||||
## 🚀 Быстрый запуск
|
## 🚀 Быстрый запуск
|
||||||
|
|
||||||
### **Вариант 1: Только MinIO в Docker + FastAPI локально**
|
### **Вариант 1: Все сервисы в Docker (рекомендуется)**
|
||||||
|
```bash
|
||||||
|
# Запуск всех сервисов: MinIO + FastAPI + Streamlit
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Доступ:
|
||||||
|
# - MinIO Console: http://localhost:9001
|
||||||
|
# - FastAPI: http://localhost:8000
|
||||||
|
# - Streamlit: http://localhost:8501
|
||||||
|
# - API Docs: http://localhost:8000/docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Вариант 2: Только MinIO в Docker + FastAPI локально**
|
||||||
```bash
|
```bash
|
||||||
# Запуск MinIO в Docker
|
# Запуск MinIO в Docker
|
||||||
docker-compose up -d minio
|
docker-compose up -d minio
|
||||||
@@ -13,16 +25,8 @@ docker-compose up -d minio
|
|||||||
python run_dev.py
|
python run_dev.py
|
||||||
|
|
||||||
# В отдельном терминале запуск Streamlit
|
# В отдельном терминале запуск Streamlit
|
||||||
python run_streamlit.py
|
cd streamlit_app
|
||||||
```
|
streamlit run app.py
|
||||||
|
|
||||||
### **Вариант 2: MinIO + FastAPI в Docker + Streamlit локально**
|
|
||||||
```bash
|
|
||||||
# Запуск MinIO и FastAPI в Docker
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# В отдельном терминале запуск Streamlit
|
|
||||||
python run_streamlit.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### **Вариант 3: Только MinIO в Docker**
|
### **Вариант 3: Только MinIO в Docker**
|
||||||
@@ -37,13 +41,6 @@ docker-compose up -d minio
|
|||||||
- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов
|
- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов
|
||||||
- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API
|
- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API
|
||||||
|
|
||||||
## 🔧 Диагностика
|
|
||||||
|
|
||||||
Для проверки состояния всех сервисов:
|
|
||||||
```bash
|
|
||||||
python check_services.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛑 Остановка
|
## 🛑 Остановка
|
||||||
|
|
||||||
### Остановка Docker сервисов:
|
### Остановка Docker сервисов:
|
||||||
@@ -55,9 +52,9 @@ docker-compose down
|
|||||||
docker-compose stop minio
|
docker-compose stop minio
|
||||||
```
|
```
|
||||||
|
|
||||||
### Остановка Streamlit:
|
### Остановка локальных сервисов:
|
||||||
```bash
|
```bash
|
||||||
# Нажмите Ctrl+C в терминале с Streamlit
|
# Нажмите Ctrl+C в терминале с FastAPI/Streamlit
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📁 Структура проекта
|
## 📁 Структура проекта
|
||||||
@@ -74,124 +71,73 @@ python_parser/
|
|||||||
├── adapters/ # Адаптеры для внешних систем
|
├── adapters/ # Адаптеры для внешних систем
|
||||||
│ ├── storage.py # MinIO адаптер
|
│ ├── storage.py # MinIO адаптер
|
||||||
│ └── parsers/ # Парсеры Excel файлов
|
│ └── parsers/ # Парсеры Excel файлов
|
||||||
|
├── streamlit_app/ # Изолированный Streamlit пакет
|
||||||
|
│ ├── app.py # Основное Streamlit приложение
|
||||||
|
│ ├── requirements.txt # Зависимости Streamlit
|
||||||
|
│ ├── Dockerfile # Docker образ для Streamlit
|
||||||
|
│ └── .streamlit/ # Конфигурация Streamlit
|
||||||
├── data/ # Тестовые данные
|
├── data/ # Тестовые данные
|
||||||
├── docker-compose.yml # Docker Compose конфигурация
|
├── docker-compose.yml # Docker Compose конфигурация
|
||||||
├── Dockerfile # Docker образ для FastAPI
|
├── Dockerfile # Docker образ для FastAPI
|
||||||
├── run_dev.py # Запуск FastAPI локально
|
└── run_dev.py # Запуск FastAPI локально
|
||||||
├── run_streamlit.py # Запуск Streamlit
|
|
||||||
└── check_services.py # Диагностика сервисов
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔍 Доступные эндпоинты
|
## 🔍 Доступные эндпоинты
|
||||||
|
|
||||||
- **GET /** - Информация об API
|
- **GET /** - Информация об API
|
||||||
- **GET /docs** - Swagger документация
|
- **GET /docs** - Swagger документация
|
||||||
|
- **GET /parsers** - Список доступных парсеров
|
||||||
|
- **GET /parsers/{parser_name}/getters** - Информация о геттерах парсера
|
||||||
- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ
|
- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ
|
||||||
- **POST /svodka_ca/upload-zip** - Загрузка сводок ЦА
|
- **POST /svodka_ca/upload** - Загрузка сводок ЦА
|
||||||
- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива
|
- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива
|
||||||
- **GET /svodka_pm/data** - Получение данных сводок ПМ
|
- **POST /svodka_pm/get_data** - Получение данных сводок ПМ
|
||||||
- **GET /svodka_ca/data** - Получение данных сводок ЦА
|
- **POST /svodka_ca/get_data** - Получение данных сводок ЦА
|
||||||
- **GET /monitoring_fuel/data** - Получение данных мониторинга топлива
|
- **POST /monitoring_fuel/get_data** - Получение данных мониторинга топлива
|
||||||
|
|
||||||
## 📊 Поддерживаемые типы отчетов
|
## 📊 Поддерживаемые типы отчетов
|
||||||
|
|
||||||
1. **svodka_pm** - Сводки по переработке нефти (ПМ)
|
1. **svodka_pm** - Сводки по переработке нефти (ПМ)
|
||||||
|
- Геттеры: `single_og`, `total_ogs`
|
||||||
2. **svodka_ca** - Сводки по переработке нефти (ЦА)
|
2. **svodka_ca** - Сводки по переработке нефти (ЦА)
|
||||||
|
- Геттеры: `get_data`
|
||||||
3. **monitoring_fuel** - Мониторинг топлива
|
3. **monitoring_fuel** - Мониторинг топлива
|
||||||
|
- Геттеры: `total_by_columns`, `month_by_code`
|
||||||
|
|
||||||
## 🐳 Docker команды
|
## 🏗️ Архитектура
|
||||||
|
|
||||||
### Сборка и запуск:
|
Проект использует **Hexagonal Architecture (Ports and Adapters)**:
|
||||||
|
|
||||||
|
- **Порты (Ports)**: Интерфейсы для бизнес-логики
|
||||||
|
- **Адаптеры (Adapters)**: Реализации для внешних систем
|
||||||
|
- **Сервисы (Services)**: Бизнес-логика приложения
|
||||||
|
|
||||||
|
### Система геттеров парсеров
|
||||||
|
|
||||||
|
Каждый парсер может иметь несколько методов получения данных (геттеров):
|
||||||
|
- Регистрация геттеров в словаре с метаданными
|
||||||
|
- Валидация параметров для каждого геттера
|
||||||
|
- Единый интерфейс `get_value(getter_name, params)`
|
||||||
|
|
||||||
|
## 🐳 Docker
|
||||||
|
|
||||||
|
### Сборка образов:
|
||||||
```bash
|
```bash
|
||||||
# Все сервисы
|
# FastAPI
|
||||||
docker-compose up -d --build
|
docker build -t nin-fastapi .
|
||||||
|
|
||||||
|
# Streamlit
|
||||||
|
docker build -t nin-streamlit ./streamlit_app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Запуск отдельных сервисов:
|
||||||
|
```bash
|
||||||
# Только MinIO
|
# Только MinIO
|
||||||
docker-compose up -d minio
|
docker-compose up -d minio
|
||||||
|
|
||||||
# Только FastAPI (требует MinIO)
|
# MinIO + FastAPI
|
||||||
docker-compose up -d fastapi
|
docker-compose up -d minio fastapi
|
||||||
```
|
|
||||||
|
|
||||||
### Просмотр логов:
|
|
||||||
```bash
|
|
||||||
# Все сервисы
|
# Все сервисы
|
||||||
docker-compose logs
|
docker-compose up -d
|
||||||
|
```
|
||||||
# Конкретный сервис
|
|
||||||
docker-compose logs fastapi
|
|
||||||
docker-compose logs minio
|
|
||||||
```
|
|
||||||
|
|
||||||
### Остановка:
|
|
||||||
```bash
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Устранение неполадок
|
|
||||||
|
|
||||||
### Проблема: "Streamlit не может подключиться к FastAPI"
|
|
||||||
|
|
||||||
**Симптомы:**
|
|
||||||
- Streamlit открывается, но показывает "API недоступен по адресу http://localhost:8000"
|
|
||||||
- FastAPI не отвечает на порту 8000
|
|
||||||
|
|
||||||
**Решения:**
|
|
||||||
|
|
||||||
1. **Проверьте порты:**
|
|
||||||
```bash
|
|
||||||
# Windows
|
|
||||||
netstat -an | findstr :8000
|
|
||||||
|
|
||||||
# Linux/Mac
|
|
||||||
netstat -an | grep :8000
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Перезапустите FastAPI:**
|
|
||||||
```bash
|
|
||||||
# Остановите текущий процесс (Ctrl+C)
|
|
||||||
python run_dev.py
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Проверьте логи Docker:**
|
|
||||||
```bash
|
|
||||||
docker-compose logs fastapi
|
|
||||||
```
|
|
||||||
|
|
||||||
### Проблема: "MinIO недоступен"
|
|
||||||
|
|
||||||
**Решения:**
|
|
||||||
1. Запустите Docker Desktop
|
|
||||||
2. Проверьте статус контейнера: `docker ps`
|
|
||||||
3. Перезапустите MinIO: `docker-compose restart minio`
|
|
||||||
|
|
||||||
### Проблема: "Порт уже занят"
|
|
||||||
|
|
||||||
**Решения:**
|
|
||||||
1. Найдите процесс: `netstat -ano | findstr :8000`
|
|
||||||
2. Остановите процесс: `taskkill /PID <номер_процесса>`
|
|
||||||
3. Или используйте другой порт в конфигурации
|
|
||||||
|
|
||||||
## 🚀 Разработка
|
|
||||||
|
|
||||||
### Добавление нового парсера:
|
|
||||||
|
|
||||||
1. Создайте файл в `adapters/parsers/`
|
|
||||||
2. Реализуйте интерфейс `ParserPort`
|
|
||||||
3. Добавьте в `core/services.py`
|
|
||||||
4. Создайте схемы в `app/schemas/`
|
|
||||||
5. Добавьте эндпоинты в `app/main.py`
|
|
||||||
|
|
||||||
### Тестирование:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Запуск тестов
|
|
||||||
pytest
|
|
||||||
|
|
||||||
# Запуск с покрытием
|
|
||||||
pytest --cov=.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Лицензия
|
|
||||||
|
|
||||||
Проект разработан для внутреннего использования НИН.
|
|
||||||
@@ -28,5 +28,16 @@ services:
|
|||||||
- minio
|
- minio
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
streamlit:
|
||||||
|
build: ./streamlit_app
|
||||||
|
container_name: svodka_streamlit
|
||||||
|
ports:
|
||||||
|
- "8501:8501"
|
||||||
|
environment:
|
||||||
|
- API_BASE_URL=http://fastapi:8000
|
||||||
|
depends_on:
|
||||||
|
- fastapi
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
minio_data:
|
minio_data:
|
||||||
@@ -1,19 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Запуск Streamlit интерфейса для NIN Excel Parsers API
|
Запуск Streamlit интерфейса локально из изолированного пакета
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import time
|
import os
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Основная функция"""
|
"""Основная функция"""
|
||||||
print("🚀 ЗАПУСК STREAMLIT ИНТЕРФЕЙСА")
|
print("🚀 ЗАПУСК STREAMLIT ИЗ ИЗОЛИРОВАННОГО ПАКЕТА")
|
||||||
print("=" * 50)
|
print("=" * 60)
|
||||||
print("Убедитесь, что FastAPI сервер запущен на порту 8000")
|
print("Убедитесь, что FastAPI сервер запущен на порту 8000")
|
||||||
print("=" * 50)
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Проверяем, существует ли папка streamlit_app
|
||||||
|
if not os.path.exists("streamlit_app"):
|
||||||
|
print("❌ Папка streamlit_app не найдена")
|
||||||
|
print("Создайте изолированный пакет или используйте docker-compose up -d")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Переходим в папку streamlit_app
|
||||||
|
os.chdir("streamlit_app")
|
||||||
|
|
||||||
# Проверяем, установлен ли Streamlit
|
# Проверяем, установлен ли Streamlit
|
||||||
try:
|
try:
|
||||||
@@ -21,7 +30,7 @@ def main():
|
|||||||
print(f"✅ Streamlit {streamlit.__version__} установлен")
|
print(f"✅ Streamlit {streamlit.__version__} установлен")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("❌ Streamlit не установлен")
|
print("❌ Streamlit не установлен")
|
||||||
print("Установите: pip install streamlit")
|
print("Установите: pip install -r requirements.txt")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("\n🚀 Запускаю Streamlit...")
|
print("\n🚀 Запускаю Streamlit...")
|
||||||
@@ -38,7 +47,7 @@ def main():
|
|||||||
# Запускаем Streamlit
|
# Запускаем Streamlit
|
||||||
try:
|
try:
|
||||||
subprocess.run([
|
subprocess.run([
|
||||||
sys.executable, "-m", "streamlit", "run", "streamlit_app.py",
|
sys.executable, "-m", "streamlit", "run", "app.py",
|
||||||
"--server.port", "8501",
|
"--server.port", "8501",
|
||||||
"--server.address", "localhost",
|
"--server.address", "localhost",
|
||||||
"--server.headless", "false",
|
"--server.headless", "false",
|
||||||
31
python_parser/streamlit_app/.dockerignore
Normal file
31
python_parser/streamlit_app/.dockerignore
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
.tox
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.log
|
||||||
|
.git
|
||||||
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
.hypothesis
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
23
python_parser/streamlit_app/Dockerfile
Normal file
23
python_parser/streamlit_app/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Устанавливаем системные зависимости
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Копируем файлы зависимостей
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Устанавливаем Python зависимости
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Копируем код приложения
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Открываем порт
|
||||||
|
EXPOSE 8501
|
||||||
|
|
||||||
|
# Команда запуска
|
||||||
|
CMD ["streamlit", "run", "app.py", "--server.port", "8501", "--server.address", "0.0.0.0"]
|
||||||
44
python_parser/streamlit_app/README.md
Normal file
44
python_parser/streamlit_app/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 📊 Streamlit App - NIN Excel Parsers API
|
||||||
|
|
||||||
|
Изолированное Streamlit приложение для демонстрации работы NIN Excel Parsers API.
|
||||||
|
|
||||||
|
## 🚀 Запуск
|
||||||
|
|
||||||
|
### Локально:
|
||||||
|
```bash
|
||||||
|
cd streamlit_app
|
||||||
|
pip install -r requirements.txt
|
||||||
|
streamlit run app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### В Docker:
|
||||||
|
```bash
|
||||||
|
docker build -t streamlit-app .
|
||||||
|
docker run -p 8501:8501 streamlit-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Переменные окружения:
|
||||||
|
- `API_BASE_URL` - адрес FastAPI сервера (по умолчанию: `http://fastapi:8000`)
|
||||||
|
|
||||||
|
### Параметры Streamlit:
|
||||||
|
- Порт: 8501
|
||||||
|
- Адрес: 0.0.0.0 (для Docker)
|
||||||
|
- Режим: headless (для Docker)
|
||||||
|
|
||||||
|
## 📁 Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
streamlit_app/
|
||||||
|
├── app.py # Основное приложение
|
||||||
|
├── requirements.txt # Зависимости Python
|
||||||
|
├── Dockerfile # Docker образ
|
||||||
|
├── .streamlit/ # Конфигурация Streamlit
|
||||||
|
│ └── config.toml # Настройки
|
||||||
|
└── README.md # Документация
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 Доступ
|
||||||
|
|
||||||
|
После запуска приложение доступно по адресу: **http://localhost:8501**
|
||||||
447
python_parser/streamlit_app/app.py
Normal file
447
python_parser/streamlit_app/app.py
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import pandas as pd
|
||||||
|
import io
|
||||||
|
import zipfile
|
||||||
|
from typing import Dict, Any
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Конфигурация страницы
|
||||||
|
st.set_page_config(
|
||||||
|
page_title="NIN Excel Parsers API Demo",
|
||||||
|
page_icon="📊",
|
||||||
|
layout="wide",
|
||||||
|
initial_sidebar_state="expanded"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Конфигурация API - используем переменную окружения или значение по умолчанию
|
||||||
|
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000")
|
||||||
|
|
||||||
|
def check_api_health():
|
||||||
|
"""Проверка доступности API"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/", timeout=5)
|
||||||
|
return response.status_code == 200
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_available_parsers():
|
||||||
|
"""Получение списка доступных парсеров"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/parsers")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()["parsers"]
|
||||||
|
return []
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_parser_getters(parser_name: str):
|
||||||
|
"""Получение информации о геттерах парсера"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/parsers/{parser_name}/getters")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return {}
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_server_info():
|
||||||
|
"""Получение информации о сервере"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/server-info")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return {}
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def upload_file_to_api(endpoint: str, file_data: bytes, filename: str):
|
||||||
|
"""Загрузка файла на API"""
|
||||||
|
try:
|
||||||
|
files = {"zip_file": (filename, file_data, "application/zip")}
|
||||||
|
response = requests.post(f"{API_BASE_URL}{endpoint}", files=files)
|
||||||
|
return response.json(), response.status_code
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
def make_api_request(endpoint: str, data: Dict[str, Any]):
|
||||||
|
"""Выполнение API запроса"""
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{API_BASE_URL}{endpoint}", json=data)
|
||||||
|
return response.json(), response.status_code
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
def main():
|
||||||
|
st.title("🚀 NIN Excel Parsers API - Демонстрация")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Проверка доступности API
|
||||||
|
if not check_api_health():
|
||||||
|
st.error(f"❌ API недоступен по адресу {API_BASE_URL}")
|
||||||
|
st.info("Убедитесь, что FastAPI сервер запущен")
|
||||||
|
return
|
||||||
|
|
||||||
|
st.success(f"✅ API доступен по адресу {API_BASE_URL}")
|
||||||
|
|
||||||
|
# Боковая панель с информацией
|
||||||
|
with st.sidebar:
|
||||||
|
st.header("ℹ️ Информация")
|
||||||
|
|
||||||
|
# Информация о сервере
|
||||||
|
server_info = get_server_info()
|
||||||
|
if server_info:
|
||||||
|
st.subheader("Сервер")
|
||||||
|
st.write(f"PID: {server_info.get('process_id', 'N/A')}")
|
||||||
|
st.write(f"CPU ядер: {server_info.get('cpu_cores', 'N/A')}")
|
||||||
|
st.write(f"Память: {server_info.get('memory_mb', 'N/A'):.1f} MB")
|
||||||
|
|
||||||
|
# Доступные парсеры
|
||||||
|
parsers = get_available_parsers()
|
||||||
|
if parsers:
|
||||||
|
st.subheader("Доступные парсеры")
|
||||||
|
for parser in parsers:
|
||||||
|
st.write(f"• {parser}")
|
||||||
|
|
||||||
|
# Основные вкладки - по одной на каждый парсер
|
||||||
|
tab1, tab2, tab3 = st.tabs([
|
||||||
|
"📊 Сводки ПМ",
|
||||||
|
"🏭 Сводки СА",
|
||||||
|
"⛽ Мониторинг топлива"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Вкладка 1: Сводки ПМ - полный функционал
|
||||||
|
with tab1:
|
||||||
|
st.header("📊 Сводки ПМ - Полный функционал")
|
||||||
|
|
||||||
|
# Получаем информацию о геттерах
|
||||||
|
getters_info = get_parser_getters("svodka_pm")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_pm = st.file_uploader(
|
||||||
|
"Выберите ZIP архив со сводками ПМ",
|
||||||
|
type=['zip'],
|
||||||
|
key="pm_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_pm is not None:
|
||||||
|
if st.button("📤 Загрузить сводки ПМ", key="upload_pm_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
result, status = upload_file_to_api(
|
||||||
|
"/svodka_pm/upload-zip",
|
||||||
|
uploaded_pm.read(),
|
||||||
|
uploaded_pm.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
# Показываем доступные геттеры
|
||||||
|
if getters_info and "getters" in getters_info:
|
||||||
|
st.info("📋 Доступные геттеры:")
|
||||||
|
for getter_name, getter_info in getters_info["getters"].items():
|
||||||
|
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||||
|
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||||
|
if getter_info.get('optional_params'):
|
||||||
|
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Данные по одному ОГ")
|
||||||
|
|
||||||
|
og_id = st.selectbox(
|
||||||
|
"Выберите ОГ",
|
||||||
|
["SNPZ", "KNPZ", "ANHK", "AchNPZ", "UNPZ", "UNH", "NOV",
|
||||||
|
"NovKuybNPZ", "KuybNPZ", "CyzNPZ", "TuapsNPZ", "RNPK",
|
||||||
|
"NVNPO", "KLNPZ", "PurNP", "YANOS"],
|
||||||
|
key="pm_single_og"
|
||||||
|
)
|
||||||
|
|
||||||
|
codes = st.multiselect(
|
||||||
|
"Выберите коды строк",
|
||||||
|
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
||||||
|
default=[78, 79],
|
||||||
|
key="pm_single_codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
["БП", "ПП", "СЭБ", "Факт", "План"],
|
||||||
|
default=["БП", "ПП"],
|
||||||
|
key="pm_single_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные по ОГ", key="pm_single_btn"):
|
||||||
|
if codes and columns:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"getter": "single_og",
|
||||||
|
"id": og_id,
|
||||||
|
"codes": codes,
|
||||||
|
"columns": columns
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_pm/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите коды и столбцы")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Данные по всем ОГ")
|
||||||
|
|
||||||
|
codes_total = st.multiselect(
|
||||||
|
"Выберите коды строк",
|
||||||
|
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
||||||
|
default=[78, 79, 394, 395],
|
||||||
|
key="pm_total_codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns_total = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
["БП", "ПП", "СЭБ", "Факт", "План"],
|
||||||
|
default=["БП", "ПП", "СЭБ"],
|
||||||
|
key="pm_total_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные по всем ОГ", key="pm_total_btn"):
|
||||||
|
if codes_total and columns_total:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"getter": "total_ogs",
|
||||||
|
"codes": codes_total,
|
||||||
|
"columns": columns_total
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_pm/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите коды и столбцы")
|
||||||
|
|
||||||
|
# Вкладка 2: Сводки СА - полный функционал
|
||||||
|
with tab2:
|
||||||
|
st.header("🏭 Сводки СА - Полный функционал")
|
||||||
|
|
||||||
|
# Получаем информацию о геттерах
|
||||||
|
getters_info = get_parser_getters("svodka_ca")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_ca = st.file_uploader(
|
||||||
|
"Выберите Excel файл сводки СА",
|
||||||
|
type=['xlsx', 'xlsm', 'xls'],
|
||||||
|
key="ca_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_ca is not None:
|
||||||
|
if st.button("📤 Загрузить сводку СА", key="upload_ca_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
try:
|
||||||
|
files = {"file": (uploaded_ca.name, uploaded_ca.read(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
|
||||||
|
response = requests.post(f"{API_BASE_URL}/svodka_ca/upload", files=files)
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
# Показываем доступные геттеры
|
||||||
|
if getters_info and "getters" in getters_info:
|
||||||
|
st.info("📋 Доступные геттеры:")
|
||||||
|
for getter_name, getter_info in getters_info["getters"].items():
|
||||||
|
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||||
|
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||||
|
if getter_info.get('optional_params'):
|
||||||
|
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Параметры запроса")
|
||||||
|
|
||||||
|
modes = st.multiselect(
|
||||||
|
"Выберите режимы",
|
||||||
|
["План", "Факт", "Норматив"],
|
||||||
|
default=["План", "Факт"],
|
||||||
|
key="ca_modes"
|
||||||
|
)
|
||||||
|
|
||||||
|
tables = st.multiselect(
|
||||||
|
"Выберите таблицы",
|
||||||
|
["ТиП", "Топливо", "Потери"],
|
||||||
|
default=["ТиП", "Топливо"],
|
||||||
|
key="ca_tables"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Результат")
|
||||||
|
if st.button("🔍 Получить данные СА", key="ca_btn"):
|
||||||
|
if modes and tables:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"getter": "get_data",
|
||||||
|
"modes": modes,
|
||||||
|
"tables": tables
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_ca/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите режимы и таблицы")
|
||||||
|
|
||||||
|
# Вкладка 3: Мониторинг топлива - полный функционал
|
||||||
|
with tab3:
|
||||||
|
st.header("⛽ Мониторинг топлива - Полный функционал")
|
||||||
|
|
||||||
|
# Получаем информацию о геттерах
|
||||||
|
getters_info = get_parser_getters("monitoring_fuel")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_fuel = st.file_uploader(
|
||||||
|
"Выберите ZIP архив с мониторингом топлива",
|
||||||
|
type=['zip'],
|
||||||
|
key="fuel_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_fuel is not None:
|
||||||
|
if st.button("📤 Загрузить мониторинг топлива", key="upload_fuel_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
result, status = upload_file_to_api(
|
||||||
|
"/monitoring_fuel/upload-zip",
|
||||||
|
uploaded_fuel.read(),
|
||||||
|
uploaded_fuel.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
# Показываем доступные геттеры
|
||||||
|
if getters_info and "getters" in getters_info:
|
||||||
|
st.info("📋 Доступные геттеры:")
|
||||||
|
for getter_name, getter_info in getters_info["getters"].items():
|
||||||
|
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||||
|
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||||
|
if getter_info.get('optional_params'):
|
||||||
|
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Агрегация по колонкам")
|
||||||
|
|
||||||
|
columns_fuel = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
["normativ", "total", "total_1"],
|
||||||
|
default=["normativ", "total"],
|
||||||
|
key="fuel_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить агрегированные данные", key="fuel_total_btn"):
|
||||||
|
if columns_fuel:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"getter": "total_by_columns",
|
||||||
|
"columns": columns_fuel
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/monitoring_fuel/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите столбцы")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Данные за месяц")
|
||||||
|
|
||||||
|
month = st.selectbox(
|
||||||
|
"Выберите месяц",
|
||||||
|
[f"{i:02d}" for i in range(1, 13)],
|
||||||
|
key="fuel_month"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные за месяц", key="fuel_month_btn"):
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"getter": "month_by_code",
|
||||||
|
"month": month
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/monitoring_fuel/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
# Футер
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### 📚 Документация API")
|
||||||
|
st.markdown(f"Полная документация доступна по адресу: {API_BASE_URL}/docs")
|
||||||
|
|
||||||
|
# Информация о проекте
|
||||||
|
with st.expander("ℹ️ О проекте"):
|
||||||
|
st.markdown("""
|
||||||
|
**NIN Excel Parsers API** - это веб-сервис для парсинга и обработки Excel-файлов нефтеперерабатывающих заводов.
|
||||||
|
|
||||||
|
**Возможности:**
|
||||||
|
- 📊 Парсинг сводок ПМ (план и факт)
|
||||||
|
- 🏭 Парсинг сводок СА
|
||||||
|
- ⛽ Мониторинг топлива
|
||||||
|
|
||||||
|
**Технологии:**
|
||||||
|
- FastAPI
|
||||||
|
- Pandas
|
||||||
|
- MinIO (S3-совместимое хранилище)
|
||||||
|
- Streamlit (веб-интерфейс)
|
||||||
|
""")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
4
python_parser/streamlit_app/requirements.txt
Normal file
4
python_parser/streamlit_app/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
streamlit>=1.28.0
|
||||||
|
requests>=2.31.0
|
||||||
|
pandas>=1.5.0
|
||||||
|
numpy>=1.24.0
|
||||||
Reference in New Issue
Block a user