This commit is contained in:
2025-08-26 23:33:29 +03:00
commit 3b238ae283
110 changed files with 3837 additions and 0 deletions

View File

828
python_parser/app/main.py Normal file
View 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)

View 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'
]

View 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"]
}
}

View 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
}
}

View 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": ["ТиП, %", "Топливо итого, тонн", "Топливо итого, %", "Потери итого, тонн"]
}
}

View 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": ["БП", "ПП", "СЭБ"]
}
}

View 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"
}
}
}