Compare commits
2 Commits
34937ec062
...
9f9adce4f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f9adce4f3 | |||
| 8ee1c816e2 |
@@ -5,8 +5,8 @@ import logging
|
|||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
from core.ports import ParserPort
|
from core.ports import ParserPort
|
||||||
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
from core.schema_utils import register_getter_from_schema, validate_params_with_schema
|
||||||
from app.schemas.monitoring_fuel import MonitoringFuelTotalRequest, MonitoringFuelMonthRequest
|
from app.schemas.monitoring_fuel import MonitoringFuelTotalRequest, MonitoringFuelMonthRequest, MonitoringFuelSeriesRequest
|
||||||
from adapters.pconfig import data_to_json
|
from adapters.pconfig import data_to_json, get_object_by_name
|
||||||
|
|
||||||
# Настройка логгера для модуля
|
# Настройка логгера для модуля
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -36,6 +36,14 @@ class MonitoringFuelParser(ParserPort):
|
|||||||
description="Получение данных за конкретный месяц"
|
description="Получение данных за конкретный месяц"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register_getter_from_schema(
|
||||||
|
parser_instance=self,
|
||||||
|
getter_name="series_by_id_and_columns",
|
||||||
|
method=self._get_series_by_id_and_columns,
|
||||||
|
schema_class=MonitoringFuelSeriesRequest,
|
||||||
|
description="Получение временного ряда по ID и колонкам"
|
||||||
|
)
|
||||||
|
|
||||||
def _get_total_by_columns(self, params: dict):
|
def _get_total_by_columns(self, params: dict):
|
||||||
"""Агрегация данных по колонкам"""
|
"""Агрегация данных по колонкам"""
|
||||||
# Валидируем параметры с помощью схемы Pydantic
|
# Валидируем параметры с помощью схемы Pydantic
|
||||||
@@ -102,6 +110,62 @@ class MonitoringFuelParser(ParserPort):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _get_series_by_id_and_columns(self, params: dict):
|
||||||
|
"""Получение временных рядов по колонкам для всех ID"""
|
||||||
|
# Валидируем параметры с помощью схемы Pydantic
|
||||||
|
validated_params = validate_params_with_schema(params, MonitoringFuelSeriesRequest)
|
||||||
|
|
||||||
|
columns = validated_params["columns"]
|
||||||
|
|
||||||
|
# Проверяем, есть ли данные в data_dict (из парсинга) или в df (из загрузки)
|
||||||
|
if hasattr(self, 'data_dict') and self.data_dict is not None:
|
||||||
|
# Данные из парсинга
|
||||||
|
data_source = self.data_dict
|
||||||
|
elif hasattr(self, 'df') and self.df is not None and not self.df.empty:
|
||||||
|
# Данные из загрузки - преобразуем DataFrame обратно в словарь
|
||||||
|
data_source = self._df_to_data_dict()
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Проверяем, что все колонки существуют хотя бы в одном месяце
|
||||||
|
valid_columns = set()
|
||||||
|
for month_df in data_source.values():
|
||||||
|
valid_columns.update(month_df.columns)
|
||||||
|
|
||||||
|
for col in columns:
|
||||||
|
if col not in valid_columns:
|
||||||
|
raise ValueError(f"Колонка '{col}' не найдена ни в одном месяце")
|
||||||
|
|
||||||
|
# Подготавливаем результат: словарь id → {col: [значения по месяцам]}
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# Обрабатываем месяцы от 01 до 12
|
||||||
|
for month_key in [f"{i:02d}" for i in range(1, 13)]:
|
||||||
|
if month_key not in data_source:
|
||||||
|
logger.warning(f"Месяц '{month_key}' не найден в df_monitorings, пропускаем.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = data_source[month_key]
|
||||||
|
|
||||||
|
for col in columns:
|
||||||
|
if col not in df.columns:
|
||||||
|
continue # Пропускаем, если в этом месяце нет колонки
|
||||||
|
|
||||||
|
for idx, value in df[col].items():
|
||||||
|
if pd.isna(value):
|
||||||
|
continue # Пропускаем NaN
|
||||||
|
|
||||||
|
if idx not in result:
|
||||||
|
result[idx] = {c: [] for c in columns}
|
||||||
|
|
||||||
|
# Добавляем значение в массив для данного ID и колонки
|
||||||
|
if not pd.isna(value) and value != float('inf') and value != float('-inf'):
|
||||||
|
result[idx][col].append(float(value) if isinstance(value, (int, float)) else value)
|
||||||
|
|
||||||
|
# Преобразуем ключи id в строки (для JSON-совместимости)
|
||||||
|
result_str_keys = {str(k): v for k, v in result.items()}
|
||||||
|
return result_str_keys
|
||||||
|
|
||||||
def _df_to_data_dict(self):
|
def _df_to_data_dict(self):
|
||||||
"""Преобразование DataFrame обратно в словарь данных"""
|
"""Преобразование DataFrame обратно в словарь данных"""
|
||||||
if not hasattr(self, 'df') or self.df is None or self.df.empty:
|
if not hasattr(self, 'df') or self.df is None or self.df.empty:
|
||||||
@@ -115,7 +179,12 @@ class MonitoringFuelParser(ParserPort):
|
|||||||
data = row.get('data')
|
data = row.get('data')
|
||||||
|
|
||||||
if month and data is not None:
|
if month and data is not None:
|
||||||
|
# data уже является DataFrame, поэтому используем его напрямую
|
||||||
|
if isinstance(data, pd.DataFrame):
|
||||||
data_dict[month] = data
|
data_dict[month] = data
|
||||||
|
else:
|
||||||
|
# Если data не DataFrame, пропускаем
|
||||||
|
logger.warning(f"Данные за месяц {month} не являются DataFrame, пропускаем")
|
||||||
|
|
||||||
return data_dict
|
return data_dict
|
||||||
|
|
||||||
@@ -231,10 +300,10 @@ class MonitoringFuelParser(ParserPort):
|
|||||||
|
|
||||||
# Проверяем, что колонка 'name' существует
|
# Проверяем, что колонка 'name' существует
|
||||||
if 'name' in df_full.columns:
|
if 'name' in df_full.columns:
|
||||||
# Применяем функцию get_id_by_name к каждой строке в колонке 'name'
|
# Применяем функцию get_object_by_name к каждой строке в колонке 'name'
|
||||||
# df_full['id'] = df_full['name'].apply(get_object_by_name) # This line was removed as per new_code
|
df_full['id'] = df_full['name'].apply(get_object_by_name)
|
||||||
# Временно используем name как id
|
# Удаляем строки, где не удалось найти ID
|
||||||
df_full['id'] = df_full['name']
|
df_full = df_full.dropna(subset=['id'])
|
||||||
else:
|
else:
|
||||||
# Если нет колонки name, создаем id из индекса
|
# Если нет колонки name, создаем id из индекса
|
||||||
df_full['id'] = df_full.index
|
df_full['id'] = df_full.index
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from app.schemas import (
|
|||||||
UploadResponse, UploadErrorResponse,
|
UploadResponse, UploadErrorResponse,
|
||||||
SvodkaPMTotalOGsRequest, SvodkaPMSingleOGRequest,
|
SvodkaPMTotalOGsRequest, SvodkaPMSingleOGRequest,
|
||||||
SvodkaCARequest,
|
SvodkaCARequest,
|
||||||
MonitoringFuelMonthRequest, MonitoringFuelTotalRequest
|
MonitoringFuelMonthRequest, MonitoringFuelTotalRequest, MonitoringFuelSeriesRequest
|
||||||
)
|
)
|
||||||
from app.schemas.oper_spravka_tech_pos import OperSpravkaTechPosRequest, OperSpravkaTechPosResponse
|
from app.schemas.oper_spravka_tech_pos import OperSpravkaTechPosRequest, OperSpravkaTechPosResponse
|
||||||
from app.schemas.svodka_repair_ca import SvodkaRepairCARequest
|
from app.schemas.svodka_repair_ca import SvodkaRepairCARequest
|
||||||
@@ -933,40 +933,6 @@ async def get_statuses_repair_ca_data(
|
|||||||
# raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(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])
|
# @app.post("/monitoring_fuel/upload_directory", tags=[MonitoringFuelParser.name])
|
||||||
# async def upload_monitoring_fuel_directory(
|
# async def upload_monitoring_fuel_directory(
|
||||||
# request_data: dict
|
# request_data: dict
|
||||||
@@ -1195,6 +1161,66 @@ async def get_monitoring_fuel_month_by_code(
|
|||||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(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 ======
|
# ====== MONITORING TAR ENDPOINTS ======
|
||||||
|
|
||||||
@app.post("/monitoring_tar/upload", tags=[MonitoringTarParser.name],
|
@app.post("/monitoring_tar/upload", tags=[MonitoringTarParser.name],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .monitoring_fuel import MonitoringFuelMonthRequest, MonitoringFuelTotalRequest
|
from .monitoring_fuel import MonitoringFuelMonthRequest, MonitoringFuelTotalRequest, MonitoringFuelSeriesRequest
|
||||||
from .svodka_ca import SvodkaCARequest
|
from .svodka_ca import SvodkaCARequest
|
||||||
from .svodka_pm import SvodkaPMSingleOGRequest, SvodkaPMTotalOGsRequest
|
from .svodka_pm import SvodkaPMSingleOGRequest, SvodkaPMTotalOGsRequest
|
||||||
from .server import ServerInfoResponse
|
from .server import ServerInfoResponse
|
||||||
@@ -6,7 +6,7 @@ from .upload import UploadResponse, UploadErrorResponse
|
|||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'MonitoringFuelMonthRequest', 'MonitoringFuelTotalRequest',
|
'MonitoringFuelMonthRequest', 'MonitoringFuelTotalRequest', 'MonitoringFuelSeriesRequest',
|
||||||
'SvodkaCARequest',
|
'SvodkaCARequest',
|
||||||
'SvodkaPMSingleOGRequest', 'SvodkaPMTotalOGsRequest',
|
'SvodkaPMSingleOGRequest', 'SvodkaPMTotalOGsRequest',
|
||||||
'ServerInfoResponse',
|
'ServerInfoResponse',
|
||||||
|
|||||||
@@ -32,3 +32,19 @@ class MonitoringFuelTotalRequest(BaseModel):
|
|||||||
"columns": ["total", "normativ"]
|
"columns": ["total", "normativ"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MonitoringFuelSeriesRequest(BaseModel):
|
||||||
|
columns: List[str] = Field(
|
||||||
|
...,
|
||||||
|
description="Массив названий выбираемых столбцов",
|
||||||
|
example=["total", "normativ"],
|
||||||
|
min_items=1
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"columns": ["total", "normativ"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
UI модуль для мониторинга топлива
|
UI модуль для мониторинга топлива
|
||||||
"""
|
"""
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
from api_client import upload_file_to_api, make_api_request
|
from api_client import upload_file_to_api, make_api_request
|
||||||
from config import FUEL_COLUMNS
|
from config import FUEL_COLUMNS
|
||||||
|
|
||||||
@@ -89,3 +90,73 @@ def render_monitoring_fuel_tab():
|
|||||||
st.json(result)
|
st.json(result)
|
||||||
else:
|
else:
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Новая секция для временных рядов
|
||||||
|
st.subheader("📈 Временные ряды")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Временные ряды по колонкам")
|
||||||
|
|
||||||
|
# Выбор колонок для временного ряда
|
||||||
|
series_columns = st.multiselect(
|
||||||
|
"Выберите столбцы для временного ряда",
|
||||||
|
FUEL_COLUMNS,
|
||||||
|
default=["total", "normativ"],
|
||||||
|
key="fuel_series_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("📊 Получить временные ряды", key="fuel_series_btn"):
|
||||||
|
if series_columns:
|
||||||
|
with st.spinner("Получаю временные ряды..."):
|
||||||
|
data = {
|
||||||
|
"columns": series_columns
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/monitoring_fuel/get_series_by_id_and_columns", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Временные ряды получены")
|
||||||
|
|
||||||
|
# Отображаем данные
|
||||||
|
if result.get('data'):
|
||||||
|
series_data = result['data']
|
||||||
|
|
||||||
|
# Показываем количество найденных ID
|
||||||
|
st.info(f"📊 Найдено {len(series_data)} объектов")
|
||||||
|
|
||||||
|
# Показываем JSON данные
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Данные не найдены")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите столбцы")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("ℹ️ Справка")
|
||||||
|
st.info("""
|
||||||
|
**Временные ряды** показывают изменение значений по месяцам для всех объектов.
|
||||||
|
|
||||||
|
**Формат данных:**
|
||||||
|
- Каждый ID объекта содержит массивы значений по месяцам
|
||||||
|
- Массивы упорядочены по месяцам (01, 02, 03, ..., 12)
|
||||||
|
- Отсутствующие месяцы пропускаются
|
||||||
|
|
||||||
|
**Доступные колонки:**
|
||||||
|
- `total` - общее потребление
|
||||||
|
- `normativ` - нормативное потребление
|
||||||
|
- И другие колонки из загруженных данных
|
||||||
|
|
||||||
|
**Пример результата:**
|
||||||
|
```
|
||||||
|
SNPZ.VISB: {
|
||||||
|
"total": [23.86, 26.51, 19.66, ...],
|
||||||
|
"normativ": [19.46, 19.45, 18.57, ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
""")
|
||||||
Reference in New Issue
Block a user