Reorganize algorithm packages by business responsibility, move orchestration into services, and keep database access behind pooled repositories. Harden analysis API validation, remove unsafe legacy simulation endpoints, and add regression and architecture boundary coverage. BREAKING CHANGE: legacy algorithm module paths and obsolete simulation endpoints are removed.
258 lines
9.7 KiB
Python
258 lines
9.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from datetime import datetime
|
||
from psycopg import AsyncConnection
|
||
from uuid import UUID
|
||
|
||
from app.services.timeseries_analysis import TimeseriesAnalysisService
|
||
from .dependencies import get_timescale_connection, get_postgres_connection
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/timeseries/views/scada-simulations", summary="获取SCADA关联的模拟数据")
|
||
async def get_scada_associated_simulation_data(
|
||
start_time: datetime = Query(..., description="查询开始时间"),
|
||
end_time: datetime = Query(..., description="查询结束时间"),
|
||
device_ids: str = Query(..., description="SCADA设备ID列表,逗号分隔"),
|
||
run_id: UUID | None = Query(None, description="分析运行 ID;为空时查询实时数据"),
|
||
timescale_conn: AsyncConnection = Depends(get_timescale_connection),
|
||
postgres_conn: AsyncConnection = Depends(get_postgres_connection),
|
||
):
|
||
"""
|
||
获取SCADA关联的link/node模拟值
|
||
|
||
根据传入的SCADA device_ids,找到关联的link/node,
|
||
并根据对应的type,查询对应的模拟数据。支持查询实时或分析运行数据。
|
||
|
||
Args:
|
||
start_time: 查询开始时间
|
||
end_time: 查询结束时间
|
||
device_ids: SCADA设备ID列表,用逗号分隔
|
||
run_id: 分析运行 ID,若为空则查询实时数据
|
||
timescale_conn: TimescaleDB连接
|
||
postgres_conn: PostgreSQL连接
|
||
|
||
Returns:
|
||
SCADA关联的模拟数据
|
||
|
||
Raises:
|
||
HTTPException: 当查询参数无效时返回400错误,未找到数据时返回404错误
|
||
"""
|
||
try:
|
||
device_ids_list = (
|
||
[id.strip() for id in device_ids.split(",") if id.strip()]
|
||
if device_ids
|
||
else []
|
||
)
|
||
|
||
if run_id is not None:
|
||
result = await TimeseriesAnalysisService.get_scada_associated_analysis_simulation_data(
|
||
timescale_conn,
|
||
postgres_conn,
|
||
device_ids_list,
|
||
start_time,
|
||
end_time,
|
||
run_id,
|
||
)
|
||
else:
|
||
result = (
|
||
await TimeseriesAnalysisService.get_scada_associated_realtime_simulation_data(
|
||
timescale_conn,
|
||
postgres_conn,
|
||
device_ids_list,
|
||
start_time,
|
||
end_time,
|
||
)
|
||
)
|
||
if result is None:
|
||
raise HTTPException(status_code=404, detail="No simulation data found")
|
||
return result
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.get("/timeseries/views/element-simulations", summary="获取管网元素的模拟数据")
|
||
async def get_feature_simulation_data(
|
||
start_time: datetime = Query(..., description="查询开始时间"),
|
||
end_time: datetime = Query(..., description="查询结束时间"),
|
||
feature_infos: str = Query(
|
||
..., description="特征信息,格式: id1:type1,id2:type2,type为pipe(管道)或junction(节点)"
|
||
),
|
||
run_id: UUID | None = Query(None, description="分析运行 ID;为空时查询实时数据"),
|
||
timescale_conn: AsyncConnection = Depends(get_timescale_connection),
|
||
):
|
||
"""
|
||
获取link/node模拟值
|
||
|
||
根据传入的featureInfos,找到关联的link/node,
|
||
并根据对应的type,查询对应的模拟数据。支持查询实时或分析运行数据。
|
||
|
||
Args:
|
||
start_time: 查询开始时间
|
||
end_time: 查询结束时间
|
||
feature_infos: 格式为 "element_id1:type1,element_id2:type2"
|
||
例如: "P1:pipe,J1:junction"
|
||
run_id: 分析运行 ID,若为空则查询实时数据
|
||
timescale_conn: TimescaleDB连接
|
||
|
||
Returns:
|
||
管网元素的模拟数据
|
||
|
||
Raises:
|
||
HTTPException: 当feature_infos为空返回400错误,未找到数据返回404错误,其他错误返回400错误
|
||
"""
|
||
try:
|
||
feature_infos_list = []
|
||
if feature_infos:
|
||
for item in feature_infos.split(","):
|
||
item = item.strip()
|
||
if ":" in item:
|
||
element_id, element_type = item.split(":", 1)
|
||
feature_infos_list.append(
|
||
(element_id.strip(), element_type.strip())
|
||
)
|
||
|
||
if not feature_infos_list:
|
||
raise HTTPException(status_code=400, detail="feature_infos cannot be empty")
|
||
|
||
if run_id is not None:
|
||
result = await TimeseriesAnalysisService.get_analysis_simulation_data(
|
||
timescale_conn,
|
||
feature_infos_list,
|
||
start_time,
|
||
end_time,
|
||
run_id,
|
||
)
|
||
else:
|
||
result = await TimeseriesAnalysisService.get_realtime_simulation_data(
|
||
timescale_conn,
|
||
feature_infos_list,
|
||
start_time,
|
||
end_time,
|
||
)
|
||
|
||
if result is None:
|
||
raise HTTPException(status_code=404, detail="No simulation data found")
|
||
return result
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.get("/timeseries/views/element-scada-readings", summary="获取管网元素关联的SCADA监测数据")
|
||
async def get_element_associated_scada_data(
|
||
element_id: str = Query(..., description="管网元素ID(管道或节点)"),
|
||
start_time: datetime = Query(..., description="查询开始时间"),
|
||
end_time: datetime = Query(..., description="查询结束时间"),
|
||
use_cleaned: bool = Query(False, description="是否使用清洗后的数据"),
|
||
timescale_conn: AsyncConnection = Depends(get_timescale_connection),
|
||
postgres_conn: AsyncConnection = Depends(get_postgres_connection),
|
||
):
|
||
"""
|
||
获取link/node关联的SCADA监测值
|
||
|
||
根据传入的link/node id,匹配SCADA信息,
|
||
如果存在关联的SCADA device_id,获取实际的监测数据。
|
||
|
||
Args:
|
||
element_id: 管网元素ID
|
||
start_time: 查询开始时间
|
||
end_time: 查询结束时间
|
||
use_cleaned: 是否使用清洗后的数据,默认为False使用原始数据
|
||
timescale_conn: TimescaleDB连接
|
||
postgres_conn: PostgreSQL连接
|
||
|
||
Returns:
|
||
管网元素关联的SCADA监测数据
|
||
|
||
Raises:
|
||
HTTPException: 当查询参数无效时返回400错误,未找到关联数据返回404错误
|
||
"""
|
||
try:
|
||
result = await TimeseriesAnalysisService.get_element_associated_scada_data(
|
||
timescale_conn, postgres_conn, element_id, start_time, end_time, use_cleaned
|
||
)
|
||
if result is None:
|
||
raise HTTPException(
|
||
status_code=404, detail="No associated SCADA data found"
|
||
)
|
||
return result
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.post("/timeseries/scada-cleaning-runs", summary="清洗SCADA监测数据")
|
||
async def clean_scada_data(
|
||
device_ids: str = Query(..., description="设备ID列表或 'all' 表示清洗所有设备"),
|
||
start_time: datetime = Query(..., description="清洗数据的开始时间"),
|
||
end_time: datetime = Query(..., description="清洗数据的结束时间"),
|
||
timescale_conn: AsyncConnection = Depends(get_timescale_connection),
|
||
postgres_conn: AsyncConnection = Depends(get_postgres_connection),
|
||
):
|
||
"""
|
||
清洗SCADA监测数据
|
||
|
||
根据device_ids查询monitored_value,清洗后更新cleaned_value。
|
||
支持清洗指定设备或所有设备的数据。
|
||
|
||
Args:
|
||
device_ids: 设备ID列表,用逗号分隔,或 'all' 表示清洗所有设备
|
||
start_time: 清洗数据的开始时间
|
||
end_time: 清洗数据的结束时间
|
||
timescale_conn: TimescaleDB连接
|
||
postgres_conn: PostgreSQL连接
|
||
|
||
Returns:
|
||
清洗结果信息
|
||
|
||
Raises:
|
||
HTTPException: 当清洗过程出现错误时返回400错误
|
||
"""
|
||
try:
|
||
if device_ids == "all":
|
||
device_ids_list = []
|
||
else:
|
||
device_ids_list = (
|
||
[id.strip() for id in device_ids.split(",") if id.strip()]
|
||
if device_ids
|
||
else []
|
||
)
|
||
return await TimeseriesAnalysisService.clean_scada_data(
|
||
timescale_conn, postgres_conn, device_ids_list, start_time, end_time
|
||
)
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.get("/pipeline-health-predictions", summary="预测管道健康状况")
|
||
async def predict_pipeline_health(
|
||
query_time: datetime = Query(..., description="查询时间"),
|
||
timescale_conn: AsyncConnection = Depends(get_timescale_connection),
|
||
postgres_conn: AsyncConnection = Depends(get_postgres_connection),
|
||
):
|
||
"""
|
||
预测管道健康状况
|
||
|
||
根据管网名称和当前时间,查询管道信息和实时数据,
|
||
使用随机生存森林模型预测管道的生存概率。
|
||
|
||
Args:
|
||
query_time: 查询时间
|
||
timescale_conn: TimescaleDB连接
|
||
|
||
Returns:
|
||
预测结果列表,每个元素包含 link_id 和对应的生存函数
|
||
|
||
Raises:
|
||
HTTPException: 当模型文件不存在返回404错误,其他错误返回400或500错误
|
||
"""
|
||
try:
|
||
return await TimeseriesAnalysisService.predict_pipeline_health(
|
||
timescale_conn, postgres_conn, query_time
|
||
)
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
except FileNotFoundError as e:
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"内部服务器错误: {str(e)}")
|