init
This commit is contained in:
0
python_parser/app/__init__.py
Normal file
0
python_parser/app/__init__.py
Normal file
828
python_parser/app/main.py
Normal file
828
python_parser/app/main.py
Normal file
@@ -0,0 +1,828 @@
|
||||
import os
|
||||
import multiprocessing
|
||||
import uvicorn
|
||||
from typing import Dict, List
|
||||
from fastapi import FastAPI, 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, DataRequest
|
||||
from core.services import ReportService, PARSERS
|
||||
|
||||
from app.schemas import (
|
||||
ServerInfoResponse,
|
||||
UploadResponse, UploadErrorResponse,
|
||||
SvodkaPMTotalOGsRequest, SvodkaPMSingleOGRequest,
|
||||
SvodkaCARequest,
|
||||
MonitoringFuelMonthRequest, MonitoringFuelTotalRequest
|
||||
)
|
||||
|
||||
|
||||
# Парсеры
|
||||
PARSERS.update({
|
||||
'svodka_pm': SvodkaPMParser,
|
||||
'svodka_ca': SvodkaCAParser,
|
||||
'monitoring_fuel': MonitoringFuelParser,
|
||||
# 'svodka_plan_sarnpz': SvodkaPlanSarnpzParser,
|
||||
})
|
||||
|
||||
# Адаптеры
|
||||
storage_adapter = MinIOStorageAdapter()
|
||||
|
||||
|
||||
def get_report_service() -> ReportService:
|
||||
return 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("/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_<OG_ID>.xlsm`
|
||||
- План: `svodka_plan_pm_<OG_ID>.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'
|
||||
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'
|
||||
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("/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/get_data", tags=[MonitoringFuelParser.name])
|
||||
# async def get_monitoring_fuel_data(
|
||||
# request_data: dict
|
||||
# ):
|
||||
# report_service = get_report_service()
|
||||
# """
|
||||
# Получение данных из отчета мониторинга топлива
|
||||
|
||||
# - column: Название колонки для агрегации (normativ, total, total_svod)
|
||||
# """
|
||||
# try:
|
||||
# # Создаем запрос
|
||||
# request = DataRequest(
|
||||
# report_type='monitoring_fuel',
|
||||
# get_params=request_data
|
||||
# )
|
||||
|
||||
# # Получаем данные
|
||||
# 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_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'
|
||||
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'
|
||||
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)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
||||
14
python_parser/app/schemas/__init__.py
Normal file
14
python_parser/app/schemas/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .monitoring_fuel import MonitoringFuelMonthRequest, MonitoringFuelTotalRequest
|
||||
from .svodka_ca import SvodkaCARequest
|
||||
from .svodka_pm import SvodkaPMSingleOGRequest, SvodkaPMTotalOGsRequest
|
||||
from .server import ServerInfoResponse
|
||||
from .upload import UploadResponse, UploadErrorResponse
|
||||
|
||||
|
||||
__all__ = [
|
||||
'MonitoringFuelMonthRequest', 'MonitoringFuelTotalRequest',
|
||||
'SvodkaCARequest',
|
||||
'SvodkaPMSingleOGRequest', 'SvodkaPMTotalOGsRequest',
|
||||
'ServerInfoResponse',
|
||||
'UploadResponse', 'UploadErrorResponse'
|
||||
]
|
||||
34
python_parser/app/schemas/monitoring_fuel.py
Normal file
34
python_parser/app/schemas/monitoring_fuel.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List
|
||||
|
||||
|
||||
class MonitoringFuelMonthRequest(BaseModel):
|
||||
month: str = Field(
|
||||
...,
|
||||
description="Номер месяца строкой с ведущим 0",
|
||||
example="02",
|
||||
pattern="^(0[1-9]|1[0-2])$"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"month": "02"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MonitoringFuelTotalRequest(BaseModel):
|
||||
columns: List[str] = Field(
|
||||
...,
|
||||
description="Массив названий выбираемых столбцов",
|
||||
example=["total", "normativ"],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"columns": ["total", "normativ"]
|
||||
}
|
||||
}
|
||||
18
python_parser/app/schemas/server.py
Normal file
18
python_parser/app/schemas/server.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServerInfoResponse(BaseModel):
|
||||
process_id: int = Field(..., description="Идентификатор текущего процесса сервера")
|
||||
parent_id: int = Field(..., description="Идентификатор родительского процесса")
|
||||
cpu_cores: int = Field(..., description="Количество ядер процессора в системе")
|
||||
memory_mb: float = Field(..., description="Общий объем оперативной памяти в мегабайтах")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"process_id": 12345,
|
||||
"parent_id": 6789,
|
||||
"cpu_cores": 8,
|
||||
"memory_mb": 16384.5
|
||||
}
|
||||
}
|
||||
33
python_parser/app/schemas/svodka_ca.py
Normal file
33
python_parser/app/schemas/svodka_ca.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ReportMode(str, Enum):
|
||||
PLAN = "plan"
|
||||
FACT = "fact"
|
||||
NORMATIV = "normativ"
|
||||
|
||||
|
||||
class SvodkaCARequest(BaseModel):
|
||||
modes: List[ReportMode] = Field(
|
||||
...,
|
||||
description="Массив кодов режимов: plan, fact или normativ",
|
||||
example=["plan", "fact"],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
tables: List[str] = Field(
|
||||
...,
|
||||
description="Массив названий таблиц как есть",
|
||||
example=["ТиП, %", "Топливо итого, тонн", "Топливо итого, %", "Потери итого, тонн"],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"modes": ["plan", "fact"],
|
||||
"tables": ["ТиП, %", "Топливо итого, тонн", "Топливо итого, %", "Потери итого, тонн"]
|
||||
}
|
||||
}
|
||||
91
python_parser/app/schemas/svodka_pm.py
Normal file
91
python_parser/app/schemas/svodka_pm.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from pydantic import Field, BaseModel
|
||||
from typing import Optional, List
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class OGID(str, Enum):
|
||||
"""Доступные идентификаторы ОГ"""
|
||||
KNPZ = "KNPZ"
|
||||
ANHK = "ANHK"
|
||||
AchNPZ = "AchNPZ"
|
||||
BASH = "BASH"
|
||||
UNPZ = "UNPZ"
|
||||
UNH = "UNH"
|
||||
NOV = "NOV"
|
||||
NovKuybNPZ = "NovKuybNPZ"
|
||||
KuybNPZ = "KuybNPZ"
|
||||
CyzNPZ = "CyzNPZ"
|
||||
TuapsNPZ = "TuapsNPZ"
|
||||
SNPZ = "SNPZ"
|
||||
RNPK = "RNPK"
|
||||
NVNPO = "NVNPO"
|
||||
KLNPZ = "KLNPZ"
|
||||
PurNP = "PurNP"
|
||||
YANOS = "YANOS"
|
||||
|
||||
|
||||
class SvodkaPMSingleOGRequest(BaseModel):
|
||||
id: OGID = Field(
|
||||
...,
|
||||
description="Идентификатор МА для запрашиваемого ОГ",
|
||||
example="SNPZ"
|
||||
)
|
||||
|
||||
codes: List[int] = Field(
|
||||
...,
|
||||
description="Массив кодов выбираемых строк",
|
||||
example=[78, 79],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
columns: List[str] = Field(
|
||||
...,
|
||||
description="Массив названий выбираемых столбцов",
|
||||
example=["ПП", "СЭБ"],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
search: Optional[str] = Field(
|
||||
None,
|
||||
description="Опциональный параметр для фильтрации ('Итого' или null)",
|
||||
example="Итого"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"id": "SNPZ",
|
||||
"codes": [78, 79],
|
||||
"columns": ["ПП", "СЭБ"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SvodkaPMTotalOGsRequest(BaseModel):
|
||||
codes: List[int] = Field(
|
||||
...,
|
||||
description="Массив кодов выбираемых строк",
|
||||
example=[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
columns: List[str] = Field(
|
||||
...,
|
||||
description="Массив названий выбираемых столбцов",
|
||||
example=["БП", "ПП", "СЭБ"],
|
||||
min_items=1
|
||||
)
|
||||
|
||||
search: Optional[str] = Field(
|
||||
None,
|
||||
description="Опциональный параметр для фильтрации ('Итого' или null)",
|
||||
example="Итого"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"codes": [78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
||||
"columns": ["БП", "ПП", "СЭБ"]
|
||||
}
|
||||
}
|
||||
44
python_parser/app/schemas/upload.py
Normal file
44
python_parser/app/schemas/upload.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class UploadStatus(str, Enum):
|
||||
SUCCESS = "success"
|
||||
PROCESSING = "processing"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class UploadResponse(BaseModel):
|
||||
success: bool = Field(..., description="Успешность операции")
|
||||
message: str = Field(..., description="Сообщение о результате операции")
|
||||
object_id: Optional[str] = Field(None, description="Идентификатор загруженного объекта")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Файл успешно загружен и обработан",
|
||||
"object_id": "65a1b2c3d4e5f6a7b8c9d0e1",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UploadErrorResponse(BaseModel):
|
||||
success: bool = Field(False, description="Успешность операции")
|
||||
message: str = Field(..., description="Сообщение об ошибке")
|
||||
error_code: Optional[str] = Field(None, description="Код ошибки")
|
||||
details: Optional[Dict[str, Any]] = Field(None, description="Детали ошибки")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": False,
|
||||
"message": "Неверный формат файла",
|
||||
"error_code": "INVALID_FILE_FORMAT",
|
||||
"details": {
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": ".rar"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user