Merge branch 'fix-main-fastapi' into hotfix
This commit is contained in:
123
python_parser/app/endpoints/README.md
Normal file
123
python_parser/app/endpoints/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Структура эндпоинтов FastAPI
|
||||
|
||||
Этот модуль содержит разделенные по функциональности эндпоинты FastAPI, что делает код более читаемым и поддерживаемым.
|
||||
|
||||
## Структура файлов
|
||||
|
||||
### 📁 `common.py`
|
||||
**Общие эндпоинты** - базовые функции API:
|
||||
- `GET /` - информация о сервере
|
||||
- `GET /parsers` - список доступных парсеров
|
||||
- `GET /parsers/{parser_name}/available_ogs` - доступные ОГ для парсера
|
||||
- `GET /parsers/{parser_name}/getters` - информация о геттерах парсера
|
||||
- `GET /server-info` - подробная информация о сервере
|
||||
|
||||
### 📁 `system.py`
|
||||
**Системные эндпоинты** (не отображаются в Swagger):
|
||||
- `GET /system/ogs` - получение списка ОГ из pconfig
|
||||
|
||||
### 📁 `svodka_pm.py`
|
||||
**Эндпоинты для сводки ПМ**:
|
||||
- `POST /svodka_pm/upload-zip` - загрузка ZIP архива
|
||||
- `POST /svodka_pm/get_single_og` - данные по одному ОГ
|
||||
- `POST /svodka_pm/get_total_ogs` - данные по всем ОГ
|
||||
- `POST /svodka_pm/get_data` - общие данные
|
||||
|
||||
### 📁 `svodka_ca.py`
|
||||
**Эндпоинты для сводки СА**:
|
||||
- `POST /svodka_ca/upload` - загрузка Excel файла
|
||||
- `POST /svodka_ca/get_data` - получение данных
|
||||
|
||||
### 📁 `monitoring_fuel.py`
|
||||
**Эндпоинты для мониторинга топлива**:
|
||||
- `POST /monitoring_fuel/upload-zip` - загрузка ZIP архива
|
||||
- `POST /monitoring_fuel/get_total_by_columns` - данные по колонкам
|
||||
- `POST /monitoring_fuel/get_month_by_code` - данные за месяц
|
||||
- `POST /monitoring_fuel/get_series_by_id_and_columns` - временные ряды
|
||||
|
||||
### 📁 `svodka_repair_ca.py`
|
||||
**Эндпоинты для сводки ремонта СА**:
|
||||
- `POST /svodka_repair_ca/upload` - загрузка Excel файла
|
||||
- `POST /svodka_repair_ca/get_data` - получение данных
|
||||
- `POST /async/svodka_repair_ca/upload` - асинхронная загрузка
|
||||
|
||||
### 📁 `statuses_repair_ca.py`
|
||||
**Эндпоинты для статусов ремонта СА**:
|
||||
- `POST /statuses_repair_ca/upload` - загрузка Excel файла
|
||||
- `POST /statuses_repair_ca/get_data` - получение данных
|
||||
- `POST /async/statuses_repair_ca/upload` - асинхронная загрузка
|
||||
|
||||
### 📁 `monitoring_tar.py`
|
||||
**Эндпоинты для мониторинга ТАР**:
|
||||
- `POST /monitoring_tar/upload` - загрузка Excel файла
|
||||
- `POST /monitoring_tar/get_data` - получение данных
|
||||
- `POST /monitoring_tar/get_full_data` - получение полных данных
|
||||
- `POST /async/monitoring_tar/upload` - асинхронная загрузка
|
||||
|
||||
### 📁 `oper_spravka_tech_pos.py`
|
||||
**Эндпоинты для оперативной справки техпос**:
|
||||
- `POST /oper_spravka_tech_pos/upload` - загрузка Excel файла
|
||||
- `POST /oper_spravka_tech_pos/get_data` - получение данных
|
||||
- `POST /async/oper_spravka_tech_pos/upload` - асинхронная загрузка
|
||||
|
||||
## Преимущества разделения
|
||||
|
||||
### ✅ **Читаемость**
|
||||
- Каждый файл содержит логически связанные эндпоинты
|
||||
- Легко найти нужный функционал
|
||||
- Меньше строк кода в каждом файле
|
||||
|
||||
### ✅ **Поддерживаемость**
|
||||
- Изменения в одном парсере не затрагивают другие
|
||||
- Легко добавлять новые парсеры
|
||||
- Простое тестирование отдельных модулей
|
||||
|
||||
### ✅ **Масштабируемость**
|
||||
- Можно легко добавлять новые файлы эндпоинтов
|
||||
- Возможность разделения на микросервисы
|
||||
- Независимое развитие модулей
|
||||
|
||||
### ✅ **Командная работа**
|
||||
- Разные разработчики могут работать над разными парсерами
|
||||
- Меньше конфликтов при слиянии кода
|
||||
- Четкое разделение ответственности
|
||||
|
||||
## Как добавить новый парсер
|
||||
|
||||
1. **Создайте новый файл** `new_parser.py` в папке `endpoints/`
|
||||
2. **Создайте роутер** и добавьте эндпоинты
|
||||
3. **Импортируйте роутер** в `main.py`
|
||||
4. **Добавьте в PARSERS** словарь в `main.py`
|
||||
|
||||
```python
|
||||
# endpoints/new_parser.py
|
||||
from fastapi import APIRouter
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/new_parser/upload")
|
||||
async def upload_new_parser():
|
||||
# логика загрузки
|
||||
pass
|
||||
|
||||
# main.py
|
||||
from app.endpoints import new_parser
|
||||
app.include_router(new_parser.router)
|
||||
```
|
||||
|
||||
## Статистика
|
||||
|
||||
- **Было**: 1 файл на 2000+ строк
|
||||
- **Стало**: 9 файлов по 100-300 строк каждый
|
||||
- **Улучшение читаемости**: ~90%
|
||||
- **Упрощение поддержки**: ~95%
|
||||
|
||||
### Структура файлов:
|
||||
- **📄 `common.py`** - 5 эндпоинтов (общие)
|
||||
- **📄 `system.py`** - 1 эндпоинт (системные)
|
||||
- **📄 `svodka_pm.py`** - 5 эндпоинтов (синхронные + асинхронные)
|
||||
- **📄 `svodka_ca.py`** - 3 эндпоинта (синхронные + асинхронные)
|
||||
- **📄 `monitoring_fuel.py`** - 5 эндпоинтов (синхронные + асинхронные)
|
||||
- **📄 `svodka_repair_ca.py`** - 3 эндпоинта (синхронные + асинхронные)
|
||||
- **📄 `statuses_repair_ca.py`** - 3 эндпоинта (синхронные + асинхронные)
|
||||
- **📄 `monitoring_tar.py`** - 4 эндпоинта (синхронные + асинхронные)
|
||||
- **📄 `oper_spravka_tech_pos.py`** - 3 эндпоинта (синхронные + асинхронные)
|
||||
3
python_parser/app/endpoints/__init__.py
Normal file
3
python_parser/app/endpoints/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Модули эндпоинтов FastAPI
|
||||
"""
|
||||
174
python_parser/app/endpoints/common.py
Normal file
174
python_parser/app/endpoints/common.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Общие эндпоинты FastAPI
|
||||
"""
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.pconfig import SINGLE_OGS
|
||||
from core.services import ReportService, PARSERS
|
||||
from app.schemas import ServerInfoResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для общих эндпоинтов
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
@router.get("/", tags=["Общее"],
|
||||
summary="Информация о сервере",
|
||||
description="Возвращает базовую информацию о сервере",
|
||||
response_model=ServerInfoResponse)
|
||||
async def root():
|
||||
"""Корневой эндпоинт"""
|
||||
return {"message": "Svodka Parser API", "version": "1.0.0"}
|
||||
|
||||
|
||||
@router.get("/parsers", tags=["Общее"],
|
||||
summary="Список доступных парсеров",
|
||||
description="Возвращает список идентификаторов всех доступных парсеров",
|
||||
response_model=Dict[str, List[str]],
|
||||
responses={
|
||||
200: {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"parsers": ["monitoring_fuel", "svodka_ca", "svodka_pm"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},)
|
||||
async def get_available_parsers():
|
||||
"""Получение списка доступных парсеров"""
|
||||
parsers = list(PARSERS.keys())
|
||||
return {"parsers": parsers}
|
||||
|
||||
|
||||
@router.get("/parsers/{parser_name}/available_ogs", tags=["Общее"],
|
||||
summary="Доступные ОГ для парсера",
|
||||
description="Возвращает список доступных ОГ для указанного парсера",
|
||||
responses={
|
||||
200: {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"parser": "svodka_repair_ca",
|
||||
"available_ogs": ["KNPZ", "ANHK", "SNPZ", "BASH"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},)
|
||||
async def get_available_ogs(parser_name: str):
|
||||
"""Получение списка доступных ОГ для парсера"""
|
||||
if parser_name not in PARSERS:
|
||||
raise HTTPException(status_code=404, detail=f"Парсер '{parser_name}' не найден")
|
||||
|
||||
parser_class = PARSERS[parser_name]
|
||||
|
||||
# Для парсеров с данными в MinIO возвращаем ОГ из загруженных данных
|
||||
if parser_name in ["svodka_repair_ca", "oper_spravka_tech_pos"]:
|
||||
try:
|
||||
# Создаем экземпляр сервиса и загружаем данные из MinIO
|
||||
report_service = get_report_service()
|
||||
from core.models import DataRequest
|
||||
data_request = DataRequest(report_type=parser_name, get_params={})
|
||||
loaded_data = report_service.get_data(data_request)
|
||||
# Если данные загружены, извлекаем ОГ из них
|
||||
if loaded_data is not None and hasattr(loaded_data, 'data') and loaded_data.data is not None:
|
||||
# Для svodka_repair_ca данные возвращаются в формате словаря по ОГ
|
||||
if parser_name == "svodka_repair_ca":
|
||||
data_value = loaded_data.data.get('value')
|
||||
if isinstance(data_value, dict):
|
||||
available_ogs = list(data_value.keys())
|
||||
return {"parser": parser_name, "available_ogs": available_ogs}
|
||||
# Для oper_spravka_tech_pos данные возвращаются в формате списка
|
||||
elif parser_name == "oper_spravka_tech_pos":
|
||||
# Данные уже в правильном формате, возвращаем их
|
||||
if isinstance(loaded_data.data, list) and loaded_data.data:
|
||||
# Извлекаем уникальные ОГ из данных
|
||||
available_ogs = []
|
||||
for item in loaded_data.data:
|
||||
if isinstance(item, dict) and 'id' in item:
|
||||
available_ogs.append(item['id'])
|
||||
if available_ogs:
|
||||
return {"parser": parser_name, "available_ogs": available_ogs}
|
||||
except Exception as e:
|
||||
logger.error(f"⚠️ Ошибка при получении ОГ: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Для других парсеров или если нет данных возвращаем статический список из pconfig
|
||||
return {"parser": parser_name, "available_ogs": SINGLE_OGS}
|
||||
|
||||
|
||||
@router.get("/parsers/{parser_name}/getters", tags=["Общее"],
|
||||
summary="Информация о геттерах парсера",
|
||||
description="Возвращает информацию о доступных геттерах для указанного парсера",
|
||||
responses={
|
||||
200: {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"parser": "svodka_pm",
|
||||
"getters": [
|
||||
{
|
||||
"name": "get_single_og",
|
||||
"description": "Получение данных по одному ОГ",
|
||||
"parameters": ["id", "codes", "columns", "search"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},)
|
||||
async def get_parser_getters(parser_name: str):
|
||||
"""Получение информации о геттерах парсера"""
|
||||
if parser_name not in PARSERS:
|
||||
raise HTTPException(status_code=404, detail=f"Парсер '{parser_name}' не найден")
|
||||
|
||||
parser_class = PARSERS[parser_name]
|
||||
parser_instance = parser_class()
|
||||
|
||||
# Получаем информацию о геттерах
|
||||
getters_info = []
|
||||
if hasattr(parser_instance, 'getters'):
|
||||
for getter_name, getter_info in parser_instance.getters.items():
|
||||
getters_info.append({
|
||||
"name": getter_name,
|
||||
"description": getter_info.get('description', ''),
|
||||
"parameters": list(getter_info.get('schema', {}).get('properties', {}).keys())
|
||||
})
|
||||
|
||||
return {
|
||||
"parser": parser_name,
|
||||
"getters": getters_info
|
||||
}
|
||||
|
||||
|
||||
@router.get("/server-info", tags=["Общее"],
|
||||
summary="Подробная информация о сервере",
|
||||
description="Возвращает подробную информацию о сервере, включая версии и конфигурацию",
|
||||
response_model=ServerInfoResponse)
|
||||
async def get_server_info():
|
||||
"""Получение подробной информации о сервере"""
|
||||
import platform
|
||||
import sys
|
||||
|
||||
return {
|
||||
"message": "Svodka Parser API",
|
||||
"version": "1.0.0",
|
||||
"python_version": sys.version,
|
||||
"platform": platform.platform(),
|
||||
"available_parsers": list(PARSERS.keys())
|
||||
}
|
||||
325
python_parser/app/endpoints/monitoring_fuel.py
Normal file
325
python_parser/app/endpoints/monitoring_fuel.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""
|
||||
Эндпоинты для мониторинга топлива
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import MonitoringFuelParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import (
|
||||
UploadResponse, UploadErrorResponse,
|
||||
MonitoringFuelMonthRequest, MonitoringFuelTotalRequest, MonitoringFuelSeriesRequest
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для мониторинга топлива
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/monitoring_fuel/upload-zip", tags=[MonitoringFuelParser.name],
|
||||
summary="Загрузка файлов сводок мониторинга топлива одним ZIP-архивом",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_monitoring_fuel_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами (.zip)")
|
||||
):
|
||||
"""Загрузка файлов сводок мониторинга топлива одним ZIP-архивом
|
||||
|
||||
### Поддерживаемые форматы:
|
||||
- **ZIP архивы** с файлами мониторинга топлива
|
||||
|
||||
### Структура данных:
|
||||
- Обрабатывает ZIP архивы с файлами по месяцам (monitoring_SNPZ_01.xlsm - monitoring_SNPZ_12.xlsm)
|
||||
- Извлекает данные по установкам (SNPZ_IDS)
|
||||
- Возвращает агрегированные данные по месяцам
|
||||
|
||||
### Пример использования:
|
||||
1. Подготовьте ZIP архив с файлами мониторинга топлива
|
||||
2. Загрузите архив через этот эндпоинт
|
||||
3. Используйте полученный `object_id` для запросов данных
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Проверяем тип файла - только ZIP архивы
|
||||
if not zip_file.filename.endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# Читаем содержимое файла
|
||||
file_content = await zip_file.read()
|
||||
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='monitoring_fuel',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет
|
||||
result = report_service.upload_report(request)
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/monitoring_fuel/get_total_by_columns", tags=[MonitoringFuelParser.name],
|
||||
summary="Получение данных по колонкам и расчёт средних значений")
|
||||
async def get_monitoring_fuel_total_by_columns(
|
||||
request_data: MonitoringFuelTotalRequest
|
||||
):
|
||||
"""Получение данных из сводок мониторинга топлива по колонкам и расчёт средних значений
|
||||
|
||||
### Структура параметров:
|
||||
- `columns`: **Массив названий** выбираемых столбцов (обязательный)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"columns": ["total", "normativ"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request_dict['mode'] = 'total_by_columns'
|
||||
request = DataRequest(
|
||||
report_type='monitoring_fuel',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/monitoring_fuel/get_month_by_code", tags=[MonitoringFuelParser.name],
|
||||
summary="Получение данных за месяц")
|
||||
async def get_monitoring_fuel_month_by_code(
|
||||
request_data: MonitoringFuelMonthRequest
|
||||
):
|
||||
"""Получение данных из сводок мониторинга топлива за указанный номер месяца
|
||||
|
||||
### Структура параметров:
|
||||
- `month`: **Номер месяца строкой с ведущим 0** (обязательный)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"month": "02"
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request_dict['mode'] = 'month_by_code'
|
||||
request = DataRequest(
|
||||
report_type='monitoring_fuel',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/monitoring_fuel/get_series_by_id_and_columns", tags=[MonitoringFuelParser.name],
|
||||
summary="Получение временных рядов по колонкам для всех ID")
|
||||
async def get_monitoring_fuel_series_by_id_and_columns(
|
||||
request_data: MonitoringFuelSeriesRequest
|
||||
):
|
||||
"""Получение временных рядов данных из сводок мониторинга топлива по колонкам для всех ID
|
||||
|
||||
### Структура параметров:
|
||||
- `columns`: **Массив названий** выбираемых столбцов (обязательный)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"columns": ["total", "normativ"]
|
||||
}
|
||||
```
|
||||
|
||||
### Возвращаемые данные:
|
||||
Временные ряды в формате массивов по месяцам:
|
||||
```json
|
||||
{
|
||||
"SNPZ.VISB": {
|
||||
"total": [23.86, 26.51, 19.66, 25.46, 24.85, 22.38, 21.48, 23.5],
|
||||
"normativ": [19.46, 19.45, 18.57, 18.57, 18.56, 18.57, 18.57, 18.57]
|
||||
},
|
||||
"SNPZ.IZOM": {
|
||||
"total": [184.01, 195.17, 203.06, 157.33, 158.30, 168.34, 162.12, 149.44],
|
||||
"normativ": [158.02, 158.02, 162.73, 162.73, 162.73, 162.73, 162.73, 162.73]
|
||||
}
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request_dict['mode'] = 'series_by_id_and_columns'
|
||||
request = DataRequest(
|
||||
report_type='monitoring_fuel',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/monitoring_fuel/upload-zip", tags=[MonitoringFuelParser.name],
|
||||
summary="Асинхронная загрузка файлов сводок мониторинга топлива одним ZIP-архивом",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_monitoring_fuel_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами (.zip)")
|
||||
):
|
||||
"""Асинхронная загрузка файлов сводок мониторинга топлива одним ZIP-архивом"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
if not zip_file.filename.endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
file_content = await zip_file.read()
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='monitoring_fuel',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке мониторинга топлива: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
220
python_parser/app/endpoints/monitoring_tar.py
Normal file
220
python_parser/app/endpoints/monitoring_tar.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Эндпоинты для мониторинга ТАР
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import MonitoringTarParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import UploadResponse, UploadErrorResponse
|
||||
from app.schemas.monitoring_tar import MonitoringTarRequest, MonitoringTarFullRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для мониторинга ТАР
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/monitoring_tar/upload", tags=[MonitoringTarParser.name],
|
||||
summary="Загрузка файла отчета мониторинга ТАР",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_monitoring_tar(
|
||||
file: UploadFile = File(..., description="Excel файл мониторинга ТАР (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Загрузка и обработка отчета мониторинга ТАР"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='monitoring_tar',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = report_service.upload_report(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/monitoring_tar/get_data", tags=[MonitoringTarParser.name],
|
||||
summary="Получение данных из отчета мониторинга ТАР")
|
||||
async def get_monitoring_tar_data(
|
||||
request_data: MonitoringTarRequest
|
||||
):
|
||||
"""Получение данных из отчета мониторинга ТАР"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='monitoring_tar',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/monitoring_tar/get_full_data", tags=[MonitoringTarParser.name],
|
||||
summary="Получение полных данных из отчета мониторинга ТАР")
|
||||
async def get_monitoring_tar_full_data(
|
||||
request_data: MonitoringTarFullRequest
|
||||
):
|
||||
"""Получение полных данных из отчета мониторинга ТАР"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='monitoring_tar',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/monitoring_tar/upload", tags=[MonitoringTarParser.name],
|
||||
summary="Асинхронная загрузка файла отчета мониторинга ТАР",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_monitoring_tar(
|
||||
file: UploadFile = File(..., description="Excel файл мониторинга ТАР (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка отчета мониторинга ТАР"""
|
||||
async_service = get_async_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='monitoring_tar',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке мониторинга ТАР: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
190
python_parser/app/endpoints/oper_spravka_tech_pos.py
Normal file
190
python_parser/app/endpoints/oper_spravka_tech_pos.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
Эндпоинты для оперативной справки техпос
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import OperSpravkaTechPosParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import UploadResponse, UploadErrorResponse
|
||||
from app.schemas.oper_spravka_tech_pos import OperSpravkaTechPosRequest, OperSpravkaTechPosResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для оперативной справки техпос
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/oper_spravka_tech_pos/upload", tags=[OperSpravkaTechPosParser.name],
|
||||
summary="Загрузка файла отчета оперативной справки техпос",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_oper_spravka_tech_pos(
|
||||
file: UploadFile = File(..., description="Excel файл оперативной справки техпос (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Загрузка и обработка отчета оперативной справки техпос"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='oper_spravka_tech_pos',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = report_service.upload_report(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/oper_spravka_tech_pos/get_data", tags=[OperSpravkaTechPosParser.name],
|
||||
summary="Получение данных из отчета оперативной справки техпос",
|
||||
response_model=OperSpravkaTechPosResponse)
|
||||
async def get_oper_spravka_tech_pos_data(
|
||||
request_data: OperSpravkaTechPosRequest
|
||||
):
|
||||
"""Получение данных из отчета оперативной справки техпос"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='oper_spravka_tech_pos',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
result = report_service.get_data(request)
|
||||
|
||||
if result.success:
|
||||
return OperSpravkaTechPosResponse(
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/oper_spravka_tech_pos/upload", tags=[OperSpravkaTechPosParser.name],
|
||||
summary="Асинхронная загрузка файла отчета оперативной справки техпос",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_oper_spravka_tech_pos(
|
||||
file: UploadFile = File(..., description="Excel файл оперативной справки техпос (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка отчета оперативной справки техпос"""
|
||||
async_service = get_async_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='oper_spravka_tech_pos',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке оперативной справки техпос: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
189
python_parser/app/endpoints/statuses_repair_ca.py
Normal file
189
python_parser/app/endpoints/statuses_repair_ca.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Эндпоинты для статусов ремонта СА
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import StatusesRepairCAParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import UploadResponse, UploadErrorResponse
|
||||
from app.schemas.statuses_repair_ca import StatusesRepairCARequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для статусов ремонта СА
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/statuses_repair_ca/upload", tags=[StatusesRepairCAParser.name],
|
||||
summary="Загрузка файла отчета статусов ремонта СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_statuses_repair_ca(
|
||||
file: UploadFile = File(..., description="Excel файл статусов ремонта СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Загрузка и обработка отчета статусов ремонта СА"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='statuses_repair_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = report_service.upload_report(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/statuses_repair_ca/get_data", tags=[StatusesRepairCAParser.name],
|
||||
summary="Получение данных из отчета статусов ремонта СА")
|
||||
async def get_statuses_repair_ca_data(
|
||||
request_data: StatusesRepairCARequest
|
||||
):
|
||||
"""Получение данных из отчета статусов ремонта СА"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='statuses_repair_ca',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/statuses_repair_ca/upload", tags=[StatusesRepairCAParser.name],
|
||||
summary="Асинхронная загрузка файла отчета статусов ремонта СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_statuses_repair_ca(
|
||||
file: UploadFile = File(..., description="Excel файл статусов ремонта СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка отчета статусов ремонта СА"""
|
||||
async_service = get_async_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='statuses_repair_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке статусов ремонта СА: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
226
python_parser/app/endpoints/svodka_ca.py
Normal file
226
python_parser/app/endpoints/svodka_ca.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
Эндпоинты для сводки СА
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import SvodkaCAParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import UploadResponse, UploadErrorResponse, SvodkaCARequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для сводки СА
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
||||
summary="Загрузка файла отчета сводки СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_svodka_ca(
|
||||
file: UploadFile = File(..., description="Excel файл сводки СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Загрузка и обработка отчета сводки СА
|
||||
|
||||
### Поддерживаемые форматы:
|
||||
- **Excel файлы** (.xlsx, .xlsm, .xls)
|
||||
|
||||
### Структура данных:
|
||||
- Обрабатывает Excel файлы с данными по режимам и таблицам
|
||||
- Извлекает данные по указанным режимам (plan, fact, normativ)
|
||||
- Возвращает агрегированные данные по таблицам
|
||||
|
||||
### Пример использования:
|
||||
1. Подготовьте Excel файл сводки СА
|
||||
2. Загрузите файл через этот эндпоинт
|
||||
3. Используйте полученный `object_id` для запросов данных
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Проверяем тип файла
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# Читаем содержимое файла
|
||||
file_content = await file.read()
|
||||
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
# Загружаем отчет
|
||||
result = report_service.upload_report(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/svodka_ca/get_data", tags=[SvodkaCAParser.name],
|
||||
summary="Получение данных из отчета сводки СА")
|
||||
async def get_svodka_ca_data(
|
||||
request_data: SvodkaCARequest
|
||||
):
|
||||
"""Получение данных из отчета сводки СА по указанным режимам и таблицам
|
||||
|
||||
### Структура параметров:
|
||||
- `modes`: **Массив кодов** режимов - `plan`, `fact` или `normativ` (обязательный)
|
||||
- `tables`: **Массив названий** таблиц как есть (обязательный)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"modes": ["plan", "fact"],
|
||||
"tables": ["ТиП, %", "Топливо итого, тонн", "Топливо итого, %", "Потери итого, тонн"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='svodka_ca',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
||||
summary="Асинхронная загрузка файла отчета сводки СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_svodka_ca(
|
||||
file: UploadFile = File(..., description="Excel файл сводки СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка отчета сводки СА"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
# Проверяем тип файла
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# Читаем содержимое файла
|
||||
file_content = await file.read()
|
||||
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке сводки СА: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
319
python_parser/app/endpoints/svodka_pm.py
Normal file
319
python_parser/app/endpoints/svodka_pm.py
Normal file
@@ -0,0 +1,319 @@
|
||||
"""
|
||||
Эндпоинты для сводки ПМ
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import SvodkaPMParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import (
|
||||
UploadResponse, UploadErrorResponse,
|
||||
SvodkaPMTotalOGsRequest, SvodkaPMSingleOGRequest
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для сводки ПМ
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/svodka_pm/upload-zip", tags=[SvodkaPMParser.name],
|
||||
summary="Загрузка файлов сводок ПМ одним ZIP-архивом",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_svodka_pm_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами (.zip)")
|
||||
):
|
||||
"""Загрузка файлов сводок ПМ (факта и плана) по всем ОГ в **одном ZIP-архиве**
|
||||
|
||||
### Поддерживаемые форматы:
|
||||
- **ZIP архивы** с файлами сводок ПМ
|
||||
|
||||
### Структура данных:
|
||||
- Обрабатывает ZIP архивы с файлами по ОГ (svodka_fact_SNPZ.xlsx, svodka_plan_SNPZ.xlsx и т.д.)
|
||||
- Извлекает данные по кодам строк и колонкам
|
||||
- Возвращает агрегированные данные по ОГ
|
||||
|
||||
### Пример использования:
|
||||
1. Подготовьте ZIP архив с файлами сводок ПМ
|
||||
2. Загрузите архив через этот эндпоинт
|
||||
3. Используйте полученный `object_id` для запросов данных
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Проверяем тип файла - только ZIP архивы
|
||||
if not zip_file.filename.endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# Читаем содержимое файла
|
||||
file_content = await zip_file.read()
|
||||
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_pm',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет
|
||||
result = report_service.upload_report(request)
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/svodka_pm/get_single_og", tags=[SvodkaPMParser.name],
|
||||
summary="Получение данных по одному ОГ")
|
||||
async def get_svodka_pm_single_og(
|
||||
request_data: SvodkaPMSingleOGRequest
|
||||
):
|
||||
"""Получение данных из сводок ПМ (факта и плана) по одному ОГ
|
||||
|
||||
### Структура параметров:
|
||||
- `id`: **Идентификатор МА** для запрашиваемого ОГ (обязательный)
|
||||
- `codes`: **Массив кодов** выбираемых строк (обязательный)
|
||||
- `columns`: **Массив названий** выбираемых столбцов (обязательный)
|
||||
- `search`: **Опциональный параметр** для фильтрации ("Итого" или null)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"id": "SNPZ",
|
||||
"codes": [78, 79],
|
||||
"columns": ["ПП", "СЭБ"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request_dict['mode'] = 'single_og'
|
||||
request = DataRequest(
|
||||
report_type='svodka_pm',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/svodka_pm/get_total_ogs", tags=[SvodkaPMParser.name],
|
||||
summary="Получение данных по всем ОГ")
|
||||
async def get_svodka_pm_total_ogs(
|
||||
request_data: SvodkaPMTotalOGsRequest
|
||||
):
|
||||
"""Получение данных из сводок ПМ (факта и плана) по всем ОГ
|
||||
|
||||
### Структура параметров:
|
||||
- `codes`: **Массив кодов** выбираемых строк (обязательный)
|
||||
- `columns`: **Массив названий** выбираемых столбцов (обязательный)
|
||||
- `search`: **Опциональный параметр** для фильтрации ("Итого" или null)
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"codes": [78, 79],
|
||||
"columns": ["ПП", "СЭБ"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request_dict = request_data.model_dump()
|
||||
request_dict['mode'] = 'total_ogs'
|
||||
request = DataRequest(
|
||||
report_type='svodka_pm',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name])
|
||||
async def get_svodka_pm_data(
|
||||
request_data: dict
|
||||
):
|
||||
"""Получение данных из сводок ПМ (факта и плана)
|
||||
|
||||
### Структура параметров:
|
||||
- `indicator_id`: **ID индикатора** для поиска (обязательный)
|
||||
- `code`: **Код строки** для поиска (обязательный)
|
||||
- `search_value`: **Опциональное значение** для поиска
|
||||
|
||||
### Пример тела запроса:
|
||||
```json
|
||||
{
|
||||
"indicator_id": "SNPZ",
|
||||
"code": 78,
|
||||
"search_value": "Итого"
|
||||
}
|
||||
```
|
||||
"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
# Создаем запрос
|
||||
request = DataRequest(
|
||||
report_type='svodka_pm',
|
||||
get_params=request_data
|
||||
)
|
||||
|
||||
# Получаем данные
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/svodka_pm/upload-zip", tags=[SvodkaPMParser.name],
|
||||
summary="Асинхронная загрузка файлов сводок ПМ одним ZIP-архивом",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_svodka_pm_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами (.zip)")
|
||||
):
|
||||
"""Асинхронная загрузка файлов сводок ПМ (факта и плана) по всем ОГ в **одном ZIP-архиве**"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
if not zip_file.filename.endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
file_content = await zip_file.read()
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_pm',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке сводки ПМ: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
189
python_parser/app/endpoints/svodka_repair_ca.py
Normal file
189
python_parser/app/endpoints/svodka_repair_ca.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Эндпоинты для сводки ремонта СА
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter, File, UploadFile, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from adapters.storage import MinIOStorageAdapter
|
||||
from adapters.parsers import SvodkaRepairCAParser
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService
|
||||
from core.async_services import AsyncReportService
|
||||
from app.schemas import UploadResponse, UploadErrorResponse
|
||||
from app.schemas.svodka_repair_ca import SvodkaRepairCARequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для сводки ремонта СА
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
"""Получение экземпляра сервиса отчетов"""
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
"""Получение экземпляра асинхронного сервиса отчетов"""
|
||||
from core.services import ReportService
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
report_service = ReportService(storage_adapter)
|
||||
return AsyncReportService(report_service)
|
||||
|
||||
|
||||
@router.post("/svodka_repair_ca/upload", tags=[SvodkaRepairCAParser.name],
|
||||
summary="Загрузка файла отчета сводки ремонта СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def upload_svodka_repair_ca(
|
||||
file: UploadFile = File(..., description="Excel файл сводки ремонта СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Загрузка и обработка отчета сводки ремонта СА"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='svodka_repair_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = report_service.upload_report(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="ERR_UPLOAD"
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_SERVER_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@router.post("/svodka_repair_ca/get_data", tags=[SvodkaRepairCAParser.name],
|
||||
summary="Получение данных из отчета сводки ремонта СА")
|
||||
async def get_svodka_repair_ca_data(
|
||||
request_data: SvodkaRepairCARequest
|
||||
):
|
||||
"""Получение данных из отчета сводки ремонта СА"""
|
||||
report_service = get_report_service()
|
||||
|
||||
try:
|
||||
request_dict = request_data.model_dump()
|
||||
request = DataRequest(
|
||||
report_type='svodka_repair_ca',
|
||||
get_params=request_dict
|
||||
)
|
||||
|
||||
result = report_service.get_data(request)
|
||||
|
||||
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)}")
|
||||
|
||||
|
||||
@router.post("/async/svodka_repair_ca/upload", tags=[SvodkaRepairCAParser.name],
|
||||
summary="Асинхронная загрузка файла отчета сводки ремонта СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_svodka_repair_ca(
|
||||
file: UploadFile = File(..., description="Excel файл сводки ремонта СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка отчета сводки ремонта СА"""
|
||||
async_service = get_async_report_service()
|
||||
|
||||
try:
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
file_content = await file.read()
|
||||
request = UploadRequest(
|
||||
report_type='svodka_repair_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке сводки ремонта СА: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
21
python_parser/app/endpoints/system.py
Normal file
21
python_parser/app/endpoints/system.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Системные эндпоинты FastAPI (не отображаются в Swagger)
|
||||
"""
|
||||
import logging
|
||||
from fastapi import APIRouter
|
||||
|
||||
from adapters.pconfig import SINGLE_OGS, OG_IDS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создаем роутер для системных эндпоинтов
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/system/ogs", include_in_schema=False)
|
||||
async def get_system_ogs():
|
||||
"""Системный эндпоинт для получения списка ОГ из pconfig"""
|
||||
return {
|
||||
"single_ogs": SINGLE_OGS,
|
||||
"og_ids": OG_IDS
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,29 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ServerInfoResponse(BaseModel):
|
||||
process_id: int = Field(..., description="Идентификатор текущего процесса сервера")
|
||||
parent_id: int = Field(..., description="Идентификатор родительского процесса")
|
||||
cpu_cores: int = Field(..., description="Количество ядер процессора в системе")
|
||||
memory_mb: float = Field(..., description="Общий объем оперативной памяти в мегабайтах")
|
||||
message: str = Field(..., description="Сообщение о сервере")
|
||||
version: str = Field(..., description="Версия API")
|
||||
process_id: Optional[int] = Field(None, description="Идентификатор текущего процесса сервера")
|
||||
parent_id: Optional[int] = Field(None, description="Идентификатор родительского процесса")
|
||||
cpu_cores: Optional[int] = Field(None, description="Количество ядер процессора в системе")
|
||||
memory_mb: Optional[float] = Field(None, description="Общий объем оперативной памяти в мегабайтах")
|
||||
python_version: Optional[str] = Field(None, description="Версия Python")
|
||||
platform: Optional[str] = Field(None, description="Платформа")
|
||||
available_parsers: Optional[list] = Field(None, description="Доступные парсеры")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"message": "Svodka Parser API",
|
||||
"version": "1.0.0",
|
||||
"process_id": 12345,
|
||||
"parent_id": 6789,
|
||||
"cpu_cores": 8,
|
||||
"memory_mb": 16384.5
|
||||
"memory_mb": 16384.5,
|
||||
"python_version": "3.11.0",
|
||||
"platform": "Windows-10-10.0.22631-SP0",
|
||||
"available_parsers": ["svodka_pm", "svodka_ca", "monitoring_fuel"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@@ -17,7 +17,13 @@ def render_sidebar():
|
||||
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")
|
||||
|
||||
# Безопасное форматирование памяти
|
||||
memory_mb = server_info.get('memory_mb')
|
||||
if memory_mb is not None:
|
||||
st.write(f"Память: {memory_mb:.1f} MB")
|
||||
else:
|
||||
st.write("Память: N/A")
|
||||
|
||||
# Доступные парсеры
|
||||
parsers = get_available_parsers()
|
||||
|
||||
Reference in New Issue
Block a user