From 2f459487fecfa7eda2bfcd1e1a021badf10f32ec Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 8 Sep 2025 17:54:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D1=8B=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_parser/app/endpoints/async.py | 211 ---- python_parser/app/main_new.py | 65 - python_parser/app/main_old.py | 1679 -------------------------- streamlit_app/_streamlit_app.py | 100 -- 4 files changed, 2055 deletions(-) delete mode 100644 python_parser/app/endpoints/async.py delete mode 100644 python_parser/app/main_new.py delete mode 100644 python_parser/app/main_old.py delete mode 100644 streamlit_app/_streamlit_app.py diff --git a/python_parser/app/endpoints/async.py b/python_parser/app/endpoints/async.py deleted file mode 100644 index c5f1619..0000000 --- a/python_parser/app/endpoints/async.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/main_new.py b/python_parser/app/main_new.py deleted file mode 100644 index df531c9..0000000 --- a/python_parser/app/main_new.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Главный файл FastAPI приложения -""" -import os -import multiprocessing -import uvicorn -import logging -from fastapi import FastAPI - -# Настройка логирования -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' -) - -# Настройка логгера для модуля -logger = logging.getLogger(__name__) - -# Импортируем парсеры и обновляем PARSERS -from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser, MonitoringTarParser, SvodkaRepairCAParser, StatusesRepairCAParser, OperSpravkaTechPosParser -from core.services import PARSERS - -# Обновляем словарь парсеров -PARSERS.update({ - 'svodka_pm': SvodkaPMParser, - 'svodka_ca': SvodkaCAParser, - 'monitoring_fuel': MonitoringFuelParser, - 'monitoring_tar': MonitoringTarParser, - 'svodka_repair_ca': SvodkaRepairCAParser, - 'statuses_repair_ca': StatusesRepairCAParser, - 'oper_spravka_tech_pos': OperSpravkaTechPosParser, -}) - -# Создаем FastAPI приложение -app = FastAPI( - title="Svodka Parser API", - description="API для парсинга различных типов отчетов", - version="1.0.0", - docs_url="/docs", - redoc_url="/redoc" -) - -# Подключаем роутеры -from app.endpoints import common, system, svodka_pm, svodka_ca, monitoring_fuel, other_parsers, async_endpoints - -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) - - -if __name__ == "__main__": - # Настройка для запуска в продакшене - workers = multiprocessing.cpu_count() - uvicorn.run( - "app.main:app", - host="0.0.0.0", - port=8000, - workers=workers, - reload=False - ) \ No newline at end of file diff --git a/python_parser/app/main_old.py b/python_parser/app/main_old.py deleted file mode 100644 index 2f5a303..0000000 --- a/python_parser/app/main_old.py +++ /dev/null @@ -1,1679 +0,0 @@ -import os -import multiprocessing -import uvicorn -import logging -from typing import Dict, List -from fastapi import FastAPI, File, UploadFile, HTTPException, status -from fastapi.responses import JSONResponse - -# Настройка логирования -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(name)s:%(lineno)d - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' -) - -# Настройка логгера для модуля -logger = logging.getLogger(__name__) - -from adapters.storage import MinIOStorageAdapter -from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParser, MonitoringTarParser, SvodkaRepairCAParser, StatusesRepairCAParser, OperSpravkaTechPosParser -from adapters.pconfig import SINGLE_OGS, OG_IDS - -from core.models import UploadRequest, DataRequest -from core.services import ReportService, PARSERS -from core.async_services import AsyncReportService - -from app.schemas import ( - ServerInfoResponse, - UploadResponse, UploadErrorResponse, - SvodkaPMTotalOGsRequest, SvodkaPMSingleOGRequest, - SvodkaCARequest, - MonitoringFuelMonthRequest, MonitoringFuelTotalRequest, MonitoringFuelSeriesRequest -) -from app.schemas.oper_spravka_tech_pos import OperSpravkaTechPosRequest, OperSpravkaTechPosResponse -from app.schemas.svodka_repair_ca import SvodkaRepairCARequest -from app.schemas.statuses_repair_ca import StatusesRepairCARequest -from app.schemas.monitoring_tar import MonitoringTarRequest, MonitoringTarFullRequest - - -# Парсеры -PARSERS.update({ - 'svodka_pm': SvodkaPMParser, - 'svodka_ca': SvodkaCAParser, - 'monitoring_fuel': MonitoringFuelParser, - 'monitoring_tar': MonitoringTarParser, - 'svodka_repair_ca': SvodkaRepairCAParser, - 'statuses_repair_ca': StatusesRepairCAParser, - 'oper_spravka_tech_pos': OperSpravkaTechPosParser, - # 'svodka_plan_sarnpz': SvodkaPlanSarnpzParser, -}) - -# Адаптеры -storage_adapter = MinIOStorageAdapter() - - -def get_report_service() -> ReportService: - return ReportService(storage_adapter) - - -def get_async_report_service() -> AsyncReportService: - return AsyncReportService(ReportService(storage_adapter)) - - -tags_metadata = [ - { - "name": "Общее", - "display_name": "Общее", - }, - { - "name": SvodkaPMParser.name, - "description": "✅ Ready", - }, - { - "name": SvodkaCAParser.name, - "description": "✅ Ready", - "display_name": "Сводка ПМ", - }, - { - "name": MonitoringFuelParser.name, - "description": "✅ Ready", - "display_name": "Мониторинг топлива", - }, - # { - # "name": MonitoringFuelParser.name, - # "description": "⚠️ WORK IN PROGRESS", - # }, - -] - -app = FastAPI( - title="NIN Excel Parsers API", - description="API для парсинга сводок и работы с данными экселей НиН", - version="1.0.0", - openapi_tags=tags_metadata, -) - - -@app.get("/", tags=["Общее"]) -async def root(): - return {"message": "Svodka Parser API", "version": "1.0.0"} - - -@app.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} - - -@app.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 - from adapters.pconfig import SINGLE_OGS - return {"parser": parser_name, "available_ogs": SINGLE_OGS} - - -@app.get("/parsers/{parser_name}/getters", tags=["Общее"], - summary="Информация о геттерах парсера", - description="Возвращает информацию о доступных геттерах для указанного парсера", - responses={ - 200: { - "content": { - "application/json": { - "example": { - "parser": "svodka_pm", - "getters": { - "single_og": { - "required_params": ["id", "codes", "columns"], - "optional_params": ["search"], - "description": "Получение данных по одному ОГ" - }, - "total_ogs": { - "required_params": ["codes", "columns"], - "optional_params": ["search"], - "description": "Получение данных по всем ОГ" - } - } - } - } - } - }, - 404: { - "description": "Парсер не найден" - } - }) -async def get_parser_getters(parser_name: str): - """Получение информации о геттерах парсера""" - if parser_name not in PARSERS: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Парсер '{parser_name}' не найден" - ) - - parser_class = PARSERS[parser_name] - parser_instance = parser_class() - - getters_info = parser_instance.get_available_getters() - - return { - "parser": parser_name, - "getters": getters_info - } - - -@app.get("/server-info", tags=["Общее"], - summary="Информация о сервере", - response_model=ServerInfoResponse,) -async def server_info(): - return { - "process_id": os.getpid(), - "parent_id": os.getppid(), - "cpu_cores": multiprocessing.cpu_count(), - "memory_mb": os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024. ** 2) - } - - -# @app.get("/svodka_pm/schema", tags=[SvodkaPMParser.name]) -# async def get_svodka_pm_schema(): -# """Получение схемы параметров для парсера сводок ПМ факта и плана""" -# parser = PARSERS['svodka_pm']() -# return parser.get_schema() - - -# @app.get("/svodka_ca/schema", tags=[SvodkaCAParser.name]) -# async def get_svodka_ca_schema(): -# """Получение схемы параметров для парсера сводки СА""" -# parser = PARSERS['svodka_ca']() -# return parser.get_schema() - - -# @app.get("/monitoring_fuel/schema", tags=[MonitoringFuelParser.name]) -# async def get_monitoring_fuel_schema(): -# """Получение схемы параметров для парсера мониторинга топлива""" -# parser = PARSERS['monitoring_fuel']() -# return parser.get_schema() - - -@app.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-архиве** - - **Шаблоны названий файлов:** - - Факт: `svodka_fact_pm_.xlsm` - - План: `svodka_plan_pm_.xlsx` - """ - report_service = get_report_service() - try: - if not zip_file.filename.lower().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() - ) - - -# @app.post("/svodka_pm/upload", tags=[SvodkaPMParser.name]) -# async def upload_svodka_pm( -# file: UploadFile = File(...) -# ): -# report_service = get_report_service() -# """ -# Загрузка отчета сводки факта СарНПЗ - -# - file: Excel файл для загрузки -# """ -# try: -# # Проверяем тип файла -# if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')): -# raise HTTPException( -# status_code=status.HTTP_400_BAD_REQUEST, -# detail="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)" -# ) - -# # Читаем содержимое файла -# file_content = await file.read() - -# # Создаем запрос -# request = UploadRequest( -# report_type='svodka_pm', -# file_content=file_content, -# file_name=file.filename -# ) - -# # Загружаем отчет -# result = report_service.upload_report(request) -# # print(result) -# if result.success: -# return { -# "success": True, -# "message": result.message, -# "object_id": result.object_id -# } -# else: -# raise HTTPException(status_code=500, detail=result.message) - -# except HTTPException: -# raise -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -@app.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() - """ - Получение данных из отчета сводки факта СарНПЗ - - - id: ID ОГ - - codes: коды выбираемых строк [78, 79] - - columns: выбираемые колонки ["БП", "СЭБ"] - - search: "Итого" не обязательный - """ - 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)}") - - -@app.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, 394, 395, 396, 397, 81, 82, 83, 84], - "columns": ["БП", "ПП", "СЭБ"] - } - ``` - """ - report_service = get_report_service() - """ - Получение данных из отчета сводки факта СарНПЗ - - - codes: коды выбираемых строк [78, 79] - - columns: выбираемые колонки ["БП", "СЭБ"] - - search: "Итого" - """ - 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)}") - - -@app.post("/svodka_pm/get_data", tags=[SvodkaPMParser.name]) -async def get_svodka_pm_data( - request_data: dict -): - report_service = get_report_service() - """ - Получение данных из отчета сводки факта СарНПЗ - - - indicator_id: ID индикатора - - code: Код для поиска - - search_value: Опциональное значение для поиска - """ - try: - # Создаем запрос - request = DataRequest( - report_type='svodka_pm', - get_params=request_data - ) - - # Получаем данные - 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)}") - - -@app.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 файла отчета сводки СА - - **Поддерживаемые форматы:** - - 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_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() - ) - - -@app.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)}") - - -@app.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 файл или ZIP архив сводки ремонта СА (.xlsx, .xlsm, .xls, .zip)") -): - """ - Загрузка и обработка Excel файла или ZIP архива отчета сводки ремонта СА - - **Поддерживаемые форматы:** - - Excel (.xlsx, .xlsm, .xls) - - ZIP архив (.zip) - """ - report_service = get_report_service() - - try: - # Проверяем тип файла - if not file.filename.lower().endswith(('.xlsx', '.xlsm', '.xls', '.zip')): - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content=UploadErrorResponse( - message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls) или ZIP архивы (.zip)", - error_code="INVALID_FILE_TYPE", - details={ - "expected_formats": [".xlsx", ".xlsm", ".xls", ".zip"], - "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() - ) - - -@app.post("/svodka_repair_ca/get_data", tags=[SvodkaRepairCAParser.name], - summary="Получение данных из отчета сводки ремонта СА") -async def get_svodka_repair_ca_data( - request_data: SvodkaRepairCARequest -): - """ - Получение данных из отчета сводки ремонта СА - - ### Структура параметров: - - `og_ids`: **Массив ID ОГ** для фильтрации (опциональный) - - `repair_types`: **Массив типов ремонта** - `КР`, `КП`, `ТР` (опциональный) - - `include_planned`: **Включать плановые данные** (по умолчанию true) - - `include_factual`: **Включать фактические данные** (по умолчанию true) - - ### Пример тела запроса: - ```json - { - "og_ids": ["SNPZ", "KNPZ"], - "repair_types": ["КР", "КП"], - "include_planned": true, - "include_factual": true - } - ``` - """ - 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)}") - - -@app.post("/statuses_repair_ca/upload", tags=[StatusesRepairCAParser.name], - summary="Загрузка отчета статусов ремонта СА") -async def upload_statuses_repair_ca( - file: UploadFile = File(...) -): - """ - Загрузка отчета статусов ремонта СА - - ### Поддерживаемые форматы: - - **Excel файлы**: `.xlsx`, `.xlsm`, `.xls` - - **ZIP архивы**: `.zip` (содержащие Excel файлы) - - ### Пример использования: - ```bash - curl -X POST "http://localhost:8000/statuses_repair_ca/upload" \ - -H "accept: application/json" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@statuses_repair_ca.xlsx" - ``` - """ - report_service = get_report_service() - - try: - # Проверяем тип файла - if not file.filename.endswith(('.xlsx', '.xlsm', '.xls', '.zip')): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls) или архивы (.zip)" - ) - - # Читаем содержимое файла - file_content = await file.read() - - # Создаем запрос на загрузку - upload_request = UploadRequest( - report_type='statuses_repair_ca', - file_content=file_content, - file_name=file.filename - ) - - # Загружаем отчет - result = report_service.upload_report(upload_request) - - if result.success: - return UploadResponse( - success=True, - message="Отчет успешно загружен и обработан", - report_id=result.object_id, - filename=file.filename - ).model_dump() - else: - return UploadErrorResponse( - success=False, - message=result.message, - error_code="ERR_UPLOAD", - details=None - ).model_dump() - - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -@app.post("/statuses_repair_ca/get_data", tags=[StatusesRepairCAParser.name], - summary="Получение данных из отчета статусов ремонта СА") -async def get_statuses_repair_ca_data( - request_data: StatusesRepairCARequest -): - """ - Получение данных из отчета статусов ремонта СА - - ### Структура параметров: - - `ids`: **Массив ID ОГ** для фильтрации (опциональный) - - `keys`: **Массив ключей** для извлечения данных (опциональный) - - ### Пример тела запроса: - ```json - { - "ids": ["SNPZ", "KNPZ", "ANHK"], - "keys": [ - ["Дата начала ремонта"], - ["Готовность к КР", "Факт"], - ["Заключение договоров на СМР", "Договор", "%"] - ] - } - ``` - """ - 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)}") - - -# @app.post("/monitoring_fuel/upload", tags=[MonitoringFuelParser.name]) -# async def upload_monitoring_fuel( -# file: UploadFile = File(...), -# directory_path: str = None -# ): -# report_service = get_report_service() -# """ -# Загрузка отчета мониторинга топлива - -# - file: Excel файл для загрузки (или архив с файлами) -# - directory_path: Путь к директории с файлами (опционально) -# """ -# try: -# # Проверяем тип файла -# if not file.filename.endswith(('.xlsx', '.xlsm', '.xls', '.zip')): -# raise HTTPException( -# status_code=status.HTTP_400_BAD_REQUEST, -# detail="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls) или архивы (.zip)" -# ) - -# # Читаем содержимое файла -# file_content = await file.read() - -# # Создаем параметры для парсинга -# parse_params = {} -# if directory_path: -# parse_params['directory_path'] = directory_path - -# # Создаем запрос -# request = UploadRequest( -# report_type='monitoring_fuel', -# file_content=file_content, -# file_name=file.filename, -# parse_params=parse_params -# ) - -# # Загружаем отчет -# result = report_service.upload_report(request) - -# if result.success: -# return { -# "success": True, -# "message": result.message, -# "object_id": result.object_id -# } -# else: -# raise HTTPException(status_code=500, detail=result.message) - -# except HTTPException: -# raise -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name]) -# async def upload_monitoring_fuel_directory( -# request_data: dict -# ): -# report_service = get_report_service() -# """ -# Загрузка отчета мониторинга топлива из директории - -# - directory_path: Путь к директории с файлами monitoring_SNPZ_*.xlsm -# """ -# try: -# import os -# import glob - -# # Извлекаем directory_path из request_data -# directory_path = request_data.get('directory_path') -# if not directory_path: -# raise HTTPException( -# status_code=status.HTTP_400_BAD_REQUEST, -# detail="Параметр 'directory_path' обязателен" -# ) - -# # Проверяем существование директории -# if not os.path.exists(directory_path): -# raise HTTPException( -# status_code=status.HTTP_400_BAD_REQUEST, -# detail=f"Директория не найдена: {directory_path}" -# ) - -# # Проверяем наличие файлов -# file_pattern = os.path.join(directory_path, "monitoring_SNPZ_*.xlsm") -# files = glob.glob(file_pattern) - -# if not files: -# raise HTTPException( -# status_code=status.HTTP_400_BAD_REQUEST, -# detail=f"Не найдены файлы по паттерну {file_pattern}" -# ) - -# # Создаем параметры для парсинга -# parse_params = { -# 'directory_path': directory_path, -# 'sheet_name': 'Мониторинг потребления', -# 'search_value': 'Установка' -# } - -# # Создаем запрос (используем пустой файл, так как парсим директорию) -# request = UploadRequest( -# report_type='monitoring_fuel', -# file_content=b'', # Пустой контент, так как парсим директорию -# file_name='directory_upload', -# parse_params=parse_params -# ) - -# # Загружаем отчет -# result = report_service.upload_report(request) - -# if result.success: -# return { -# "success": True, -# "message": result.message, -# "object_id": result.object_id, -# "files_processed": len(files) -# } -# else: -# raise HTTPException(status_code=500, detail=result.message) - -# except HTTPException: -# raise -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -@app.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-архиве** - - **Шаблоны названий файлов:** - - `monitoring_SNPZ_{MM}.xlsm`, `MM` - номер месяца с ведущим 0 - """ - report_service = get_report_service() - try: - if not zip_file.filename.lower().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() - ) - - -@app.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)}") - - -@app.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)}") - - -@app.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)}") - - -# ====== MONITORING TAR ENDPOINTS ====== - -@app.post("/monitoring_tar/upload", tags=[MonitoringTarParser.name], - summary="Загрузка отчета мониторинга ТЭР") -async def upload_monitoring_tar( - file: UploadFile = File(...) -): - """Загрузка и обработка отчета мониторинга ТЭР (Топливно-энергетических ресурсов) - - ### Поддерживаемые форматы: - - **ZIP архивы** с файлами мониторинга ТЭР - - ### Структура данных: - - Обрабатывает ZIP архивы с файлами по месяцам (svodka_tar_SNPZ_01.xlsx - svodka_tar_SNPZ_12.xlsx) - - Извлекает данные по установкам (SNPZ_IDS) - - Возвращает два типа данных: 'total' (строки "Всего") и 'last_day' (последние строки) - """ - report_service = get_report_service() - - try: - # Проверяем тип файла - только ZIP архивы - if not file.filename.endswith('.zip'): - raise HTTPException( - status_code=400, - detail="Неподдерживаемый тип файла. Ожидается только ZIP архив (.zip)" - ) - - # Читаем содержимое файла - file_content = await file.read() - - # Создаем запрос на загрузку - upload_request = UploadRequest( - report_type='monitoring_tar', - file_content=file_content, - file_name=file.filename - ) - - # Загружаем отчет - result = report_service.upload_report(upload_request) - - if result.success: - return UploadResponse( - success=True, - message="Отчет успешно загружен и обработан", - report_id=result.object_id, - filename=file.filename - ).model_dump() - else: - return UploadErrorResponse( - success=False, - message=result.message, - error_code="ERR_UPLOAD", - details=None - ).model_dump() - - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -@app.post("/monitoring_tar/get_data", tags=[MonitoringTarParser.name], - summary="Получение данных из отчета мониторинга ТЭР") -async def get_monitoring_tar_data( - request_data: MonitoringTarRequest -): - """Получение данных из отчета мониторинга ТЭР - - ### Структура параметров: - - `mode`: **Режим получения данных** (опциональный) - - `"total"` - строки "Всего" (агрегированные данные) - - `"last_day"` - последние строки данных - - Если не указан, возвращаются все данные - - ### Пример тела запроса: - ```json - { - "mode": "total" - } - ``` - """ - 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)}") - - -@app.post("/monitoring_tar/get_full_data", tags=[MonitoringTarParser.name], - summary="Получение всех данных из отчета мониторинга ТЭР") -async def get_monitoring_tar_full_data(): - """Получение всех данных из отчета мониторинга ТЭР без фильтрации - - ### Возвращает: - - Все данные по всем установкам - - И данные 'total', и данные 'last_day' - - Полная структура данных мониторинга ТЭР - """ - report_service = get_report_service() - - try: - # Создаем запрос без параметров - request = DataRequest( - report_type='monitoring_tar', - get_params={} - ) - - # Получаем данные - 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)}") - - -# ====== OPER SPRAVKA TECH POS ENDPOINTS ====== - -@app.post("/oper_spravka_tech_pos/upload", tags=[OperSpravkaTechPosParser.name], - summary="Загрузка отчета операционной справки технологических позиций") -async def upload_oper_spravka_tech_pos( - file: UploadFile = File(...) -): - """Загрузка и обработка отчета операционной справки технологических позиций - - ### Поддерживаемые форматы: - - **ZIP архивы** с файлами операционных справок - - ### Структура данных: - - Обрабатывает ZIP архивы с файлами операционных справок по технологическим позициям - - Извлекает данные по процессам: Первичная переработка, Гидроочистка топлив, Риформирование, Изомеризация - - Возвращает данные по установкам с планом и фактом - """ - report_service = get_report_service() - - try: - # Проверяем тип файла - только ZIP архивы - if not file.filename.endswith('.zip'): - raise HTTPException( - status_code=400, - detail="Неподдерживаемый тип файла. Ожидается только ZIP архив (.zip)" - ) - - # Читаем содержимое файла - file_content = await file.read() - - # Создаем запрос на загрузку - upload_request = UploadRequest( - report_type="oper_spravka_tech_pos", - file_name=file.filename, - file_content=file_content, - parse_params={} - ) - - # Загружаем и обрабатываем отчет - result = report_service.upload_report(upload_request) - - if result.success: - return UploadResponse( - success=True, - message="Отчет успешно загружен и обработан", - object_id=result.object_id - ) - else: - return UploadErrorResponse( - success=False, - message=result.message, - error_code="ERR_UPLOAD", - details=None - ) - - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -@app.post("/oper_spravka_tech_pos/get_data", tags=[OperSpravkaTechPosParser.name], - summary="Получение данных операционной справки технологических позиций", - response_model=OperSpravkaTechPosResponse) -async def get_oper_spravka_tech_pos_data(request: OperSpravkaTechPosRequest): - """Получение данных операционной справки технологических позиций по ОГ - - ### Параметры: - - **id** (str): ID ОГ (например, 'SNPZ', 'KNPZ') - - ### Возвращает: - - Данные по технологическим позициям для указанного ОГ - - Включает информацию о процессах, установках, плане и факте - """ - report_service = get_report_service() - - try: - # Создаем запрос на получение данных - data_request = DataRequest( - report_type="oper_spravka_tech_pos", - get_params={"id": request.id} - ) - - # Получаем данные - result = report_service.get_data(data_request) - - if result.success: - # Извлекаем данные из результата - value_data = result.data.get("value", []) if isinstance(result.data.get("value"), list) else [] - logger.debug(f"🔍 API возвращает данные: {type(value_data)}, длина: {len(value_data) if isinstance(value_data, (list, dict)) else 'N/A'}") - - return OperSpravkaTechPosResponse( - success=True, - data=value_data, - message="Данные успешно получены" - ) - else: - return OperSpravkaTechPosResponse( - success=False, - data=None, - message=result.message - ) - - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") - - -# ============================================================================ -# АСИНХРОННЫЕ ЭНДПОИНТЫ -# ============================================================================ - -@app.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.lower().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() - ) - - -# ====== СИСТЕМНЫЕ ЭНДПОИНТЫ (НЕ ОТОБРАЖАЮТСЯ В SWAGGER) ====== - -@app.get("/system/ogs", include_in_schema=False) -async def get_system_ogs(): - """Системный эндпоинт для получения списка ОГ из pconfig""" - return { - "single_ogs": SINGLE_OGS, - "og_ids": OG_IDS - } - - -@app.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)") -): - """Асинхронная загрузка и обработка Excel файла отчета сводки СА""" - 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_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() - ) - - -@app.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.lower().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() - ) - - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8080) diff --git a/streamlit_app/_streamlit_app.py b/streamlit_app/_streamlit_app.py deleted file mode 100644 index 4f8f90f..0000000 --- a/streamlit_app/_streamlit_app.py +++ /dev/null @@ -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") \ No newline at end of file