Files
TJWaterServerBinary/app/api/v1/endpoints/leakage.py
jiang 5966d039de refactor(backend)!: separate algorithm and data layers
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.
2026-09-04 17:30:55 +08:00

88 lines
3.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from typing import Any
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Body
from pydantic import BaseModel, ConfigDict, Field
from starlette.concurrency import run_in_threadpool
from app.auth.keycloak_dependencies import get_current_keycloak_username
from app.services.dma_leakage_estimation import (
run_leakage_identification,
)
router = APIRouter()
DEFAULT_N_WORKERS = max(1, min((os.cpu_count() or 1) - 1, 4))
MAX_POPULATION_SIZE = 1_000
MAX_GENERATIONS = 1_000
MAX_DURATION_HOURS = 168
class LeakageIdentifyRequest(BaseModel):
"""漏损识别请求模型"""
model_config = ConfigDict(extra="forbid")
network: str = Field(..., description="管网名称(或数据库名称)")
observed_pressure_data: dict[str, list[Any]] | list[dict[str, Any]] | None = (
Field(None, description="观测的压力数据;文件路径不属于公共 API 输入")
)
start_time: float = Field(0, ge=0, description="起始时间(小时)")
duration: float = Field(
24, gt=0, le=MAX_DURATION_HOURS, description="持续时间(小时)"
)
timestep: float = Field(5, gt=0, le=1440, description="时间步长(分钟)")
q_sum: float = Field(0.2, ge=0, description="总流量(m3/s")
q_sum_unit: str = Field("m3/s", description="流量单位")
pop_size: int = Field(
50, ge=2, le=MAX_POPULATION_SIZE, description="种群大小"
)
max_gen: int = Field(100, ge=1, le=MAX_GENERATIONS, description="最大代数")
n_workers: int = Field(
DEFAULT_N_WORKERS,
ge=1,
le=DEFAULT_N_WORKERS,
description="工作进程数",
)
output_flow_unit: str = Field("m3/s", description="输出流量单位")
dma_count: int | None = Field(None, ge=1, description="DMA区域数量")
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="方案名称")
@router.post(
"/leakage-identifications",
summary="执行漏损识别",
description="基于压力观测数据和遗传算法识别管网中的漏损位置和大小"
)
async def identify_leakage(
data: LeakageIdentifyRequest = 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_leakage_identification,
**data.model_dump(),
username=username,
)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc))