8 Commits

Author SHA1 Message Date
72fe115a99 main 2025-09-01 19:22:58 +03:00
46a30c32ed Сервисы 2025-09-01 19:20:16 +03:00
5e217c7cce порты 2025-09-01 19:19:28 +03:00
7d2747c8fe rm лишнее 2025-09-01 19:16:23 +03:00
513ff3c144 Реализация для дева с хот релоадом 2025-09-01 19:06:55 +03:00
a0b6e04d99 Правильное отображение имени 2025-09-01 19:01:06 +03:00
47a7344755 streamlit fix 2025-09-01 18:57:39 +03:00
456e9935f0 fix streamlit 2025-09-01 14:08:19 +03:00
44 changed files with 853 additions and 631 deletions

155
.gitignore vendored Normal file
View File

@@ -0,0 +1,155 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
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/
# 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
htmlcov/
# Jupyter Notebook
.ipynb_checkpoints
# 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
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# Local development
local_settings.py
db.sqlite3
db.sqlite3-journal
# FastAPI
.pytest_cache/
.coverage
htmlcov/
# Streamlit
.streamlit/secrets.toml
# Node.js (if any frontend components)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

41
QUICK_START.md Normal file
View File

@@ -0,0 +1,41 @@
# 🚀 Быстрый запуск проекта
## 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 Normal file
View File

@@ -0,0 +1,117 @@
# 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
```
## 📝 Лицензия
Проект разработан для внутреннего использования.

58
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,58 @@
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
"

View File

@@ -1,3 +1,5 @@
# Продакшн конфигурация
# Для разработки используйте: docker compose -f docker-compose.dev.yml up -d
services: services:
minio: minio:
image: minio/minio:latest image: minio/minio:latest
@@ -10,11 +12,11 @@ services:
MINIO_ROOT_PASSWORD: minioadmin MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001" command: server /data --console-address ":9001"
volumes: volumes:
- minio_data:/data - ./minio_data:/data
restart: unless-stopped restart: unless-stopped
fastapi: fastapi:
build: . build: ./python_parser
container_name: svodka_fastapi container_name: svodka_fastapi
ports: ports:
- "8000:8000" - "8000:8000"
@@ -28,5 +30,20 @@ services:
- minio - minio
restart: unless-stopped restart: unless-stopped
volumes: streamlit:
minio_data: build: ./streamlit_app
container_name: svodka_streamlit
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
depends_on:
- minio
- fastapi
restart: unless-stopped

View File

@@ -1,28 +0,0 @@
[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

View File

@@ -1 +0,0 @@
web: python /app/run_stand.py

View File

@@ -1,66 +0,0 @@
# 🚀 Быстрый старт 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
```

View File

@@ -1,197 +0,0 @@
# NIN Excel Parsers API
API для парсинга Excel отчетов нефтеперерабатывающих заводов (НПЗ) с использованием FastAPI и MinIO для хранения данных.
## 🚀 Быстрый запуск
### **Вариант 1: Только MinIO в Docker + FastAPI локально**
```bash
# Запуск MinIO в Docker
docker-compose up -d minio
# Запуск FastAPI локально
python run_dev.py
# В отдельном терминале запуск Streamlit
python run_streamlit.py
```
### **Вариант 2: MinIO + FastAPI в Docker + Streamlit локально**
```bash
# Запуск MinIO и FastAPI в Docker
docker-compose up -d
# В отдельном терминале запуск Streamlit
python run_streamlit.py
```
### **Вариант 3: Только MinIO в Docker**
```bash
# Запуск только MinIO
docker-compose up -d minio
```
## 📋 Описание сервисов
- **MinIO** (порт 9000-9001): S3-совместимое хранилище для данных
- **FastAPI** (порт 8000): API сервер для парсинга Excel файлов
- **Streamlit** (порт 8501): Веб-интерфейс для демонстрации API
## 🔧 Диагностика
Для проверки состояния всех сервисов:
```bash
python check_services.py
```
## 🛑 Остановка
### Остановка Docker сервисов:
```bash
# Все сервисы
docker-compose down
# Только MinIO
docker-compose stop minio
```
### Остановка Streamlit:
```bash
# Нажмите Ctrl+C в терминале с Streamlit
```
## 📁 Структура проекта
```
python_parser/
├── app/ # FastAPI приложение
│ ├── main.py # Основной файл приложения
│ └── schemas/ # Pydantic схемы
├── core/ # Бизнес-логика
│ ├── models.py # Модели данных
│ ├── ports.py # Интерфейсы (порты)
│ └── services.py # Сервисы
├── adapters/ # Адаптеры для внешних систем
│ ├── storage.py # MinIO адаптер
│ └── parsers/ # Парсеры Excel файлов
├── data/ # Тестовые данные
├── docker-compose.yml # Docker Compose конфигурация
├── Dockerfile # Docker образ для FastAPI
├── run_dev.py # Запуск FastAPI локально
├── run_streamlit.py # Запуск Streamlit
└── check_services.py # Диагностика сервисов
```
## 🔍 Доступные эндпоинты
- **GET /** - Информация об API
- **GET /docs** - Swagger документация
- **POST /svodka_pm/upload-zip** - Загрузка сводок ПМ
- **POST /svodka_ca/upload-zip** - Загрузка сводок ЦА
- **POST /monitoring_fuel/upload-zip** - Загрузка мониторинга топлива
- **GET /svodka_pm/data** - Получение данных сводок ПМ
- **GET /svodka_ca/data** - Получение данных сводок ЦА
- **GET /monitoring_fuel/data** - Получение данных мониторинга топлива
## 📊 Поддерживаемые типы отчетов
1. **svodka_pm** - Сводки по переработке нефти (ПМ)
2. **svodka_ca** - Сводки по переработке нефти (ЦА)
3. **monitoring_fuel** - Мониторинг топлива
## 🐳 Docker команды
### Сборка и запуск:
```bash
# Все сервисы
docker-compose up -d --build
# Только MinIO
docker-compose up -d minio
# Только FastAPI (требует MinIO)
docker-compose up -d fastapi
```
### Просмотр логов:
```bash
# Все сервисы
docker-compose logs
# Конкретный сервис
docker-compose logs fastapi
docker-compose logs minio
```
### Остановка:
```bash
docker-compose down
```
## 🔧 Устранение неполадок
### Проблема: "Streamlit не может подключиться к FastAPI"
**Симптомы:**
- Streamlit открывается, но показывает "API недоступен по адресу http://localhost:8000"
- FastAPI не отвечает на порту 8000
**Решения:**
1. **Проверьте порты:**
```bash
# Windows
netstat -an | findstr :8000
# Linux/Mac
netstat -an | grep :8000
```
2. **Перезапустите FastAPI:**
```bash
# Остановите текущий процесс (Ctrl+C)
python run_dev.py
```
3. **Проверьте логи Docker:**
```bash
docker-compose logs fastapi
```
### Проблема: "MinIO недоступен"
**Решения:**
1. Запустите Docker Desktop
2. Проверьте статус контейнера: `docker ps`
3. Перезапустите MinIO: `docker-compose restart minio`
### Проблема: "Порт уже занят"
**Решения:**
1. Найдите процесс: `netstat -ano | findstr :8000`
2. Остановите процесс: `taskkill /PID <номер_процесса>`
3. Или используйте другой порт в конфигурации
## 🚀 Разработка
### Добавление нового парсера:
1. Создайте файл в `adapters/parsers/`
2. Реализуйте интерфейс `ParserPort`
3. Добавьте в `core/services.py`
4. Создайте схемы в `app/schemas/`
5. Добавьте эндпоинты в `app/main.py`
### Тестирование:
```bash
# Запуск тестов
pytest
# Запуск с покрытием
pytest --cov=.
```
## 📝 Лицензия
Проект разработан для внутреннего использования НИН.

View File

@@ -1,186 +0,0 @@
# 🚀 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

View File

@@ -96,6 +96,54 @@ async def get_available_parsers():
return {"parsers": parsers} return {"parsers": parsers}
@app.get("/parsers/{parser_name}/getters", tags=["Общее"],
summary="Информация о геттерах парсера",
description="Возвращает информацию о доступных геттерах для указанного парсера",
responses={
200: {
"content": {
"application/json": {
"example": {
"parser": "svodka_pm",
"getters": {
"single_og": {
"required_params": ["id", "codes", "columns"],
"optional_params": ["search"],
"description": "Получение данных по одному ОГ"
},
"total_ogs": {
"required_params": ["codes", "columns"],
"optional_params": ["search"],
"description": "Получение данных по всем ОГ"
}
}
}
}
}
},
404: {
"description": "Парсер не найден"
}
})
async def get_parser_getters(parser_name: str):
"""Получение информации о геттерах парсера"""
if parser_name not in PARSERS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Парсер '{parser_name}' не найден"
)
parser_class = PARSERS[parser_name]
parser_instance = parser_class()
getters_info = parser_instance.get_available_getters()
return {
"parser": parser_name,
"getters": getters_info
}
@app.get("/server-info", tags=["Общее"], @app.get("/server-info", tags=["Общее"],
summary="Информация о сервере", summary="Информация о сервере",
response_model=ServerInfoResponse,) response_model=ServerInfoResponse,)
@@ -352,40 +400,40 @@ async def get_svodka_pm_total_ogs(
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
# @app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name]) @app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
# async def get_svodka_pm_data( async def get_svodka_pm_data(
# request_data: dict request_data: dict
# ): ):
# report_service = get_report_service() report_service = get_report_service()
# """ """
# Получение данных из отчета сводки факта СарНПЗ Получение данных из отчета сводки факта СарНПЗ
# - indicator_id: ID индикатора - indicator_id: ID индикатора
# - code: Код для поиска - code: Код для поиска
# - search_value: Опциональное значение для поиска - search_value: Опциональное значение для поиска
# """ """
# try: try:
# # Создаем запрос # Создаем запрос
# request = DataRequest( request = DataRequest(
# report_type='svodka_pm', report_type='svodka_pm',
# get_params=request_data get_params=request_data
# ) )
# # Получаем данные # Получаем данные
# result = report_service.get_data(request) result = report_service.get_data(request)
# if result.success: if result.success:
# return { return {
# "success": True, "success": True,
# "data": result.data "data": result.data
# } }
# else: else:
# raise HTTPException(status_code=404, detail=result.message) raise HTTPException(status_code=404, detail=result.message)
# except HTTPException: except HTTPException:
# raise raise
# except Exception as e: except Exception as e:
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
@app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name], @app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
@@ -562,38 +610,38 @@ async def get_svodka_ca_data(
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") # raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
# @app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name]) @app.post("/monitoring_fuel/get_data", tags=[MonitoringFuelParser.name])
# async def get_monitoring_fuel_data( async def get_monitoring_fuel_data(
# request_data: dict request_data: dict
# ): ):
# report_service = get_report_service() report_service = get_report_service()
# """ """
# Получение данных из отчета мониторинга топлива Получение данных из отчета мониторинга топлива
# - column: Название колонки для агрегации (normativ, total, total_svod) - column: Название колонки для агрегации (normativ, total, total_svod)
# """ """
# try: try:
# # Создаем запрос # Создаем запрос
# request = DataRequest( request = DataRequest(
# report_type='monitoring_fuel', report_type='monitoring_fuel',
# get_params=request_data get_params=request_data
# ) )
# # Получаем данные # Получаем данные
# result = report_service.get_data(request) result = report_service.get_data(request)
# if result.success: if result.success:
# return { return {
# "success": True, "success": True,
# "data": result.data "data": result.data
# } }
# else: else:
# raise HTTPException(status_code=404, detail=result.message) raise HTTPException(status_code=404, detail=result.message)
# except HTTPException: except HTTPException:
# raise raise
# except Exception as e: except Exception as e:
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name]) # @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])

View File

@@ -2,28 +2,93 @@
Порты (интерфейсы) для hexagonal architecture Порты (интерфейсы) для hexagonal architecture
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional from typing import Optional, Dict, List, Any, Callable
import pandas as pd import pandas as pd
class ParserPort(ABC): class ParserPort(ABC):
"""Интерфейс для парсеров""" """Интерфейс для парсеров с поддержкой множественных геттеров"""
def __init__(self):
"""Инициализация с пустым словарем геттеров"""
self.getters: Dict[str, Dict[str, Any]] = {}
self._register_default_getters()
def _register_default_getters(self):
"""Регистрация геттеров по умолчанию - переопределяется в наследниках"""
pass
def register_getter(self, name: str, method: Callable, required_params: List[str],
optional_params: List[str] = None, description: str = ""):
"""
Регистрация нового геттера
Args:
name: Имя геттера
method: Метод для выполнения
required_params: Список обязательных параметров
optional_params: Список необязательных параметров
description: Описание геттера
"""
if optional_params is None:
optional_params = []
self.getters[name] = {
"method": method,
"required_params": required_params,
"optional_params": optional_params,
"description": description
}
def get_available_getters(self) -> Dict[str, Dict[str, Any]]:
"""Получение списка доступных геттеров с их описанием"""
return {
name: {
"required_params": info["required_params"],
"optional_params": info["optional_params"],
"description": info["description"]
}
for name, info in self.getters.items()
}
# Добавить схему
def get_value(self, getter_name: str, params: Dict[str, Any]):
"""
Получение значения через указанный геттер
Args:
getter_name: Имя геттера
params: Параметры для геттера
Returns:
Результат выполнения геттера
Raises:
ValueError: Если геттер не найден или параметры неверны
"""
if getter_name not in self.getters:
available = list(self.getters.keys())
raise ValueError(f"Геттер '{getter_name}' не найден. Доступные: {available}")
getter_info = self.getters[getter_name]
required = getter_info["required_params"]
# Проверка обязательных параметров
missing = [p for p in required if p not in params]
if missing:
raise ValueError(f"Отсутствуют обязательные параметры для геттера '{getter_name}': {missing}")
# Вызов метода геттера
try:
return getter_info["method"](params)
except Exception as e:
raise ValueError(f"Ошибка выполнения геттера '{getter_name}': {str(e)}")
@abstractmethod @abstractmethod
def parse(self, file_path: str, params: dict) -> pd.DataFrame: def parse(self, file_path: str, params: dict) -> pd.DataFrame:
"""Парсинг файла и возврат DataFrame""" """Парсинг файла и возврат DataFrame"""
pass pass
@abstractmethod
def get_value(self, df: pd.DataFrame, params: dict):
"""Получение значения из DataFrame по параметрам"""
pass
# @abstractmethod
# def get_schema(self) -> dict:
# """Возвращает схему входных параметров для парсера"""
# pass
class StoragePort(ABC): class StoragePort(ABC):
"""Интерфейс для хранилища данных""" """Интерфейс для хранилища данных"""

View File

@@ -99,9 +99,35 @@ class ReportService:
# Получаем парсер # Получаем парсер
parser = get_parser(request.report_type) parser = get_parser(request.report_type)
# Устанавливаем DataFrame в парсер для использования в геттерах
parser.df = df
# Получаем значение # Получаем параметры запроса
value = parser.get_value(df, request.get_params) get_params = request.get_params or {}
# Определяем имя геттера (по умолчанию используем первый доступный)
getter_name = get_params.pop("getter", None)
if not getter_name:
# Если геттер не указан, берем первый доступный
available_getters = list(parser.getters.keys())
if available_getters:
getter_name = available_getters[0]
print(f"⚠️ Геттер не указан, используем первый доступный: {getter_name}")
else:
return DataResult(
success=False,
message="Парсер не имеет доступных геттеров"
)
# Получаем значение через указанный геттер
try:
value = parser.get_value(getter_name, get_params)
except ValueError as e:
return DataResult(
success=False,
message=f"Ошибка параметров: {str(e)}"
)
# Формируем результат # Формируем результат
if value is not None: if value is not None:

View File

@@ -1,17 +0,0 @@
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

View File

@@ -1 +0,0 @@
{"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"}}

View File

@@ -11,5 +11,4 @@ requests>=2.31.0
# pytest-cov>=4.0.0 # pytest-cov>=4.0.0
# pytest-mock>=3.10.0 # pytest-mock>=3.10.0
httpx>=0.24.0 httpx>=0.24.0
numpy numpy
streamlit>=1.28.0

View File

@@ -1,51 +0,0 @@
#!/usr/bin/env python3
"""
Запуск Streamlit интерфейса для NIN Excel Parsers API
"""
import subprocess
import sys
import webbrowser
import time
def main():
"""Основная функция"""
print("🚀 ЗАПУСК STREAMLIT ИНТЕРФЕЙСА")
print("=" * 50)
print("Убедитесь, что FastAPI сервер запущен на порту 8000")
print("=" * 50)
# Проверяем, установлен ли Streamlit
try:
import streamlit
print(f"✅ Streamlit {streamlit.__version__} установлен")
except ImportError:
print("❌ Streamlit не установлен")
print("Установите: pip install streamlit")
return
print("\n🚀 Запускаю Streamlit...")
print("📍 URL: http://localhost:8501")
print("🛑 Для остановки нажмите Ctrl+C")
# Открываем браузер
try:
webbrowser.open("http://localhost:8501")
print("✅ Браузер открыт")
except Exception as e:
print(f"⚠️ Не удалось открыть браузер: {e}")
# Запускаем Streamlit
try:
subprocess.run([
sys.executable, "-m", "streamlit", "run", "streamlit_app.py",
"--server.port", "8501",
"--server.address", "localhost",
"--server.headless", "false",
"--browser.gatherUsageStats", "false"
])
except KeyboardInterrupt:
print("\n👋 Streamlit остановлен")
if __name__ == "__main__":
main()

View File

@@ -1 +0,0 @@
python-3.11.*

49
start_dev.py Normal file
View File

@@ -0,0 +1,49 @@
#!/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()

49
start_prod.py Normal file
View File

@@ -0,0 +1,49 @@
#!/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()

View File

@@ -0,0 +1,15 @@
[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"

23
streamlit_app/Dockerfile Normal file
View 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/*
# Копирование 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"]

View File

@@ -0,0 +1,100 @@
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")

View File

@@ -0,0 +1,7 @@
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

View File

@@ -16,7 +16,8 @@ st.set_page_config(
) )
# Конфигурация API # Конфигурация API
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000") API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000") # Внутренний адрес для Docker
API_PUBLIC_URL = os.getenv("API_PUBLIC_URL", "http://localhost:8000") # Внешний адрес для пользователя
def check_api_health(): def check_api_health():
"""Проверка доступности API""" """Проверка доступности API"""
@@ -73,7 +74,7 @@ def main():
st.info("Убедитесь, что FastAPI сервер запущен") st.info("Убедитесь, что FastAPI сервер запущен")
return return
st.success(f"✅ API доступен по адресу {API_BASE_URL}") st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
# Боковая панель с информацией # Боковая панель с информацией
with st.sidebar: with st.sidebar:
@@ -254,8 +255,8 @@ def main():
modes = st.multiselect( modes = st.multiselect(
"Выберите режимы", "Выберите режимы",
["План", "Факт", "Норматив"], ["plan", "fact", "normativ"],
default=["План", "Факт"], default=["plan", "fact"],
key="ca_modes" key="ca_modes"
) )
@@ -373,7 +374,7 @@ def main():
# Футер # Футер
st.markdown("---") st.markdown("---")
st.markdown("### 📚 Документация API") st.markdown("### 📚 Документация API")
st.markdown(f"Полная документация доступна по адресу: {API_BASE_URL}/docs") st.markdown(f"Полная документация доступна по адресу: {API_PUBLIC_URL}/docs")
# Информация о проекте # Информация о проекте
with st.expander(" О проекте"): with st.expander(" О проекте"):