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.
84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Body
|
|
from pydantic import BaseModel, Field
|
|
from starlette.concurrency import run_in_threadpool
|
|
|
|
from app.auth.keycloak_dependencies import get_current_keycloak_username
|
|
from app.services.burst_detection import (
|
|
run_burst_detection,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class BurstDetectionRequest(BaseModel):
|
|
"""爆管检测请求模型"""
|
|
network: str = Field(..., description="管网名称(或数据库名称)")
|
|
observed_pressure_data: (
|
|
dict[str, list[Any]] | list[dict[str, Any]] | list[list[Any]] | None
|
|
) = Field(
|
|
default=None,
|
|
description=(
|
|
"压力观测数据。支持列式字典 {sensor_id: [values,...]}、"
|
|
"逐时刻对象数组 [{sensor_id: value,...}, ...]、"
|
|
"或二维数组 [[t1_s1, t1_s2], [t2_s1, t2_s2], ...]。"
|
|
),
|
|
)
|
|
points_per_day: int = Field(1440, description="每天的数据点数")
|
|
mu: int = Field(100, description="异常值检测的参数")
|
|
iforest_params: dict[str, Any] | None = Field(None, description="隔离森林算法参数")
|
|
target_time: datetime | None = Field(
|
|
None,
|
|
description="目标侦测时刻;为空时自动使用最近一个完整的监测时刻",
|
|
)
|
|
sampling_interval_minutes: int | None = Field(
|
|
None,
|
|
ge=1,
|
|
le=1440,
|
|
description="采样间隔(分钟);为空时根据压力 SCADA 传输频率自动推断",
|
|
)
|
|
scada_start: datetime | None = Field(None, description="SCADA数据起始时间")
|
|
scada_end: datetime | None = Field(None, description="SCADA数据结束时间")
|
|
sensor_nodes: list[str] | None = Field(None, description="传感器节点列表")
|
|
scheme_name: str | None = Field(None, description="方案名称")
|
|
data_source: str = Field("monitoring", description="数据来源:monitoring(监测)或simulation(模拟)")
|
|
simulation_run_id: UUID | None = Field(None, description="分析模拟运行 ID")
|
|
|
|
|
|
@router.post(
|
|
"/burst-detections",
|
|
summary="执行爆管检测",
|
|
description="基于压力观测数据和其他参数执行爆管检测分析"
|
|
)
|
|
async def detect_burst(
|
|
data: BurstDetectionRequest = Body(..., description="爆管检测请求数据"),
|
|
username: str = Depends(get_current_keycloak_username),
|
|
) -> dict[str, Any]:
|
|
"""
|
|
执行爆管检测分析。
|
|
|
|
使用异常检测算法(隔离森林)识别压力时间序列中的异常,
|
|
将其作为潜在的爆管事件。
|
|
|
|
Args:
|
|
data: 包含管网名称(或数据库名称)、压力数据及相关参数的请求体
|
|
username: 当前认证用户名
|
|
|
|
Returns:
|
|
包含检测结果的字典
|
|
|
|
Raises:
|
|
HTTPException: 当处理过程中发生错误时
|
|
"""
|
|
try:
|
|
return await run_in_threadpool(
|
|
run_burst_detection,
|
|
**data.model_dump(),
|
|
username=username,
|
|
)
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc))
|