Compare commits
2 Commits
upd_exist_
...
9459196804
| Author | SHA1 | Date | |
|---|---|---|---|
| 9459196804 | |||
| ce228d9756 |
178
.gitignore
vendored
178
.gitignore
vendored
@@ -1,25 +1,13 @@
|
||||
# Python
|
||||
__pycache__
|
||||
data/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
python_parser/__pycache__/
|
||||
python_parser/core/__pycache__/
|
||||
python_parser/adapters/__pycache__/
|
||||
python_parser/tests/__pycache__/
|
||||
python_parser/tests/test_core/__pycache__/
|
||||
python_parser/tests/test_adapters/__pycache__/
|
||||
python_parser/tests/test_app/__pycache__/
|
||||
python_parser/app/__pycache__/
|
||||
python_parser/app/schemas/__pycache__/
|
||||
python_parser/app/schemas/test_schemas/__pycache__/
|
||||
python_parser/app/schemas/test_schemas/test_core/__pycache__/
|
||||
python_parser/app/schemas/test_schemas/test_adapters/__pycache__/
|
||||
python_parser/app/schemas/test_schemas/test_app/__pycache__/
|
||||
|
||||
nin_python_parser
|
||||
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
@@ -32,82 +20,26 @@ lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
log/
|
||||
|
||||
# MinIO data and cache
|
||||
minio_data/
|
||||
.minio.sys/
|
||||
*.meta
|
||||
part.*
|
||||
|
||||
# Docker
|
||||
.dockerignore
|
||||
docker-compose.override.yml
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
*.backup
|
||||
*.orig
|
||||
|
||||
# Data files (Excel, CSV, etc.)
|
||||
*.xlsx
|
||||
*.xls
|
||||
*.xlsm
|
||||
*.csv
|
||||
*.json
|
||||
data/
|
||||
uploads/
|
||||
|
||||
# Cache directories
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
@@ -115,29 +47,6 @@ htmlcov/
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# poetry
|
||||
poetry.lock
|
||||
|
||||
# Celery
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
@@ -146,29 +55,36 @@ dmypy.json
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Local development
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
# Local envs
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# FastAPI
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
# MacOS
|
||||
.DS_Store
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Node.js (if any frontend components)
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# MinIO test data
|
||||
minio_data/
|
||||
minio_test/
|
||||
minio/
|
||||
|
||||
__pycache__/
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Streamlit cache
|
||||
.streamlit/
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# 🚀 Быстрый запуск проекта
|
||||
|
||||
## 1. Запуск всех сервисов
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 2. Проверка статуса
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## 3. Доступ к сервисам
|
||||
- **FastAPI**: http://localhost:8000
|
||||
- **Streamlit**: http://localhost:8501
|
||||
- **MinIO Console**: http://localhost:9001
|
||||
- **MinIO API**: http://localhost:9000
|
||||
|
||||
## 4. Остановка
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## 5. Просмотр логов
|
||||
```bash
|
||||
# Все сервисы
|
||||
docker compose logs
|
||||
|
||||
# Конкретный сервис
|
||||
docker compose logs fastapi
|
||||
docker compose logs streamlit
|
||||
docker compose logs minio
|
||||
```
|
||||
|
||||
## 6. Пересборка и перезапуск
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
---
|
||||
**Примечание**: При первом запуске Docker будет скачивать образы и собирать контейнеры, это может занять несколько минут.
|
||||
117
README.md
117
README.md
@@ -1,117 +0,0 @@
|
||||
# Python Parser CF - Система анализа данных
|
||||
|
||||
Проект состоит из трех основных компонентов:
|
||||
- **python_parser** - FastAPI приложение для парсинга и обработки данных
|
||||
- **streamlit_app** - Streamlit приложение для визуализации и анализа
|
||||
- **minio_data** - хранилище данных MinIO
|
||||
|
||||
## 🚀 Быстрый запуск
|
||||
|
||||
### Предварительные требования
|
||||
- Docker и Docker Compose
|
||||
- Git
|
||||
|
||||
### Запуск всех сервисов (продакшн)
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Запуск в режиме разработки
|
||||
```bash
|
||||
# Автоматический запуск
|
||||
python start_dev.py
|
||||
|
||||
# Или вручную
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
**Режим разработки** позволяет:
|
||||
- Автоматически перезагружать Streamlit при изменении кода
|
||||
- Монтировать исходный код напрямую в контейнер
|
||||
- Видеть изменения без пересборки контейнеров
|
||||
|
||||
### Доступ к сервисам
|
||||
- **FastAPI**: http://localhost:8000
|
||||
- **Streamlit**: http://localhost:8501
|
||||
- **MinIO Console**: http://localhost:9001
|
||||
- **MinIO API**: http://localhost:9000
|
||||
|
||||
### Остановка сервисов
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## 📁 Структура проекта
|
||||
|
||||
```
|
||||
python_parser_cf/
|
||||
├── python_parser/ # FastAPI приложение
|
||||
│ ├── app/ # Основной код приложения
|
||||
│ ├── adapters/ # Адаптеры для парсеров
|
||||
│ ├── core/ # Основная бизнес-логика
|
||||
│ ├── data/ # Тестовые данные
|
||||
│ └── Dockerfile # Docker образ для FastAPI
|
||||
├── streamlit_app/ # Streamlit приложение
|
||||
│ ├── streamlit_app.py # Основной файл приложения
|
||||
│ ├── requirements.txt # Зависимости Python
|
||||
│ ├── .streamlit/ # Конфигурация Streamlit
|
||||
│ └── Dockerfile # Docker образ для Streamlit
|
||||
├── minio_data/ # Данные для MinIO
|
||||
├── docker-compose.yml # Конфигурация всех сервисов
|
||||
└── README.md # Документация
|
||||
```
|
||||
|
||||
## 🔧 Конфигурация
|
||||
|
||||
### Переменные окружения
|
||||
Все сервисы используют следующие переменные окружения:
|
||||
- `MINIO_ENDPOINT` - адрес MinIO сервера
|
||||
- `MINIO_ACCESS_KEY` - ключ доступа к MinIO
|
||||
- `MINIO_SECRET_KEY` - секретный ключ MinIO
|
||||
- `MINIO_SECURE` - использование SSL/TLS
|
||||
- `MINIO_BUCKET` - имя bucket'а для данных
|
||||
|
||||
### Порты
|
||||
- **8000** - FastAPI
|
||||
- **8501** - Streamlit
|
||||
- **9000** - MinIO API
|
||||
- **9001** - MinIO Console
|
||||
|
||||
## 📊 Использование
|
||||
|
||||
1. **Запустите все сервисы**: `docker-compose up -d`
|
||||
2. **Откройте Streamlit**: http://localhost:8501
|
||||
3. **Выберите тип данных** для анализа
|
||||
4. **Просматривайте результаты** в интерактивном интерфейсе
|
||||
|
||||
## 🛠️ Разработка
|
||||
|
||||
### Режим разработки (рекомендуется)
|
||||
```bash
|
||||
# Запуск режима разработки
|
||||
python start_dev.py
|
||||
|
||||
# Остановка
|
||||
docker compose -f docker-compose.dev.yml down
|
||||
|
||||
# Возврат к продакшн режиму
|
||||
python start_prod.py
|
||||
```
|
||||
|
||||
### Локальная разработка FastAPI
|
||||
```bash
|
||||
cd python_parser
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
### Локальная разработка Streamlit
|
||||
```bash
|
||||
cd streamlit_app
|
||||
pip install -r requirements.txt
|
||||
streamlit run streamlit_app.py
|
||||
```
|
||||
|
||||
## 📝 Лицензия
|
||||
|
||||
Проект разработан для внутреннего использования.
|
||||
@@ -1,58 +0,0 @@
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: svodka_minio_dev
|
||||
ports:
|
||||
- "9000:9000" # API порт
|
||||
- "9001:9001" # Консоль порт
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- ./minio_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
fastapi:
|
||||
build: ./python_parser
|
||||
container_name: svodka_fastapi_dev
|
||||
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
|
||||
|
||||
streamlit:
|
||||
image: python:3.11-slim
|
||||
container_name: svodka_streamlit_dev
|
||||
ports:
|
||||
- "8501:8501"
|
||||
environment:
|
||||
- API_BASE_URL=http://fastapi:8000
|
||||
- API_PUBLIC_URL=http://localhost:8000
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minioadmin
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
- MINIO_SECURE=false
|
||||
- MINIO_BUCKET=svodka-data
|
||||
volumes:
|
||||
# Монтируем исходный код для автоматической перезагрузки
|
||||
- ./streamlit_app:/app
|
||||
# Монтируем requirements.txt для установки зависимостей
|
||||
- ./streamlit_app/requirements.txt:/app/requirements.txt
|
||||
working_dir: /app
|
||||
depends_on:
|
||||
- minio
|
||||
- fastapi
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
bash -c "
|
||||
pip install --no-cache-dir -r requirements.txt &&
|
||||
streamlit run streamlit_app.py --server.port=8501 --server.address=0.0.0.0 --server.runOnSave=true
|
||||
"
|
||||
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
|
||||
20
python_parser/Dockerfile_
Normal file
20
python_parser/Dockerfile_
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM repo-dev.predix.rosneft.ru/python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# RUN pip install kafka-python==2.0.2
|
||||
# RUN pip freeze > /app/requirements.txt
|
||||
|
||||
# ADD . /app
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN mkdir -p vendor
|
||||
RUN pip download -r /app/requirements.txt --no-binary=:none: -d /app/vendor
|
||||
|
||||
# ADD . /app
|
||||
|
||||
# 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
|
||||
|
||||
# CMD ["python", "/app/run_dev.py"]
|
||||
1
python_parser/Procfile
Normal file
1
python_parser/Procfile
Normal file
@@ -0,0 +1 @@
|
||||
web: python /app/run_stand.py
|
||||
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
|
||||
```
|
||||
143
python_parser/README.md
Normal file
143
python_parser/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# NIN Excel Parsers API
|
||||
|
||||
API для парсинга Excel отчетов нефтеперерабатывающих заводов (НПЗ) с использованием FastAPI и MinIO для хранения данных.
|
||||
|
||||
## 🚀 Быстрый запуск
|
||||
|
||||
### **Вариант 1: Все сервисы в Docker (рекомендуется)**
|
||||
```bash
|
||||
# Запуск всех сервисов: MinIO + FastAPI + Streamlit
|
||||
docker-compose up -d
|
||||
|
||||
# Доступ:
|
||||
# - MinIO Console: http://localhost:9001
|
||||
# - FastAPI: http://localhost:8000
|
||||
# - Streamlit: http://localhost:8501
|
||||
# - API Docs: http://localhost:8000/docs
|
||||
```
|
||||
|
||||
### **Вариант 2: Только MinIO в Docker + FastAPI локально**
|
||||
```bash
|
||||
# Запуск MinIO в Docker
|
||||
docker-compose up -d minio
|
||||
|
||||
# Запуск FastAPI локально
|
||||
python run_dev.py
|
||||
|
||||
# В отдельном терминале запуск Streamlit
|
||||
cd streamlit_app
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
### **Вариант 3: Только MinIO в Docker**
|
||||
```bash
|
||||
# Запуск только MinIO
|
||||
docker-compose up -d minio
|
||||
```
|
||||
|
||||
## 📋 Описание сервисов
|
||||
|
||||
- **MinIO** (порт 9000-9001): S3-совместимое хранилище для данных
|
||||
- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов
|
||||
- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API
|
||||
|
||||
## 🛑 Остановка
|
||||
|
||||
### Остановка Docker сервисов:
|
||||
```bash
|
||||
# Все сервисы
|
||||
docker-compose down
|
||||
|
||||
# Только MinIO
|
||||
docker-compose stop minio
|
||||
```
|
||||
|
||||
### Остановка локальных сервисов:
|
||||
```bash
|
||||
# Нажмите Ctrl+C в терминале с FastAPI/Streamlit
|
||||
```
|
||||
|
||||
## 📁 Структура проекта
|
||||
|
||||
```
|
||||
python_parser/
|
||||
├── app/ # FastAPI приложение
|
||||
│ ├── main.py # Основной файл приложения
|
||||
│ └── schemas/ # Pydantic схемы
|
||||
├── core/ # Бизнес-логика
|
||||
│ ├── models.py # Модели данных
|
||||
│ ├── ports.py # Интерфейсы (порты)
|
||||
│ └── services.py # Сервисы
|
||||
├── adapters/ # Адаптеры для внешних систем
|
||||
│ ├── storage.py # MinIO адаптер
|
||||
│ └── parsers/ # Парсеры Excel файлов
|
||||
├── streamlit_app/ # Изолированный Streamlit пакет
|
||||
│ ├── app.py # Основное Streamlit приложение
|
||||
│ ├── requirements.txt # Зависимости Streamlit
|
||||
│ ├── Dockerfile # Docker образ для Streamlit
|
||||
│ └── .streamlit/ # Конфигурация Streamlit
|
||||
├── data/ # Тестовые данные
|
||||
├── docker-compose.yml # Docker Compose конфигурация
|
||||
├── Dockerfile # Docker образ для FastAPI
|
||||
└── run_dev.py # Запуск FastAPI локально
|
||||
```
|
||||
|
||||
## 🔍 Доступные эндпоинты
|
||||
|
||||
- **GET /** - Информация об API
|
||||
- **GET /docs** - Swagger документация
|
||||
- **GET /parsers** - Список доступных парсеров
|
||||
- **GET /parsers/{parser_name}/getters** - Информация о геттерах парсера
|
||||
- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ
|
||||
- **POST /svodka_ca/upload** - Загрузка сводок ЦА
|
||||
- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива
|
||||
- **POST /svodka_pm/get_data** - Получение данных сводок ПМ
|
||||
- **POST /svodka_ca/get_data** - Получение данных сводок ЦА
|
||||
- **POST /monitoring_fuel/get_data** - Получение данных мониторинга топлива
|
||||
|
||||
## 📊 Поддерживаемые типы отчетов
|
||||
|
||||
1. **svodka_pm** - Сводки по переработке нефти (ПМ)
|
||||
- Геттеры: `single_og`, `total_ogs`
|
||||
2. **svodka_ca** - Сводки по переработке нефти (ЦА)
|
||||
- Геттеры: `get_data`
|
||||
3. **monitoring_fuel** - Мониторинг топлива
|
||||
- Геттеры: `total_by_columns`, `month_by_code`
|
||||
|
||||
## 🏗️ Архитектура
|
||||
|
||||
Проект использует **Hexagonal Architecture (Ports and Adapters)**:
|
||||
|
||||
- **Порты (Ports)**: Интерфейсы для бизнес-логики
|
||||
- **Адаптеры (Adapters)**: Реализации для внешних систем
|
||||
- **Сервисы (Services)**: Бизнес-логика приложения
|
||||
|
||||
### Система геттеров парсеров
|
||||
|
||||
Каждый парсер может иметь несколько методов получения данных (геттеров):
|
||||
- Регистрация геттеров в словаре с метаданными
|
||||
- Валидация параметров для каждого геттера
|
||||
- Единый интерфейс `get_value(getter_name, params)`
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
### Сборка образов:
|
||||
```bash
|
||||
# FastAPI
|
||||
docker build -t nin-fastapi .
|
||||
|
||||
# Streamlit
|
||||
docker build -t nin-streamlit ./streamlit_app
|
||||
```
|
||||
|
||||
### Запуск отдельных сервисов:
|
||||
```bash
|
||||
# Только MinIO
|
||||
docker-compose up -d minio
|
||||
|
||||
# MinIO + FastAPI
|
||||
docker-compose up -d minio fastapi
|
||||
|
||||
# Все сервисы
|
||||
docker-compose up -d
|
||||
```
|
||||
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
|
||||
@@ -1,135 +0,0 @@
|
||||
# Интеграция схем Pydantic с парсерами
|
||||
|
||||
## Обзор
|
||||
|
||||
Этот документ описывает решение для устранения дублирования логики между схемами Pydantic и парсерами. Теперь схемы Pydantic являются единым источником правды для определения параметров парсеров.
|
||||
|
||||
## Проблема
|
||||
|
||||
Ранее в парсерах дублировалась информация о параметрах:
|
||||
|
||||
```python
|
||||
# В парсере
|
||||
self.register_getter(
|
||||
name="single_og",
|
||||
method=self._get_single_og,
|
||||
required_params=["id", "codes", "columns"], # Дублирование
|
||||
optional_params=["search"], # Дублирование
|
||||
description="Получение данных по одному ОГ"
|
||||
)
|
||||
|
||||
# В схеме
|
||||
class SvodkaPMSingleOGRequest(BaseModel):
|
||||
id: OGID = Field(...) # Обязательное поле
|
||||
codes: List[int] = Field(...) # Обязательное поле
|
||||
columns: List[str] = Field(...) # Обязательное поле
|
||||
search: Optional[str] = Field(None) # Необязательное поле
|
||||
```
|
||||
|
||||
## Решение
|
||||
|
||||
### 1. Утилиты для работы со схемами
|
||||
|
||||
Создан модуль `core/schema_utils.py` с функциями:
|
||||
|
||||
- `get_required_fields_from_schema()` - извлекает обязательные поля
|
||||
- `get_optional_fields_from_schema()` - извлекает необязательные поля
|
||||
- `register_getter_from_schema()` - регистрирует геттер с использованием схемы
|
||||
- `validate_params_with_schema()` - валидирует параметры с помощью схемы
|
||||
|
||||
### 2. Обновленные парсеры
|
||||
|
||||
Теперь парсеры используют схемы как единый источник правды:
|
||||
|
||||
```python
|
||||
def _register_default_getters(self):
|
||||
"""Регистрация геттеров по умолчанию"""
|
||||
# Используем схемы Pydantic как единый источник правды
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="single_og",
|
||||
method=self._get_single_og,
|
||||
schema_class=SvodkaPMSingleOGRequest,
|
||||
description="Получение данных по одному ОГ"
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Валидация параметров
|
||||
|
||||
Методы геттеров теперь автоматически валидируют параметры:
|
||||
|
||||
```python
|
||||
def _get_single_og(self, params: dict):
|
||||
"""Получение данных по одному ОГ"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaPMSingleOGRequest)
|
||||
|
||||
og_id = validated_params["id"]
|
||||
codes = validated_params["codes"]
|
||||
columns = validated_params["columns"]
|
||||
search = validated_params.get("search")
|
||||
|
||||
# ... остальная логика
|
||||
```
|
||||
|
||||
## Преимущества
|
||||
|
||||
1. **Единый источник правды** - информация о параметрах хранится только в схемах Pydantic
|
||||
2. **Автоматическая валидация** - параметры автоматически валидируются с помощью Pydantic
|
||||
3. **Синхронизация** - изменения в схемах автоматически отражаются в парсерах
|
||||
4. **Типобезопасность** - использование типов Pydantic обеспечивает типобезопасность
|
||||
5. **Документация** - Swagger документация автоматически генерируется из схем
|
||||
|
||||
## Совместимость
|
||||
|
||||
Решение работает с:
|
||||
- Pydantic v1 (через `__fields__`)
|
||||
- Pydantic v2 (через `model_fields` и `is_required()`)
|
||||
|
||||
## Использование
|
||||
|
||||
### Для новых парсеров
|
||||
|
||||
1. Создайте схему Pydantic с нужными полями
|
||||
2. Используйте `register_getter_from_schema()` для регистрации геттера
|
||||
3. Используйте `validate_params_with_schema()` в методах геттеров
|
||||
|
||||
### Для существующих парсеров
|
||||
|
||||
1. Убедитесь, что у вас есть соответствующая схема Pydantic
|
||||
2. Замените ручную регистрацию геттеров на `register_getter_from_schema()`
|
||||
3. Добавьте валидацию параметров в методы геттеров
|
||||
|
||||
## Примеры
|
||||
|
||||
### Схема с обязательными и необязательными полями
|
||||
|
||||
```python
|
||||
class ExampleRequest(BaseModel):
|
||||
required_field: str = Field(..., description="Обязательное поле")
|
||||
optional_field: Optional[str] = Field(None, description="Необязательное поле")
|
||||
```
|
||||
|
||||
### Регистрация геттера
|
||||
|
||||
```python
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="example_getter",
|
||||
method=self._example_method,
|
||||
schema_class=ExampleRequest,
|
||||
description="Пример геттера"
|
||||
)
|
||||
```
|
||||
|
||||
### Валидация в методе
|
||||
|
||||
```python
|
||||
def _example_method(self, params: dict):
|
||||
validated_params = validate_params_with_schema(params, ExampleRequest)
|
||||
# validated_params содержит валидированные данные
|
||||
```
|
||||
|
||||
## Заключение
|
||||
|
||||
Это решение устраняет дублирование кода и обеспечивает единообразие между API схемами и парсерами. Теперь изменения в схемах автоматически отражаются в парсерах, что упрощает поддержку и развитие системы.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,8 +3,6 @@ import re
|
||||
import zipfile
|
||||
from typing import Dict, Tuple
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.monitoring_fuel import MonitoringFuelTotalRequest, MonitoringFuelMonthRequest
|
||||
from adapters.pconfig import data_to_json
|
||||
|
||||
|
||||
@@ -15,40 +13,37 @@ class MonitoringFuelParser(ParserPort):
|
||||
|
||||
def _register_default_getters(self):
|
||||
"""Регистрация геттеров по умолчанию"""
|
||||
# Используем схемы Pydantic как единый источник правды
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="total_by_columns",
|
||||
self.register_getter(
|
||||
name="total_by_columns",
|
||||
method=self._get_total_by_columns,
|
||||
schema_class=MonitoringFuelTotalRequest,
|
||||
required_params=["columns"],
|
||||
optional_params=[],
|
||||
description="Агрегация данных по колонкам"
|
||||
)
|
||||
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="month_by_code",
|
||||
self.register_getter(
|
||||
name="month_by_code",
|
||||
method=self._get_month_by_code,
|
||||
schema_class=MonitoringFuelMonthRequest,
|
||||
required_params=["month"],
|
||||
optional_params=[],
|
||||
description="Получение данных за конкретный месяц"
|
||||
)
|
||||
|
||||
def _get_total_by_columns(self, params: dict):
|
||||
"""Агрегация данных по колонкам"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, MonitoringFuelTotalRequest)
|
||||
|
||||
columns = validated_params["columns"]
|
||||
"""Агрегация по колонкам (обертка для совместимости)"""
|
||||
columns = params["columns"]
|
||||
if not columns:
|
||||
raise ValueError("Отсутствуют идентификаторы столбцов")
|
||||
|
||||
# TODO: Переделать под новую архитектуру
|
||||
df_means, _ = self.aggregate_by_columns(self.df, columns)
|
||||
return df_means.to_dict(orient='index')
|
||||
|
||||
def _get_month_by_code(self, params: dict):
|
||||
"""Получение данных за конкретный месяц"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, MonitoringFuelMonthRequest)
|
||||
|
||||
month = validated_params["month"]
|
||||
"""Получение данных за месяц (обертка для совместимости)"""
|
||||
month = params["month"]
|
||||
if not month:
|
||||
raise ValueError("Отсутствует идентификатор месяца")
|
||||
|
||||
# TODO: Переделать под новую архитектуру
|
||||
df_month = self.get_month(self.df, month)
|
||||
@@ -99,8 +94,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
file_path,
|
||||
sheet_name=sheet,
|
||||
header=None,
|
||||
nrows=max_rows,
|
||||
engine='openpyxl'
|
||||
nrows=max_rows
|
||||
)
|
||||
|
||||
# Ищем строку, где хотя бы в одном столбце встречается искомое значение
|
||||
@@ -122,8 +116,7 @@ class MonitoringFuelParser(ParserPort):
|
||||
sheet_name=sheet,
|
||||
header=header_num,
|
||||
usecols=None,
|
||||
index_col=None,
|
||||
engine='openpyxl'
|
||||
index_col=None
|
||||
)
|
||||
|
||||
# === Удаление полностью пустых столбцов ===
|
||||
|
||||
@@ -2,8 +2,6 @@ import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.svodka_ca import SvodkaCARequest
|
||||
from adapters.pconfig import get_og_by_name
|
||||
|
||||
|
||||
@@ -14,22 +12,23 @@ class SvodkaCAParser(ParserPort):
|
||||
|
||||
def _register_default_getters(self):
|
||||
"""Регистрация геттеров по умолчанию"""
|
||||
# Используем схемы Pydantic как единый источник правды
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="get_data",
|
||||
self.register_getter(
|
||||
name="get_data",
|
||||
method=self._get_data_wrapper,
|
||||
schema_class=SvodkaCARequest,
|
||||
required_params=["modes", "tables"],
|
||||
optional_params=[],
|
||||
description="Получение данных по режимам и таблицам"
|
||||
)
|
||||
|
||||
def _get_data_wrapper(self, params: dict):
|
||||
"""Получение данных по режимам и таблицам"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaCARequest)
|
||||
"""Обертка для получения данных (для совместимости)"""
|
||||
modes = params["modes"]
|
||||
tables = params["tables"]
|
||||
|
||||
modes = validated_params["modes"]
|
||||
tables = validated_params["tables"]
|
||||
if not isinstance(modes, list):
|
||||
raise ValueError("Поле 'modes' должно быть списком")
|
||||
if not isinstance(tables, list):
|
||||
raise ValueError("Поле 'tables' должно быть списком")
|
||||
|
||||
# TODO: Переделать под новую архитектуру
|
||||
data_dict = {}
|
||||
@@ -45,10 +44,6 @@ class SvodkaCAParser(ParserPort):
|
||||
|
||||
def parse_svodka_ca(self, file_path: str, params: dict) -> dict:
|
||||
"""Парсинг сводки СА"""
|
||||
# Получаем параметры из params
|
||||
sheet_name = params.get('sheet_name', 0) # По умолчанию первый лист
|
||||
inclusion_list = params.get('inclusion_list', {'ТиП', 'Топливо', 'Потери'})
|
||||
|
||||
# === Извлечение и фильтрация ===
|
||||
tables = self.extract_all_tables(file_path, sheet_name)
|
||||
|
||||
@@ -155,8 +150,8 @@ class SvodkaCAParser(ParserPort):
|
||||
return None
|
||||
|
||||
def extract_all_tables(self, file_path, sheet_name=0):
|
||||
"""Извлечение всех таблиц из Excel файла"""
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, engine='openpyxl')
|
||||
"""Извлекает все таблицы из Excel файла"""
|
||||
df = pd.read_excel(file_path, sheet_name=sheet_name, header=None)
|
||||
df_filled = df.fillna('')
|
||||
df_clean = df_filled.astype(str).replace(r'^\s*$', '', regex=True)
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import pandas as pd
|
||||
|
||||
from core.ports import ParserPort
|
||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||
from app.schemas.svodka_pm import SvodkaPMSingleOGRequest, SvodkaPMTotalOGsRequest
|
||||
from adapters.pconfig import OG_IDS, replace_id_in_path, data_to_json
|
||||
|
||||
|
||||
@@ -13,45 +11,48 @@ class SvodkaPMParser(ParserPort):
|
||||
|
||||
def _register_default_getters(self):
|
||||
"""Регистрация геттеров по умолчанию"""
|
||||
# Используем схемы Pydantic как единый источник правды
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="single_og",
|
||||
self.register_getter(
|
||||
name="single_og",
|
||||
method=self._get_single_og,
|
||||
schema_class=SvodkaPMSingleOGRequest,
|
||||
required_params=["id", "codes", "columns"],
|
||||
optional_params=["search"],
|
||||
description="Получение данных по одному ОГ"
|
||||
)
|
||||
|
||||
register_getter_from_schema(
|
||||
parser_instance=self,
|
||||
getter_name="total_ogs",
|
||||
self.register_getter(
|
||||
name="total_ogs",
|
||||
method=self._get_total_ogs,
|
||||
schema_class=SvodkaPMTotalOGsRequest,
|
||||
required_params=["codes", "columns"],
|
||||
optional_params=["search"],
|
||||
description="Получение данных по всем ОГ"
|
||||
)
|
||||
|
||||
def _get_single_og(self, params: dict):
|
||||
"""Получение данных по одному ОГ"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaPMSingleOGRequest)
|
||||
"""Получение данных по одному ОГ (обертка для совместимости)"""
|
||||
og_id = params["id"]
|
||||
codes = params["codes"]
|
||||
columns = params["columns"]
|
||||
search = params.get("search")
|
||||
|
||||
og_id = validated_params["id"]
|
||||
codes = validated_params["codes"]
|
||||
columns = validated_params["columns"]
|
||||
search = validated_params.get("search")
|
||||
if not isinstance(codes, list):
|
||||
raise ValueError("Поле 'codes' должно быть списком")
|
||||
if not isinstance(columns, list):
|
||||
raise ValueError("Поле 'columns' должно быть списком")
|
||||
|
||||
# Здесь нужно получить DataFrame из self.df, но пока используем старую логику
|
||||
# TODO: Переделать под новую архитектуру
|
||||
return self.get_svodka_og(self.df, og_id, codes, columns, search)
|
||||
|
||||
def _get_total_ogs(self, params: dict):
|
||||
"""Получение данных по всем ОГ"""
|
||||
# Валидируем параметры с помощью схемы Pydantic
|
||||
validated_params = validate_params_with_schema(params, SvodkaPMTotalOGsRequest)
|
||||
"""Получение данных по всем ОГ (обертка для совместимости)"""
|
||||
codes = params["codes"]
|
||||
columns = params["columns"]
|
||||
search = params.get("search")
|
||||
|
||||
codes = validated_params["codes"]
|
||||
columns = validated_params["columns"]
|
||||
search = validated_params.get("search")
|
||||
if not isinstance(codes, list):
|
||||
raise ValueError("Поле 'codes' должно быть списком")
|
||||
if not isinstance(columns, list):
|
||||
raise ValueError("Поле 'columns' должно быть списком")
|
||||
|
||||
# TODO: Переделать под новую архитектуру
|
||||
return self.get_svodka_total(self.df, codes, columns, search)
|
||||
@@ -69,8 +70,7 @@ class SvodkaPMParser(ParserPort):
|
||||
file,
|
||||
sheet_name=sheet,
|
||||
header=None,
|
||||
nrows=max_rows,
|
||||
engine='openpyxl'
|
||||
nrows=max_rows
|
||||
)
|
||||
|
||||
# Ищем строку, где хотя бы в одном столбце встречается искомое значение
|
||||
@@ -94,7 +94,6 @@ class SvodkaPMParser(ParserPort):
|
||||
header=header_num,
|
||||
usecols=None,
|
||||
nrows=2,
|
||||
engine='openpyxl'
|
||||
)
|
||||
|
||||
if df_probe.shape[0] == 0:
|
||||
@@ -116,8 +115,7 @@ class SvodkaPMParser(ParserPort):
|
||||
sheet_name=sheet,
|
||||
header=header_num,
|
||||
usecols=None,
|
||||
index_col=None,
|
||||
engine='openpyxl'
|
||||
index_col=None
|
||||
)
|
||||
|
||||
if indicator_col_name not in df_full.columns:
|
||||
|
||||
@@ -400,40 +400,40 @@ async def get_svodka_pm_total_ogs(
|
||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
@app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
|
||||
async def get_svodka_pm_data(
|
||||
request_data: dict
|
||||
):
|
||||
report_service = get_report_service()
|
||||
"""
|
||||
Получение данных из отчета сводки факта СарНПЗ
|
||||
# @app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
|
||||
# async def get_svodka_pm_data(
|
||||
# request_data: dict
|
||||
# ):
|
||||
# report_service = get_report_service()
|
||||
# """
|
||||
# Получение данных из отчета сводки факта СарНПЗ
|
||||
|
||||
- indicator_id: ID индикатора
|
||||
- code: Код для поиска
|
||||
- search_value: Опциональное значение для поиска
|
||||
"""
|
||||
try:
|
||||
# Создаем запрос
|
||||
request = DataRequest(
|
||||
report_type='svodka_pm',
|
||||
get_params=request_data
|
||||
)
|
||||
# - indicator_id: ID индикатора
|
||||
# - code: Код для поиска
|
||||
# - search_value: Опциональное значение для поиска
|
||||
# """
|
||||
# try:
|
||||
# # Создаем запрос
|
||||
# request = DataRequest(
|
||||
# report_type='svodka_pm',
|
||||
# get_params=request_data
|
||||
# )
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
# # Получаем данные
|
||||
# result = report_service.get_data(request)
|
||||
|
||||
if result.success:
|
||||
return {
|
||||
"success": True,
|
||||
"data": result.data
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=result.message)
|
||||
# if result.success:
|
||||
# return {
|
||||
# "success": True,
|
||||
# "data": result.data
|
||||
# }
|
||||
# else:
|
||||
# raise HTTPException(status_code=404, detail=result.message)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
# except HTTPException:
|
||||
# raise
|
||||
# except Exception as e:
|
||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
@app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
||||
@@ -610,38 +610,38 @@ async def get_svodka_ca_data(
|
||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
@app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name])
|
||||
async def get_monitoring_fuel_data(
|
||||
request_data: dict
|
||||
):
|
||||
report_service = get_report_service()
|
||||
"""
|
||||
Получение данных из отчета мониторинга топлива
|
||||
# @app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name])
|
||||
# async def get_monitoring_fuel_data(
|
||||
# request_data: dict
|
||||
# ):
|
||||
# report_service = get_report_service()
|
||||
# """
|
||||
# Получение данных из отчета мониторинга топлива
|
||||
|
||||
- column: Название колонки для агрегации (normativ, total, total_svod)
|
||||
"""
|
||||
try:
|
||||
# Создаем запрос
|
||||
request = DataRequest(
|
||||
report_type='monitoring_fuel',
|
||||
get_params=request_data
|
||||
)
|
||||
# - column: Название колонки для агрегации (normativ, total, total_svod)
|
||||
# """
|
||||
# try:
|
||||
# # Создаем запрос
|
||||
# request = DataRequest(
|
||||
# report_type='monitoring_fuel',
|
||||
# get_params=request_data
|
||||
# )
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
# # Получаем данные
|
||||
# result = report_service.get_data(request)
|
||||
|
||||
if result.success:
|
||||
return {
|
||||
"success": True,
|
||||
"data": result.data
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=result.message)
|
||||
# if result.success:
|
||||
# return {
|
||||
# "success": True,
|
||||
# "data": result.data
|
||||
# }
|
||||
# else:
|
||||
# raise HTTPException(status_code=404, detail=result.message)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
# except HTTPException:
|
||||
# raise
|
||||
# except Exception as e:
|
||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])
|
||||
|
||||
Binary file not shown.
@@ -1,140 +0,0 @@
|
||||
"""
|
||||
Упрощенные утилиты для работы со схемами Pydantic
|
||||
"""
|
||||
from typing import List, Dict, Any, Type
|
||||
from pydantic import BaseModel
|
||||
import inspect
|
||||
|
||||
|
||||
def get_required_fields_from_schema(schema_class: Type[BaseModel]) -> List[str]:
|
||||
"""
|
||||
Извлекает список обязательных полей из схемы Pydantic
|
||||
|
||||
Args:
|
||||
schema_class: Класс схемы Pydantic
|
||||
|
||||
Returns:
|
||||
Список имен обязательных полей
|
||||
"""
|
||||
required_fields = []
|
||||
|
||||
# Используем model_fields для Pydantic v2 или __fields__ для v1
|
||||
if hasattr(schema_class, 'model_fields'):
|
||||
fields = schema_class.model_fields
|
||||
else:
|
||||
fields = schema_class.__fields__
|
||||
|
||||
for field_name, field_info in fields.items():
|
||||
# В Pydantic v2 есть метод is_required()
|
||||
if hasattr(field_info, 'is_required'):
|
||||
if field_info.is_required():
|
||||
required_fields.append(field_name)
|
||||
elif hasattr(field_info, 'required'):
|
||||
if field_info.required:
|
||||
required_fields.append(field_name)
|
||||
else:
|
||||
# Fallback для старых версий - проверяем наличие default
|
||||
has_default = False
|
||||
|
||||
if hasattr(field_info, 'default'):
|
||||
has_default = field_info.default is not ...
|
||||
elif hasattr(field_info, 'default_factory'):
|
||||
has_default = field_info.default_factory is not None
|
||||
|
||||
if not has_default:
|
||||
required_fields.append(field_name)
|
||||
|
||||
return required_fields
|
||||
|
||||
|
||||
def get_optional_fields_from_schema(schema_class: Type[BaseModel]) -> List[str]:
|
||||
"""
|
||||
Извлекает список необязательных полей из схемы Pydantic
|
||||
|
||||
Args:
|
||||
schema_class: Класс схемы Pydantic
|
||||
|
||||
Returns:
|
||||
Список имен необязательных полей
|
||||
"""
|
||||
optional_fields = []
|
||||
|
||||
# Используем model_fields для Pydantic v2 или __fields__ для v1
|
||||
if hasattr(schema_class, 'model_fields'):
|
||||
fields = schema_class.model_fields
|
||||
else:
|
||||
fields = schema_class.__fields__
|
||||
|
||||
for field_name, field_info in fields.items():
|
||||
# В Pydantic v2 есть метод is_required()
|
||||
if hasattr(field_info, 'is_required'):
|
||||
if not field_info.is_required():
|
||||
optional_fields.append(field_name)
|
||||
elif hasattr(field_info, 'required'):
|
||||
if not field_info.required:
|
||||
optional_fields.append(field_name)
|
||||
else:
|
||||
# Fallback для старых версий - проверяем наличие default
|
||||
has_default = False
|
||||
|
||||
if hasattr(field_info, 'default'):
|
||||
has_default = field_info.default is not ...
|
||||
elif hasattr(field_info, 'default_factory'):
|
||||
has_default = field_info.default_factory is not None
|
||||
|
||||
if has_default:
|
||||
optional_fields.append(field_name)
|
||||
|
||||
return optional_fields
|
||||
|
||||
|
||||
def register_getter_from_schema(parser_instance, getter_name: str, method: callable,
|
||||
schema_class: Type[BaseModel], description: str = ""):
|
||||
"""
|
||||
Регистрирует геттер в парсере, используя схему Pydantic для определения параметров
|
||||
|
||||
Args:
|
||||
parser_instance: Экземпляр парсера
|
||||
getter_name: Имя геттера
|
||||
method: Метод для выполнения
|
||||
schema_class: Класс схемы Pydantic
|
||||
description: Описание геттера (если не указано, берется из docstring метода)
|
||||
"""
|
||||
# Извлекаем параметры из схемы
|
||||
required_params = get_required_fields_from_schema(schema_class)
|
||||
optional_params = get_optional_fields_from_schema(schema_class)
|
||||
|
||||
# Если описание не указано, берем из docstring метода
|
||||
if not description:
|
||||
description = inspect.getdoc(method) or ""
|
||||
|
||||
# Регистрируем геттер
|
||||
parser_instance.register_getter(
|
||||
name=getter_name,
|
||||
method=method,
|
||||
required_params=required_params,
|
||||
optional_params=optional_params,
|
||||
description=description
|
||||
)
|
||||
|
||||
|
||||
def validate_params_with_schema(params: Dict[str, Any], schema_class: Type[BaseModel]) -> Dict[str, Any]:
|
||||
"""
|
||||
Валидирует параметры с помощью схемы Pydantic
|
||||
|
||||
Args:
|
||||
params: Словарь параметров
|
||||
schema_class: Класс схемы Pydantic
|
||||
|
||||
Returns:
|
||||
Валидированные параметры
|
||||
|
||||
Raises:
|
||||
ValidationError: Если параметры не прошли валидацию
|
||||
"""
|
||||
try:
|
||||
# Создаем экземпляр схемы для валидации
|
||||
validated_data = schema_class(**params)
|
||||
return validated_data.dict()
|
||||
except Exception as e:
|
||||
raise ValueError(f"Ошибка валидации параметров: {str(e)}")
|
||||
@@ -1,5 +1,3 @@
|
||||
# Продакшн конфигурация
|
||||
# Для разработки используйте: docker compose -f docker-compose.dev.yml up -d
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
@@ -12,11 +10,11 @@ services:
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- ./minio_data:/data
|
||||
- minio_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
fastapi:
|
||||
build: ./python_parser
|
||||
build: .
|
||||
container_name: svodka_fastapi
|
||||
ports:
|
||||
- "8000:8000"
|
||||
@@ -37,13 +35,9 @@ services:
|
||||
- "8501:8501"
|
||||
environment:
|
||||
- API_BASE_URL=http://fastapi:8000
|
||||
- API_PUBLIC_URL=http://localhost:8000
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_ACCESS_KEY=minioadmin
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
- MINIO_SECURE=false
|
||||
- MINIO_BUCKET=svodka-data
|
||||
depends_on:
|
||||
- minio
|
||||
- fastapi
|
||||
restart: unless-stopped
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
minio_data:
|
||||
17
python_parser/manifest.yml
Normal file
17
python_parser/manifest.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
applications:
|
||||
- name: nin-python-parser-dev-test
|
||||
buildpack: python_buildpack
|
||||
health-check-type: web
|
||||
services:
|
||||
- logging-shared-dev
|
||||
command: python /app/run_stand.py
|
||||
path: .
|
||||
disk_quota: 2G
|
||||
memory: 4G
|
||||
instances: 1
|
||||
env:
|
||||
MINIO_ENDPOINT: s3-region1.ppc-jv-dev.sibintek.ru
|
||||
MINIO_ACCESS_KEY: 00a70fac02c1208446de
|
||||
MINIO_SECRET_KEY: 1gk9tVYEEoH9ADRxb4kiAuCo6CCISdV6ie0p6oDO
|
||||
MINIO_BUCKET: bucket-476684e7-1223-45ac-a101-8b5aeda487d6
|
||||
MINIO_SECURE: false
|
||||
BIN
python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
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.
BIN
python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
Binary file not shown.
BIN
python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/buckets/.usage.json/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
python_parser/minio/.minio.sys/config/config.json/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/config/config.json/xl.meta
Normal file
Binary file not shown.
BIN
python_parser/minio/.minio.sys/config/iam/format.json/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/config/iam/format.json/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
1
python_parser/minio/.minio.sys/format.json
Normal file
1
python_parser/minio/.minio.sys/format.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"1","format":"xl-single","id":"29118f57-702e-4363-9a41-9f06655e449d","xl":{"version":"3","this":"195a90f4-fc26-46a8-b6d4-0b50b99b1342","sets":[["195a90f4-fc26-46a8-b6d4-0b50b99b1342"]],"distributionAlgo":"SIPMOD+PARITY"}}
|
||||
BIN
python_parser/minio/.minio.sys/pool.bin/xl.meta
Normal file
BIN
python_parser/minio/.minio.sys/pool.bin/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
python_parser/minio/svodka-data/nin_excel_data_svodka_ca/xl.meta
Normal file
BIN
python_parser/minio/svodka-data/nin_excel_data_svodka_ca/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
python_parser/minio/svodka-data/nin_excel_data_svodka_pm/xl.meta
Normal file
BIN
python_parser/minio/svodka-data/nin_excel_data_svodka_pm/xl.meta
Normal file
Binary file not shown.
@@ -11,4 +11,5 @@ requests>=2.31.0
|
||||
# pytest-cov>=4.0.0
|
||||
# pytest-mock>=3.10.0
|
||||
httpx>=0.24.0
|
||||
numpy
|
||||
numpy
|
||||
streamlit>=1.28.0
|
||||
60
python_parser/run_streamlit_local.py
Normal file
60
python_parser/run_streamlit_local.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Запуск Streamlit интерфейса локально из изолированного пакета
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import webbrowser
|
||||
import os
|
||||
|
||||
def main():
|
||||
"""Основная функция"""
|
||||
print("🚀 ЗАПУСК STREAMLIT ИЗ ИЗОЛИРОВАННОГО ПАКЕТА")
|
||||
print("=" * 60)
|
||||
print("Убедитесь, что FastAPI сервер запущен на порту 8000")
|
||||
print("=" * 60)
|
||||
|
||||
# Проверяем, существует ли папка streamlit_app
|
||||
if not os.path.exists("streamlit_app"):
|
||||
print("❌ Папка streamlit_app не найдена")
|
||||
print("Создайте изолированный пакет или используйте docker-compose up -d")
|
||||
return
|
||||
|
||||
# Переходим в папку streamlit_app
|
||||
os.chdir("streamlit_app")
|
||||
|
||||
# Проверяем, установлен ли Streamlit
|
||||
try:
|
||||
import streamlit
|
||||
print(f"✅ Streamlit {streamlit.__version__} установлен")
|
||||
except ImportError:
|
||||
print("❌ Streamlit не установлен")
|
||||
print("Установите: pip install -r requirements.txt")
|
||||
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", "app.py",
|
||||
"--server.port", "8501",
|
||||
"--server.address", "localhost",
|
||||
"--server.headless", "false",
|
||||
"--browser.gatherUsageStats", "false"
|
||||
])
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Streamlit остановлен")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
python_parser/runtime.txt
Normal file
1
python_parser/runtime.txt
Normal file
@@ -0,0 +1 @@
|
||||
python-3.11.*
|
||||
@@ -16,8 +16,7 @@ st.set_page_config(
|
||||
)
|
||||
|
||||
# Конфигурация API
|
||||
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000") # Внутренний адрес для Docker
|
||||
API_PUBLIC_URL = os.getenv("API_PUBLIC_URL", "http://localhost:8000") # Внешний адрес для пользователя
|
||||
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
|
||||
|
||||
def check_api_health():
|
||||
"""Проверка доступности API"""
|
||||
@@ -74,7 +73,7 @@ def main():
|
||||
st.info("Убедитесь, что FastAPI сервер запущен")
|
||||
return
|
||||
|
||||
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
||||
st.success(f"✅ API доступен по адресу {API_BASE_URL}")
|
||||
|
||||
# Боковая панель с информацией
|
||||
with st.sidebar:
|
||||
@@ -374,7 +373,7 @@ def main():
|
||||
# Футер
|
||||
st.markdown("---")
|
||||
st.markdown("### 📚 Документация API")
|
||||
st.markdown(f"Полная документация доступна по адресу: {API_PUBLIC_URL}/docs")
|
||||
st.markdown(f"Полная документация доступна по адресу: {API_BASE_URL}/docs")
|
||||
|
||||
# Информация о проекте
|
||||
with st.expander("ℹ️ О проекте"):
|
||||
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
|
||||
49
start_dev.py
49
start_dev.py
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт для запуска проекта в режиме разработки
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
def run_command(command, description):
|
||||
"""Выполнение команды с выводом"""
|
||||
print(f"🔄 {description}...")
|
||||
try:
|
||||
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||
print(f"✅ {description} выполнено успешно")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Ошибка при {description.lower()}:")
|
||||
print(f" Команда: {command}")
|
||||
print(f" Ошибка: {e.stderr}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("🚀 Запуск проекта в режиме разработки")
|
||||
print("=" * 50)
|
||||
|
||||
# Останавливаем продакшн контейнеры если они запущены
|
||||
if run_command("docker compose ps", "Проверка статуса контейнеров"):
|
||||
if "Up" in subprocess.run("docker compose ps", shell=True, capture_output=True, text=True).stdout:
|
||||
print("🛑 Останавливаю продакшн контейнеры...")
|
||||
run_command("docker compose down", "Остановка продакшн контейнеров")
|
||||
|
||||
# Запускаем режим разработки
|
||||
print("\n🔧 Запуск режима разработки...")
|
||||
if run_command("docker compose -f docker-compose.dev.yml up -d", "Запуск контейнеров разработки"):
|
||||
print("\n🎉 Проект запущен в режиме разработки!")
|
||||
print("\n📍 Доступные сервисы:")
|
||||
print(" • Streamlit: http://localhost:8501")
|
||||
print(" • FastAPI: http://localhost:8000")
|
||||
print(" • MinIO Console: http://localhost:9001")
|
||||
print("\n💡 Теперь изменения в streamlit_app/ будут автоматически перезагружаться!")
|
||||
print("\n🛑 Для остановки используйте:")
|
||||
print(" docker compose -f docker-compose.dev.yml down")
|
||||
else:
|
||||
print("\n❌ Не удалось запустить проект в режиме разработки")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт для запуска проекта в продакшн режиме
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_command(command, description):
|
||||
"""Выполнение команды с выводом"""
|
||||
print(f"🔄 {description}...")
|
||||
try:
|
||||
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
||||
print(f"✅ {description} выполнено успешно")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Ошибка при {description.lower()}:")
|
||||
print(f" Команда: {command}")
|
||||
print(f" Ошибка: {e.stderr}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("🚀 Запуск проекта в продакшн режиме")
|
||||
print("=" * 50)
|
||||
|
||||
# Останавливаем контейнеры разработки если они запущены
|
||||
if run_command("docker compose -f docker-compose.dev.yml ps", "Проверка статуса контейнеров разработки"):
|
||||
if "Up" in subprocess.run("docker compose -f docker-compose.dev.yml ps", shell=True, capture_output=True, text=True).stdout:
|
||||
print("🛑 Останавливаю контейнеры разработки...")
|
||||
run_command("docker compose -f docker-compose.dev.yml down", "Остановка контейнеров разработки")
|
||||
|
||||
# Запускаем продакшн режим
|
||||
print("\n🏭 Запуск продакшн режима...")
|
||||
if run_command("docker compose up -d --build", "Запуск продакшн контейнеров"):
|
||||
print("\n🎉 Проект запущен в продакшн режиме!")
|
||||
print("\n📍 Доступные сервисы:")
|
||||
print(" • Streamlit: http://localhost:8501")
|
||||
print(" • FastAPI: http://localhost:8000")
|
||||
print(" • MinIO Console: http://localhost:9001")
|
||||
print("\n💡 Для разработки используйте:")
|
||||
print(" python start_dev.py")
|
||||
print("\n🛑 Для остановки используйте:")
|
||||
print(" docker compose down")
|
||||
else:
|
||||
print("\n❌ Не удалось запустить проект в продакшн режиме")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,15 +0,0 @@
|
||||
[server]
|
||||
port = 8501
|
||||
address = "0.0.0.0"
|
||||
enableCORS = false
|
||||
enableXsrfProtection = false
|
||||
|
||||
[browser]
|
||||
gatherUsageStats = false
|
||||
|
||||
[theme]
|
||||
primaryColor = "#FF4B4B"
|
||||
backgroundColor = "#FFFFFF"
|
||||
secondaryBackgroundColor = "#F0F2F6"
|
||||
textColor = "#262730"
|
||||
font = "sans serif"
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Установка системных зависимостей
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Копирование requirements.txt
|
||||
COPY requirements.txt .
|
||||
|
||||
# Установка Python зависимостей
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копирование кода приложения
|
||||
COPY . .
|
||||
|
||||
# Открытие порта
|
||||
EXPOSE 8501
|
||||
|
||||
# Запуск Streamlit
|
||||
CMD ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
||||
@@ -1,100 +0,0 @@
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from minio import Minio
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
# Конфигурация страницы
|
||||
st.set_page_config(
|
||||
page_title="Сводка данных",
|
||||
page_icon="📊",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
# Заголовок приложения
|
||||
st.title("📊 Анализ данных сводки")
|
||||
st.markdown("---")
|
||||
|
||||
# Инициализация MinIO клиента
|
||||
@st.cache_resource
|
||||
def init_minio_client():
|
||||
try:
|
||||
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"
|
||||
)
|
||||
return client
|
||||
except Exception as e:
|
||||
st.error(f"Ошибка подключения к MinIO: {e}")
|
||||
return None
|
||||
|
||||
# Боковая панель
|
||||
with st.sidebar:
|
||||
st.header("⚙️ Настройки")
|
||||
|
||||
# Выбор типа данных
|
||||
data_type = st.selectbox(
|
||||
"Тип данных",
|
||||
["Мониторинг топлива", "Сводка ПМ", "Сводка ЦА"]
|
||||
)
|
||||
|
||||
# Выбор периода
|
||||
period = st.date_input(
|
||||
"Период",
|
||||
value=pd.Timestamp.now().date()
|
||||
)
|
||||
|
||||
st.markdown("---")
|
||||
st.markdown("### 📈 Статистика")
|
||||
st.info("Выберите тип данных для анализа")
|
||||
|
||||
# Основной контент
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
st.subheader(f"📋 {data_type}")
|
||||
|
||||
if data_type == "Мониторинг топлива":
|
||||
st.info("Анализ данных мониторинга топлива")
|
||||
# Здесь будет логика для работы с данными мониторинга топлива
|
||||
|
||||
elif data_type == "Сводка ПМ":
|
||||
st.info("Анализ данных сводки ПМ")
|
||||
# Здесь будет логика для работы с данными сводки ПМ
|
||||
|
||||
elif data_type == "Сводка ЦА":
|
||||
st.info("Анализ данных сводки ЦА")
|
||||
# Здесь будет логика для работы с данными сводки ЦА
|
||||
|
||||
with col2:
|
||||
st.subheader("📊 Быстрая статистика")
|
||||
st.metric("Всего записей", "0")
|
||||
st.metric("Активных", "0")
|
||||
st.metric("Ошибок", "0")
|
||||
|
||||
# Нижняя панель
|
||||
st.markdown("---")
|
||||
st.subheader("🔍 Детальный анализ")
|
||||
|
||||
# Заглушка для графиков
|
||||
placeholder = st.empty()
|
||||
with placeholder.container():
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.write("📈 График 1")
|
||||
# Здесь будет график
|
||||
|
||||
with col2:
|
||||
st.write("📊 График 2")
|
||||
# Здесь будет график
|
||||
|
||||
# Футер
|
||||
st.markdown("---")
|
||||
st.markdown("**Разработано для анализа данных сводки** | v1.0.0")
|
||||
@@ -1,7 +0,0 @@
|
||||
streamlit>=1.28.0
|
||||
pandas>=2.0.0
|
||||
numpy>=1.24.0
|
||||
plotly>=5.15.0
|
||||
minio>=7.1.0
|
||||
openpyxl>=3.1.0
|
||||
xlrd>=2.0.1
|
||||
Reference in New Issue
Block a user