diff --git a/python_parser/app/endpoints/README.md b/python_parser/app/endpoints/README.md index 50e0c21..2d8614a 100644 --- a/python_parser/app/endpoints/README.md +++ b/python_parser/app/endpoints/README.md @@ -35,18 +35,30 @@ - `POST /monitoring_fuel/get_month_by_code` - данные за месяц - `POST /monitoring_fuel/get_series_by_id_and_columns` - временные ряды -### 📁 `other_parsers.py` -**Эндпоинты для остальных парсеров**: -- **Сводка ремонта СА**: `upload`, `get_data` -- **Статусы ремонта СА**: `upload`, `get_data` -- **Мониторинг ТАР**: `upload`, `get_data`, `get_full_data` -- **Оперативная справка техпос**: `upload`, `get_data` +### 📁 `svodka_repair_ca.py` +**Эндпоинты для сводки ремонта СА**: +- `POST /svodka_repair_ca/upload` - загрузка Excel файла +- `POST /svodka_repair_ca/get_data` - получение данных +- `POST /async/svodka_repair_ca/upload` - асинхронная загрузка -### 📁 `async_endpoints.py` -**Асинхронные эндпоинты**: -- `POST /async/svodka_pm/upload-zip` - асинхронная загрузка сводки ПМ -- `POST /async/svodka_ca/upload` - асинхронная загрузка сводки СА -- `POST /async/monitoring_fuel/upload-zip` - асинхронная загрузка мониторинга топлива +### 📁 `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` - асинхронная загрузка ## Преимущества разделения @@ -95,6 +107,17 @@ app.include_router(new_parser.router) ## Статистика - **Было**: 1 файл на 2000+ строк -- **Стало**: 7 файлов по 100-300 строк каждый -- **Улучшение читаемости**: ~85% -- **Упрощение поддержки**: ~90% \ No newline at end of file +- **Стало**: 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 эндпоинта (синхронные + асинхронные) \ No newline at end of file diff --git a/python_parser/app/endpoints/async_endpoints.py b/python_parser/app/endpoints/async_endpoints.py deleted file mode 100644 index c5f1619..0000000 --- a/python_parser/app/endpoints/async_endpoints.py +++ /dev/null @@ -1,211 +0,0 @@ -""" -Асинхронные эндпоинты FastAPI -""" -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, SvodkaCAParser, MonitoringFuelParser -from core.models import UploadRequest -from core.async_services import AsyncReportService -from app.schemas import UploadResponse, UploadErrorResponse - -logger = logging.getLogger(__name__) - -# Создаем роутер для асинхронных эндпоинтов -router = APIRouter() - - -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("/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() - ) - - -@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() - ) - - -@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() - ) \ No newline at end of file diff --git a/python_parser/app/endpoints/monitoring_fuel.py b/python_parser/app/endpoints/monitoring_fuel.py index 77eb3cd..4690dc3 100644 --- a/python_parser/app/endpoints/monitoring_fuel.py +++ b/python_parser/app/endpoints/monitoring_fuel.py @@ -9,6 +9,7 @@ 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 @@ -26,6 +27,14 @@ def get_report_service() -> ReportService: 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, @@ -253,4 +262,64 @@ async def get_monitoring_fuel_series_by_id_and_columns( except HTTPException: raise except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") \ No newline at end of file + 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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/monitoring_tar.py b/python_parser/app/endpoints/monitoring_tar.py new file mode 100644 index 0000000..fab6e7f --- /dev/null +++ b/python_parser/app/endpoints/monitoring_tar.py @@ -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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/oper_spravka_tech_pos.py b/python_parser/app/endpoints/oper_spravka_tech_pos.py new file mode 100644 index 0000000..2c4543a --- /dev/null +++ b/python_parser/app/endpoints/oper_spravka_tech_pos.py @@ -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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/other_parsers.py b/python_parser/app/endpoints/other_parsers.py deleted file mode 100644 index df5cd40..0000000 --- a/python_parser/app/endpoints/other_parsers.py +++ /dev/null @@ -1,443 +0,0 @@ -""" -Эндпоинты для остальных парсеров (сводка ремонта СА, статусы ремонта СА, мониторинг ТАР, оперативная справка) -""" -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, StatusesRepairCAParser, MonitoringTarParser, OperSpravkaTechPosParser -from core.models import UploadRequest, DataRequest -from core.services import ReportService -from app.schemas import UploadResponse, UploadErrorResponse -from app.schemas.svodka_repair_ca import SvodkaRepairCARequest -from app.schemas.statuses_repair_ca import StatusesRepairCARequest -from app.schemas.monitoring_tar import MonitoringTarRequest, MonitoringTarFullRequest -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) - - -# ====== СВОДКА РЕМОНТА СА ====== - -@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("/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("/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("/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)}") \ No newline at end of file diff --git a/python_parser/app/endpoints/statuses_repair_ca.py b/python_parser/app/endpoints/statuses_repair_ca.py new file mode 100644 index 0000000..15fce7a --- /dev/null +++ b/python_parser/app/endpoints/statuses_repair_ca.py @@ -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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/svodka_ca.py b/python_parser/app/endpoints/svodka_ca.py index cd9f443..d64d234 100644 --- a/python_parser/app/endpoints/svodka_ca.py +++ b/python_parser/app/endpoints/svodka_ca.py @@ -9,6 +9,7 @@ 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__) @@ -23,6 +24,14 @@ def get_report_service() -> ReportService: 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, @@ -148,4 +157,70 @@ async def get_svodka_ca_data( except HTTPException: raise except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") \ No newline at end of file + 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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/svodka_pm.py b/python_parser/app/endpoints/svodka_pm.py index 15232ee..3bc690c 100644 --- a/python_parser/app/endpoints/svodka_pm.py +++ b/python_parser/app/endpoints/svodka_pm.py @@ -9,6 +9,7 @@ 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 @@ -26,6 +27,14 @@ def get_report_service() -> ReportService: 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, @@ -247,4 +256,64 @@ async def get_svodka_pm_data( except HTTPException: raise except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") \ No newline at end of file + 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() + ) \ No newline at end of file diff --git a/python_parser/app/endpoints/svodka_repair_ca.py b/python_parser/app/endpoints/svodka_repair_ca.py new file mode 100644 index 0000000..a72eaac --- /dev/null +++ b/python_parser/app/endpoints/svodka_repair_ca.py @@ -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() + ) \ No newline at end of file diff --git a/python_parser/app/main.py b/python_parser/app/main.py index df531c9..3e97ea3 100644 --- a/python_parser/app/main.py +++ b/python_parser/app/main.py @@ -42,15 +42,21 @@ app = FastAPI( ) # Подключаем роутеры -from app.endpoints import common, system, svodka_pm, svodka_ca, monitoring_fuel, other_parsers, async_endpoints +from app.endpoints import ( + common, system, + svodka_pm, svodka_ca, monitoring_fuel, + svodka_repair_ca, statuses_repair_ca, monitoring_tar, oper_spravka_tech_pos +) app.include_router(common.router) app.include_router(system.router) app.include_router(svodka_pm.router) app.include_router(svodka_ca.router) app.include_router(monitoring_fuel.router) -app.include_router(other_parsers.router) -app.include_router(async_endpoints.router) +app.include_router(svodka_repair_ca.router) +app.include_router(statuses_repair_ca.router) +app.include_router(monitoring_tar.router) +app.include_router(oper_spravka_tech_pos.router) if __name__ == "__main__": diff --git a/streamlit_app/sidebar.py b/streamlit_app/sidebar.py index 31adbec..bf7e692 100644 --- a/streamlit_app/sidebar.py +++ b/streamlit_app/sidebar.py @@ -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()