5 Commits

Author SHA1 Message Date
fc0b4356da Merge branch 'arch-2' 2025-09-01 20:00:32 +03:00
72fe115a99 main 2025-09-01 19:22:58 +03:00
46a30c32ed Сервисы 2025-09-01 19:20:16 +03:00
5e217c7cce порты 2025-09-01 19:19:28 +03:00
7d2747c8fe rm лишнее 2025-09-01 19:16:23 +03:00
4 changed files with 211 additions and 111 deletions

View File

@@ -1,39 +0,0 @@
# 🧹 Сводка по очистке проекта
## ✅ Что было удалено из `python_parser/`:
### Файлы Streamlit:
- `streamlit_app.py` - основной файл Streamlit приложения
- `run_streamlit.py` - скрипт запуска Streamlit
- `Procfile` - конфигурация для Heroku (Streamlit)
- `runtime.txt` - версия Python для Heroku
- `manifest.yml` - манифест приложения
- `.streamlit/` - папка с конфигурацией Streamlit
### Зависимости:
- Удален `streamlit>=1.28.0` из `python_parser/requirements.txt`
## 🎯 Результат:
### `python_parser/` - теперь содержит ТОЛЬКО:
- FastAPI приложение
- Адаптеры для парсеров
- Основную бизнес-логику
- Dockerfile для FastAPI
- Зависимости только для FastAPI
### `streamlit_app/` - содержит ТОЛЬКО:
- Streamlit приложение
- Dockerfile для Streamlit
- Зависимости только для Streamlit
- Конфигурацию Streamlit
## 🔄 Полное разделение достигнуто:
- **FastAPI** и **Streamlit** теперь полностью независимы
- Каждый сервис имеет свои собственные зависимости
- Docker образы собираются отдельно
- Запускаются через единый `docker-compose.yml`
---
**Статус**: ✅ Проект полностью очищен и разделен

View File

@@ -96,6 +96,54 @@ async def get_available_parsers():
return {"parsers": parsers}
@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,)
@@ -352,40 +400,40 @@ async def get_svodka_pm_total_ogs(
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()
# """
# Получение данных из отчета сводки факта СарНПЗ
@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
# )
- indicator_id: ID индикатора
- code: Код для поиска
- search_value: Опциональное значение для поиска
"""
try:
# Создаем запрос
request = DataRequest(
report_type='svodka_pm',
get_params=request_data
)
# # Получаем данные
# result = report_service.get_data(request)
# Получаем данные
result = report_service.get_data(request)
# if result.success:
# return {
# "success": True,
# "data": result.data
# }
# else:
# raise HTTPException(status_code=404, detail=result.message)
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)}")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
@app.post("/svodka_ca/upload", tags=[SvodkaCAParser.name],
@@ -562,38 +610,38 @@ async def get_svodka_ca_data(
# 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()
# """
# Получение данных из отчета мониторинга топлива
@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
# )
- column: Название колонки для агрегации (normativ, total, total_svod)
"""
try:
# Создаем запрос
request = DataRequest(
report_type='monitoring_fuel',
get_params=request_data
)
# # Получаем данные
# result = report_service.get_data(request)
# Получаем данные
result = report_service.get_data(request)
# if result.success:
# return {
# "success": True,
# "data": result.data
# }
# else:
# raise HTTPException(status_code=404, detail=result.message)
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)}")
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])

View File

@@ -2,28 +2,93 @@
Порты (интерфейсы) для hexagonal architecture
"""
from abc import ABC, abstractmethod
from typing import Optional
from typing import Optional, Dict, List, Any, Callable
import pandas as pd
class ParserPort(ABC):
"""Интерфейс для парсеров"""
"""Интерфейс для парсеров с поддержкой множественных геттеров"""
def __init__(self):
"""Инициализация с пустым словарем геттеров"""
self.getters: Dict[str, Dict[str, Any]] = {}
self._register_default_getters()
def _register_default_getters(self):
"""Регистрация геттеров по умолчанию - переопределяется в наследниках"""
pass
def register_getter(self, name: str, method: Callable, required_params: List[str],
optional_params: List[str] = None, description: str = ""):
"""
Регистрация нового геттера
Args:
name: Имя геттера
method: Метод для выполнения
required_params: Список обязательных параметров
optional_params: Список необязательных параметров
description: Описание геттера
"""
if optional_params is None:
optional_params = []
self.getters[name] = {
"method": method,
"required_params": required_params,
"optional_params": optional_params,
"description": description
}
def get_available_getters(self) -> Dict[str, Dict[str, Any]]:
"""Получение списка доступных геттеров с их описанием"""
return {
name: {
"required_params": info["required_params"],
"optional_params": info["optional_params"],
"description": info["description"]
}
for name, info in self.getters.items()
}
# Добавить схему
def get_value(self, getter_name: str, params: Dict[str, Any]):
"""
Получение значения через указанный геттер
Args:
getter_name: Имя геттера
params: Параметры для геттера
Returns:
Результат выполнения геттера
Raises:
ValueError: Если геттер не найден или параметры неверны
"""
if getter_name not in self.getters:
available = list(self.getters.keys())
raise ValueError(f"Геттер '{getter_name}' не найден. Доступные: {available}")
getter_info = self.getters[getter_name]
required = getter_info["required_params"]
# Проверка обязательных параметров
missing = [p for p in required if p not in params]
if missing:
raise ValueError(f"Отсутствуют обязательные параметры для геттера '{getter_name}': {missing}")
# Вызов метода геттера
try:
return getter_info["method"](params)
except Exception as e:
raise ValueError(f"Ошибка выполнения геттера '{getter_name}': {str(e)}")
@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):
"""Интерфейс для хранилища данных"""

View File

@@ -100,8 +100,34 @@ class ReportService:
# Получаем парсер
parser = get_parser(request.report_type)
# Получаем значение
value = parser.get_value(df, request.get_params)
# Устанавливаем DataFrame в парсер для использования в геттерах
parser.df = df
# Получаем параметры запроса
get_params = request.get_params or {}
# Определяем имя геттера (по умолчанию используем первый доступный)
getter_name = get_params.pop("getter", None)
if not getter_name:
# Если геттер не указан, берем первый доступный
available_getters = list(parser.getters.keys())
if available_getters:
getter_name = available_getters[0]
print(f"⚠️ Геттер не указан, используем первый доступный: {getter_name}")
else:
return DataResult(
success=False,
message="Парсер не имеет доступных геттеров"
)
# Получаем значение через указанный геттер
try:
value = parser.get_value(getter_name, get_params)
except ValueError as e:
return DataResult(
success=False,
message=f"Ошибка параметров: {str(e)}"
)
# Формируем результат
if value is not None: