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

View File

@@ -0,0 +1,45 @@
"""
Доменные модели
"""
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
import pandas as pd
@dataclass
class UploadRequest:
"""Запрос на загрузку отчета"""
report_type: str
file_content: bytes
file_name: str
parse_params: Dict[str, Any] = field(default_factory=dict)
@dataclass
class UploadResult:
"""Результат загрузки отчета"""
success: bool
message: str
object_id: Optional[str] = None
@dataclass
class DataRequest:
"""Запрос на получение данных"""
report_type: str
get_params: Dict[str, Any]
@dataclass
class DataResult:
"""Результат получения данных"""
success: bool
data: Optional[Dict[str, Any]] = None
message: Optional[str] = None
@dataclass
class ParsedData:
"""Распарсенные данные"""
dataframe: pd.DataFrame
metadata: Optional[Dict[str, Any]] = None

View File

@@ -0,0 +1,49 @@
"""
Порты (интерфейсы) для hexagonal architecture
"""
from abc import ABC, abstractmethod
from typing import Optional
import pandas as pd
class ParserPort(ABC):
"""Интерфейс для парсеров"""
@abstractmethod
def parse(self, file_path: str, params: dict) -> pd.DataFrame:
"""Парсинг файла и возврат DataFrame"""
pass
@abstractmethod
def get_value(self, df: pd.DataFrame, params: dict):
"""Получение значения из DataFrame по параметрам"""
pass
# @abstractmethod
# def get_schema(self) -> dict:
# """Возвращает схему входных параметров для парсера"""
# pass
class StoragePort(ABC):
"""Интерфейс для хранилища данных"""
@abstractmethod
def save_dataframe(self, df: pd.DataFrame, object_id: str) -> bool:
"""Сохранение DataFrame"""
pass
@abstractmethod
def load_dataframe(self, object_id: str) -> Optional[pd.DataFrame]:
"""Загрузка DataFrame"""
pass
@abstractmethod
def delete_object(self, object_id: str) -> bool:
"""Удаление объекта"""
pass
@abstractmethod
def object_exists(self, object_id: str) -> bool:
"""Проверка существования объекта"""
pass

View File

@@ -0,0 +1,120 @@
"""
Сервисы (бизнес-логика)
"""
import tempfile
import os
from typing import Dict, Type
from core.models import UploadRequest, UploadResult, DataRequest, DataResult
from core.ports import ParserPort, StoragePort
# Глобальный словарь парсеров
PARSERS: Dict[str, Type[ParserPort]] = {}
def get_parser(report_type: str) -> ParserPort:
"""Получение парсера по типу отчета"""
if report_type not in PARSERS:
available_parsers = list(PARSERS.keys())
raise ValueError(f"Неизвестный тип отчета '{report_type}'. Доступные типы: {available_parsers}")
return PARSERS[report_type]()
class ReportService:
"""Сервис для работы с отчетами (только S3)"""
def __init__(self, storage: StoragePort):
self.storage = storage
def upload_report(self, request: UploadRequest) -> UploadResult:
"""Загрузка отчета"""
try:
# Получаем парсер для данного типа отчета
parser = get_parser(request.report_type)
# Сохраняем файл во временную директорию
suff = "." + str(request.file_name.split('.')[-1])
with tempfile.NamedTemporaryFile(delete=False, suffix=suff) as temp_file:
temp_file.write(request.file_content)
temp_file_path = temp_file.name
try:
# Парсим файл
parse_params = request.parse_params or {}
df = parser.parse(temp_file_path, parse_params)
# Генерируем object_id
object_id = f"nin_excel_data_{request.report_type}"
# Удаляем старый объект, если он существует
if self.storage.object_exists(object_id):
self.storage.delete_object(object_id)
print(f"Старый объект удален: {object_id}")
# Сохраняем в хранилище
if self.storage.save_dataframe(df, object_id):
return UploadResult(
success=True,
message="Отчет успешно загружен",
object_id=object_id
)
else:
return UploadResult(
success=False,
message="Ошибка при сохранении в хранилище"
)
finally:
# Удаляем временный файл
os.unlink(temp_file_path)
except Exception as e:
return UploadResult(
success=False,
message=f"Ошибка при обработке отчета: {str(e)}"
)
def get_data(self, request: DataRequest) -> DataResult:
"""Получение данных из отчета"""
try:
# Генерируем object_id
object_id = f"nin_excel_data_{request.report_type}"
# Проверяем существование объекта
if not self.storage.object_exists(object_id):
return DataResult(
success=False,
message=f"Отчет типа '{request.report_type}' не найден"
)
# Загружаем DataFrame из хранилища
df = self.storage.load_dataframe(object_id)
if df is None:
return DataResult(
success=False,
message="Ошибка при загрузке данных из хранилища"
)
# Получаем парсер
parser = get_parser(request.report_type)
# Получаем значение
value = parser.get_value(df, request.get_params)
# Формируем результат
if value is not None:
if hasattr(value, 'to_dict'):
result_data = dict(value)
else:
result_data = {"value": value}
return DataResult(success=True, data=result_data)
else:
return DataResult(success=False, message="Значение не найдено")
except Exception as e:
return DataResult(
success=False,
message=f"Ошибка при получении данных: {str(e)}"
)