from typing import Any, List, Optional from datetime import datetime, timedelta import json import threading from fastapi import APIRouter, Depends, HTTPException, Query, Path, Body from fastapi.responses import PlainTextResponse from app.auth.keycloak_dependencies import get_current_keycloak_username import app.services.simulation as simulation import app.services.globals as globals from app.services.tjnetwork import ( run_project, run_project_return_dict, run_inp, dump_output, ) from app.algorithms.simulation.scenarios import ( burst_analysis, valve_close_analysis, flushing_analysis, contaminant_simulation, age_analysis, # scheduling_analysis, pressure_regulation, ) from app.algorithms.sensor import ( pressure_sensor_placement_sensitivity, pressure_sensor_placement_kmeans, ) from app.services.simulation_ops import ( project_management, scheduling_simulation, daily_scheduling_simulation, ) from app.services.valve_isolation import analyze_valve_isolation from app.services.time_api import ( parse_aware_time, parse_clock_duration_seconds, parse_utc_time, ) from pydantic import BaseModel, Field, field_validator router = APIRouter() class RunSimulationManuallyByDate(BaseModel): name: str = Field(..., description="管网名称(或数据库名称)") start_time: str = Field(..., description="开始时间 (ISO 8601 / RFC3339,必须显式带时区)") duration: int = Field(..., gt=0, description="持续时间 (分钟)") @field_validator("start_time") @classmethod def validate_start_time_timezone(cls, value: str) -> str: parse_aware_time(value, field_name="start_time") return value class BurstAnalysis(BaseModel): name: str = Field(..., description="管网名称(或数据库名称)") modify_pattern_start_time: str = Field(..., description="模式修改开始时间 (ISO 8601)") burst_ID: List[str] | str | None = Field(None, description="爆管节点/管段ID列表") burst_size: List[float] | float | int | None = Field(None, description="爆管流量大小") modify_total_duration: int = Field(900, description="模拟总时长 (秒)") modify_fixed_pump_pattern: Optional[dict[str, list]] = Field(None, description="定速泵模式修改") modify_variable_pump_pattern: Optional[dict[str, list]] = Field(None, description="变速泵模式修改") modify_valve_opening: Optional[dict[str, float]] = Field(None, description="阀门开度修改") scheme_name: Optional[str] = Field(None, description="方案名称") class SchedulingAnalysis(BaseModel): network: str = Field(..., description="管网名称(或数据库名称)") start_time: str = Field(..., description="开始时间") pump_control: dict = Field(..., description="泵控制策略") tank_id: str = Field(..., description="水箱ID") water_plant_output_id: str = Field(..., description="水厂出水ID") time_delta: Optional[int] = Field(300, description="时间步长 (秒)") class PressureRegulation(BaseModel): network: str = Field(..., description="管网名称(或数据库名称)") start_time: str = Field(..., description="开始时间") pump_control: dict = Field(..., description="泵控制策略") tank_init_level: Optional[dict] = Field(None, description="水箱初始水位") duration: Optional[int] = Field(900, description="持续时间 (秒)") scheme_name: Optional[str] = Field(None, description="方案名称") class ProjectManagement(BaseModel): network: str = Field(..., description="管网名称(或数据库名称)") start_time: str = Field(..., description="开始时间") pump_control: dict = Field(..., description="泵控制策略") tank_init_level: Optional[dict] = Field(None, description="水箱初始水位") region_demand: Optional[dict] = Field(None, description="区域需水量控制") class DailySchedulingAnalysis(BaseModel): network: str = Field(..., description="管网名称(或数据库名称)") start_time: str = Field(..., description="开始时间") pump_control: dict = Field(..., description="泵控制策略") reservoir_id: str = Field(..., description="水库ID") tank_id: str = Field(..., description="水箱ID") water_plant_output_id: str = Field(..., description="水厂出水ID") time_delta: Optional[int] = Field(300, description="时间步长 (秒)") class PumpFailureState(BaseModel): time: str = Field(..., description="故障发生时间") pump_status: dict = Field(..., description="泵状态字典") class PressureSensorPlacement(BaseModel): name: str = Field(..., description="管网名称(或数据库名称)") scheme_name: str = Field(..., description="方案名称") sensor_number: int = Field(..., description="传感器数量") min_diameter: int = Field(0, description="最小管径限制") username: str = Field(..., description="用户名") def run_simulation_manually_by_date( network_name: str, start_time: datetime, duration: int ) -> None: end_datetime = start_time + timedelta(minutes=duration) time_properties = simulation.get_time(network_name) hydraulic_step_seconds = parse_clock_duration_seconds( time_properties["HYDRAULIC TIMESTEP"], field_name="HYDRAULIC TIMESTEP", ) if hydraulic_step_seconds <= 0: raise ValueError("HYDRAULIC TIMESTEP must be greater than 0.") hydraulic_step = timedelta(seconds=hydraulic_step_seconds) current_time = start_time while current_time < end_datetime: simulation.run_simulation( name=network_name, simulation_type="realtime", modify_pattern_start_time=current_time.isoformat(timespec="seconds"), ) current_time += hydraulic_step # 必须用这个PlainTextResponse,不然每个key都有引号 @router.post("/project-runs", response_class=PlainTextResponse, summary="运行项目模拟", description="基于指定的管网项目运行标准水力模拟,返回纯文本格式的模拟报告。") async def run_project_endpoint(network: str = Query(..., description="管网名称(或数据库名称)")) -> str: """ 运行项目模拟 - **network**: 管网名称(或数据库名称) 运行指定管网项目的标准水力模拟并返回文本报告。 """ return run_project(network) # DingZQ, 2025-02-04, 返回dict[str, Any] # output 和 report # output 是 json # report 是 text @router.post("/project-return-dict-runs", summary="运行项目模拟(返回字典)", description="基于指定的管网项目运行标准水力模拟,返回JSON格式的字典,包含输出数据和报告文本。") async def run_project_return_dict_endpoint(network: str = Query(..., description="管网名称(或数据库名称)")) -> dict[str, Any]: """ 运行项目模拟(返回字典) - **network**: 管网名称(或数据库名称) 返回字典包含: - output: JSON格式的模拟输出数据 - report: 文本格式的模拟报告 运行指定管网项目的标准水力模拟并返回字典结果。 """ return run_project_return_dict(network) # put in inp folder, name without extension @router.post("/inp-runs", summary="运行INP文件", description="运行指定INP文件格式的管网模型进行水力模拟。INP文件应该放在inp文件夹中,参数为文件名不含扩展名。") async def run_inp_endpoint(network: str = Query(..., description="inp文件名(不含扩展名)")) -> str: """ 运行INP文件 - **network**: inp文件名(不含扩展名) 从inp文件夹中读取指定的INP文件并运行模拟。 """ return run_inp(network) # path is absolute path @router.get("/outputs", summary="导出模拟输出", description="导出指定路径的模拟输出文件内容。参数应为绝对路径。") async def dump_output_endpoint(output: str = Query(..., description="模拟输出文件的绝对路径")) -> str: """ 导出模拟输出 - **output**: 模拟输出文件的绝对路径 读取并返回指定路径的模拟输出内容。 """ return dump_output(output) # Analysis Endpoints @router.post("/burst-analyses", summary="爆管分析(高级)", description="高级版本的爆管分析,支持在指定时间点修改泵控制模式和阀门开度,以分析这些改变对爆管影响的作用。支持固定泵和变速泵的独立控制。") async def fastapi_burst_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), modify_pattern_start_time: str = Query(..., description="模式修改开始时间(ISO 8601格式)"), burst_ID: list[str] = Query(..., description="爆管节点/管段ID列表"), burst_size: list[float] = Query(..., description="对应各爆管点的爆管流量大小列表(L/s)"), modify_total_duration: int = Query(..., description="模拟总时长(秒)"), scheme_name: str = Query(..., description="分析方案名称"), username: str = Depends(get_current_keycloak_username), ) -> str: """ 爆管分析(高级版本) - **network**: 管网名称(或数据库名称) - **modify_pattern_start_time**: 模式修改开始时间 - **burst_ID**: 爆管节点/管段ID列表 - **burst_size**: 爆管流量大小列表(与burst_ID对应) - **modify_total_duration**: 模拟总时长(秒) - **scheme_name**: 分析方案名称 支持在指定时间修改泵控制模式和阀门开度。 """ burst_analysis( name=network, modify_pattern_start_time=modify_pattern_start_time, burst_ID=burst_ID, burst_size=burst_size, modify_total_duration=modify_total_duration, scheme_name=scheme_name, username=username, ) return "success" @router.post("/valve-closure-analyses", response_class=PlainTextResponse, summary="阀门关闭分析(高级)", description="高级版本的阀门关闭分析,支持同时关闭多个阀门,并在指定持续时间内进行模拟。返回纯文本格式的分析结果。") async def fastapi_valve_close_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="阀门关闭开始时间(ISO 8601格式)"), valves: List[str] = Query(..., description="要关闭的阀门ID列表"), duration: int | None = Query(None, description="模拟持续时间(秒),默认900秒"), scheme_name: str = Query(..., description="阀门关闭方案名称"), ) -> str: """ 阀门关闭分析(高级版本) - **network**: 管网名称(或数据库名称) - **start_time**: 阀门关闭开始时间 - **valves**: 要关闭的阀门ID列表 - **duration**: 模拟持续时间(秒,可选,默认900) - **scheme_name**: 阀门关闭方案名称 支持同时关闭多个阀门进行分析。 """ result = valve_close_analysis( name=network, modify_pattern_start_time=start_time, modify_total_duration=duration or 900, modify_valve_opening={valve_id: 0.0 for valve_id in valves}, scheme_name=scheme_name, ) return result or "success" @router.post("/valve-isolation-analyses", summary="阀门隔离分析", description="分析当发生突发事件时,通过关闭指定阀门进行隔离,确定哪些阀门必须关闭、哪些可选关闭,以及隔离的可行性。") async def valve_isolation_endpoint( network: str = Query(..., description="管网名称(或数据库名称)"), accident_element: List[str] = Query(..., description="发生事故的管段/节点ID列表"), disabled_valves: List[str] = Query(None, description="已故障的阀门ID列表(可选)"), ): """ 阀门隔离分析 - **network**: 管网名称(或数据库名称) - **accident_element**: 发生事故的管段/节点ID列表 - **disabled_valves**: 已故障的阀门ID列表(可选) 返回隔离方案,包括: - must_close_valves: 必须关闭的阀门列表 - optional_valves: 可选关闭的阀门列表 - affected_nodes: 受影响的节点列表;不可隔离时为空列表 - affected_node_count: 受影响的节点总数 - isolatable: 是否可以有效隔离 """ # result = { # "accident_element": "P461309", # "accident_elements": ["P461309"], # "affected_nodes": [ # "J316629_A", # "J317037_B", # "J317060_B", # "J408189_B", # "J499996", # "J524940", # "J535933", # "J58841", # ], # "isolatable": True, # "must_close_valves": ["210521658", "V12974", "V12986", "V12993"], # "optional_valves": [], # } result = analyze_valve_isolation(network, accident_element, disabled_valves) return result @router.post("/flushing-analyses", response_class=PlainTextResponse, summary="冲洗分析(高级)", description="高级版本的冲洗分析,支持同时开启多个阀门进行冲洗,指定排污节点,并设置固定的冲洗流量。返回纯文本格式的分析结果。") async def fastapi_flushing_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="冲洗开始时间(ISO 8601格式)"), valves: List[str] = Query(..., description="要开启的阀门ID列表"), valves_k: List[float] = Query(..., description="对应各阀门的开度列表(0-1)"), drainage_node_ID: str = Query(..., description="排污节点ID"), flush_flow: float = Query(0, description="冲洗流量(L/s),0表示自动计算"), duration: int | None = Query(None, description="模拟持续时间(秒),默认900秒"), scheme_name: str = Query(..., description="冲洗方案名称"), username: str = Depends(get_current_keycloak_username), ) -> str: """ 冲洗分析(高级版本) - **network**: 管网名称(或数据库名称) - **start_time**: 冲洗开始时间 - **valves**: 要开启的阀门ID列表 - **valves_k**: 各阀门的开度列表(0-1,与valves对应) - **drainage_node_ID**: 排污节点ID - **flush_flow**: 冲洗流量(L/s) - **duration**: 模拟持续时间(秒,可选,默认900) - **scheme_name**: 冲洗方案名称 支持多阀联合冲洗操作。 """ valve_opening = { valve_id: float(valves_k[idx]) for idx, valve_id in enumerate(valves) } result = flushing_analysis( name=network, modify_pattern_start_time=start_time, modify_total_duration=duration or 900, modify_valve_opening=valve_opening, drainage_node_ID=drainage_node_ID, flushing_flow=flush_flow, scheme_name=scheme_name, username=username, ) return result or "success" @router.post("/contaminant-simulations", response_class=PlainTextResponse, summary="污染物模拟", description="对管网中的污染物扩散进行模拟,评估污染源对管网的影响范围和浓度分布。支持指定污染源位置、污染浓度和扩散模式。") async def fastapi_contaminant_simulation( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="污染开始时间(ISO 8601格式)"), source: str = Query(..., description="污染源节点ID"), concentration: float = Query(..., description="污染浓度(mg/L)"), duration: int = Query(..., description="模拟持续时间(秒)"), scheme_name: str = Query(..., description="模拟方案名称"), pattern: str | None = Query(None, description="污染源模式ID(可选)"), username: str = Depends(get_current_keycloak_username), ) -> str: """ 污染物模拟 - **network**: 管网名称(或数据库名称) - **start_time**: 污染开始时间 - **source**: 污染源节点ID - **concentration**: 污染浓度(mg/L) - **duration**: 模拟持续时间(秒) - **scheme_name**: 模拟方案名称 - **pattern**: 污染源模式ID(可选) 用于评估管网中污染物的传播和影响范围。 """ result = contaminant_simulation( name=network, modify_pattern_start_time=start_time, scheme_name=scheme_name, modify_total_duration=duration, source=source, concentration=concentration, source_pattern=pattern, username=username, ) return result or "success" @router.post("/water-age-analyses", response_class=PlainTextResponse, summary="水龄分析(高级)", description="高级版本的水龄分析,在指定时间点进行分析,支持自定义模拟持续时间。返回纯文本格式的分析结果。") async def fastapi_age_analysis( network: str = Query(..., description="管网名称(或数据库名称)"), start_time: str = Query(..., description="分析开始时间(ISO 8601格式)"), duration: int = Query(..., description="模拟持续时间(秒)"), ) -> str: """ 水龄分析(高级版本) - **network**: 管网名称(或数据库名称) - **start_time**: 分析开始时间 - **duration**: 模拟持续时间(秒) 分析指定时间段内管网中各节点的水体停留时间。 """ result = age_analysis(network, start_time, duration) return result or "success" # @router.get("/schedulinganalysis/") # async def scheduling_analysis_endpoint(network: str): # return scheduling_analysis(network) @router.post("/pressure-regulation-calculations", summary="压力调节(基础)", description="对管网的压力进行调节分析,通过控制泵的运行来维持目标节点的目标压力。此为基础版本。") async def pressure_regulation_endpoint( network: str = Query(..., description="管网名称(或数据库名称)"), target_node: str = Query(..., description="目标节点ID"), target_pressure: float = Query(..., description="目标压力值(kPa)"), ): """ 压力调节(基础版本) - **network**: 管网名称(或数据库名称) - **target_node**: 目标节点ID - **target_pressure**: 目标压力值(kPa) 通过泵控制维持目标节点的压力。 """ return pressure_regulation(network, target_node, target_pressure) @router.post("/pressure-regulation-analyses", summary="压力调节(高级)", description="高级版本的压力调节分析,通过JSON请求体提供详细的控制参数,包括固定泵和变速泵的独立控制、水箱初始水位等。") async def fastapi_pressure_regulation(data: PressureRegulation = Body(..., description="压力调节控制参数")) -> str: """ 压力调节(高级版本) 请求体参数: - **network**: 管网名称(或数据库名称) - **start_time**: 控制开始时间 - **pump_control**: 泵控制策略字典 - **tank_init_level**: 水箱初始水位字典(可选) - **duration**: 模拟持续时间(秒,可选,默认900) - **scheme_name**: 控制方案名称(可选) 支持固定泵和变速泵的独立控制。 """ item = data.dict() simulation.query_corresponding_element_id_and_query_id(item["network"]) fixed_pumps = set(globals.fixed_pumps_id.keys()) variable_pumps = set(globals.variable_pumps_id.keys()) fixed_pump_pattern: dict[str, list] = {} variable_pump_pattern: dict[str, list] = {} for pump_id, values in item["pump_control"].items(): if pump_id in variable_pumps: variable_pump_pattern[pump_id] = values else: fixed_pump_pattern[pump_id] = values pressure_regulation( name=item["network"], modify_pattern_start_time=item["start_time"], modify_total_duration=item["duration"] or 900, modify_tank_initial_level=item["tank_init_level"], modify_fixed_pump_pattern=fixed_pump_pattern or None, modify_variable_pump_pattern=variable_pump_pattern or None, scheme_name=item["scheme_name"], ) return "success" @router.post("/project-managements", summary="项目管理(高级)", description="高级版本的项目管理,通过JSON请求体提供详细的控制参数,包括泵控制策略、水箱初始水位和区域需水量控制。") async def fastapi_project_management(data: ProjectManagement = Body(..., description="项目管理控制参数")) -> str: """ 项目管理(高级版本) 请求体参数: - **network**: 管网名称(或数据库名称) - **start_time**: 管理开始时间 - **pump_control**: 泵控制策略字典 - **tank_init_level**: 水箱初始水位字典(可选) - **region_demand**: 区域需水量控制字典(可选) 支持多维度的项目管理。 """ item = data.dict() return project_management( prj_name=item["network"], start_datetime=item["start_time"], pump_control=item["pump_control"], tank_initial_level_control=item["tank_init_level"], region_demand_control=item["region_demand"], ) # @router.get("/dailyschedulinganalysis/") # async def daily_scheduling_analysis_endpoint(network: str): # return daily_scheduling_analysis(network) @router.post("/scheduling-analyses", summary="排程分析", description="对管网的供水排程进行分析,优化泵的运行时间和出水流量,平衡水厂出水、水箱进出水,满足用户需求。") async def fastapi_scheduling_analysis(data: SchedulingAnalysis = Body(..., description="排程分析参数")) -> str: """ 排程分析 请求体参数: - **network**: 管网名称(或数据库名称) - **start_time**: 分析开始时间 - **pump_control**: 泵控制策略字典 - **tank_id**: 水箱ID - **water_plant_output_id**: 水厂出水ID - **time_delta**: 时间步长(秒,可选,默认300) 用于优化供水排程。 """ item = data.dict() return scheduling_simulation( item["network"], item["start_time"], item["pump_control"], item["tank_id"], item["water_plant_output_id"], item["time_delta"], ) @router.post("/daily-scheduling-analyses", summary="日排程分析", description="对管网的每日供水排程进行分析,优化水库、水厂、水箱和用户需求的协调,制定合理的每日排程方案。") async def fastapi_daily_scheduling_analysis(data: DailySchedulingAnalysis = Body(..., description="日排程分析参数")) -> str: """ 日排程分析 请求体参数: - **network**: 管网名称(或数据库名称) - **start_time**: 分析开始时间 - **pump_control**: 泵控制策略字典 - **reservoir_id**: 水库ID - **tank_id**: 水箱ID - **water_plant_output_id**: 水厂出水ID - **time_delta**: 时间步长(秒,可选,默认300) 用于制定每日供水排程方案。 """ item = data.dict() return daily_scheduling_simulation( item["network"], item["start_time"], item["pump_control"], item["reservoir_id"], item["tank_id"], item["water_plant_output_id"], ) # @router.get("/pumpfailure/") # async def pump_failure_endpoint(network: str, pump_id: str, time: str): # return pump_failure(network, pump_id, time) @router.post("/pump-failure-events", summary="泵故障管理", description="记录和管理泵的故障状态,包括故障发生时间和受影响的泵列表。系统将记录故障日志并更新泵状态。") async def fastapi_pump_failure(data: PumpFailureState = Body(..., description="泵故障状态信息")) -> str: """ 泵故障管理 请求体参数: - **time**: 故障发生时间 - **pump_status**: 泵状态字典,包含第一阶段和第二阶段泵的故障状态 系统将验证泵信息的有效性并更新故障状态文件。 """ item = data.dict() with open("./pump_failure_message.txt", "a", encoding="utf-8-sig") as f1: f1.write("[{}] {}\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), item)) with open("./pump_failure_status.txt", "r", encoding="utf-8-sig") as f2: lines = f2.readlines() first_stage_pump_status_dict = json.loads(json.dumps(eval(lines[0]))) second_stage_pump_status_dict = json.loads(json.dumps(eval(lines[-1]))) pump_status_dict = { "first": first_stage_pump_status_dict, "second": second_stage_pump_status_dict, } status_info = item.copy() for pump_type in status_info["pump_status"].keys(): if pump_type in pump_status_dict.keys(): if all( pump_id in pump_status_dict[pump_type].keys() for pump_id in status_info["pump_status"][pump_type].keys() ): for pump_id in status_info["pump_status"][pump_type].keys(): pump_status_dict[pump_type][pump_id] = int( status_info["pump_status"][pump_type][pump_id] ) else: return json.dumps("ERROR: Wrong Pump ID") else: return json.dumps("ERROR: Wrong Pump Type") with open("./pump_failure_status.txt", "w", encoding="utf-8-sig") as f2_: f2_.write( "{}\n{}".format(pump_status_dict["first"], pump_status_dict["second"]) ) return json.dumps("SUCCESS") @router.post("/pressure-sensor-placement-sensitivity-calculations", summary="压力传感器放置-灵敏度分析(基础)", description="基于灵敏度分析方法,为指定管网项目确定最优的压力传感器放置位置。此为基础版本。") async def pressure_sensor_placement_sensitivity_endpoint( name: str = Query(..., description="管网名称(或数据库名称)"), scheme_name: str = Query(..., description="放置方案名称"), sensor_number: int = Query(..., description="传感器数量"), min_diameter: int = Query(..., description="最小管径限制(毫米)"), username: str = Query(..., description="用户名"), ): """ 压力传感器放置-灵敏度分析(基础版本) - **name**: 管网名称(或数据库名称) - **scheme_name**: 放置方案名称 - **sensor_number**: 传感器数量 - **min_diameter**: 最小管径限制(毫米) - **username**: 用户名 基于灵敏度分析方法确定传感器放置位置。 """ return pressure_sensor_placement_sensitivity( name, scheme_name, sensor_number, min_diameter, username ) @router.post("/pressure-sensor-placement-sensitivities", summary="压力传感器放置-灵敏度分析(高级)", description="高级版本的压力传感器放置分析,通过JSON请求体提供详细参数。基于灵敏度分析方法确定最优放置位置。") async def fastapi_pressure_sensor_placement_sensitivity( data: PressureSensorPlacement = Body(..., description="传感器放置分析参数"), ) -> None: """ 压力传感器放置-灵敏度分析(高级版本) 请求体参数: - **name**: 管网名称(或数据库名称) - **scheme_name**: 放置方案名称 - **sensor_number**: 传感器数量 - **min_diameter**: 最小管径限制(毫米) - **username**: 用户名 基于灵敏度分析方法确定压力传感器的最优放置位置。 """ item = data.dict() pressure_sensor_placement_sensitivity( name=item["name"], scheme_name=item["scheme_name"], sensor_number=item["sensor_number"], min_diameter=item["min_diameter"], username=item["username"], ) @router.post("/pressure-sensor-placement-kmeans-calculations", summary="压力传感器放置-KMeans聚类分析(基础)", description="基于KMeans聚类算法,为指定管网项目确定压力传感器的最优放置位置。此为基础版本。") async def pressure_sensor_placement_kmeans_endpoint( name: str = Query(..., description="管网名称(或数据库名称)"), scheme_name: str = Query(..., description="放置方案名称"), sensor_number: int = Query(..., description="传感器数量"), min_diameter: int = Query(..., description="最小管径限制(毫米)"), username: str = Query(..., description="用户名"), ): """ 压力传感器放置-KMeans聚类分析(基础版本) - **name**: 管网名称(或数据库名称) - **scheme_name**: 放置方案名称 - **sensor_number**: 传感器数量 - **min_diameter**: 最小管径限制(毫米) - **username**: 用户名 基于KMeans聚类算法确定传感器放置位置。 """ return pressure_sensor_placement_kmeans( name, scheme_name, sensor_number, min_diameter, username ) @router.post("/pressure-sensor-placement-kmeans", summary="压力传感器放置-KMeans聚类分析(高级)", description="高级版本的压力传感器放置分析,通过JSON请求体提供详细参数。基于KMeans聚类算法确定最优放置位置。") async def fastapi_pressure_sensor_placement_kmeans( data: PressureSensorPlacement = Body(..., description="传感器放置分析参数"), ) -> None: """ 压力传感器放置-KMeans聚类分析(高级版本) 请求体参数: - **name**: 管网名称(或数据库名称) - **scheme_name**: 放置方案名称 - **sensor_number**: 传感器数量 - **min_diameter**: 最小管径限制(毫米) - **username**: 用户名 基于KMeans聚类算法确定压力传感器的最优放置位置。 """ item = data.dict() pressure_sensor_placement_kmeans( name=item["name"], scheme_name=item["scheme_name"], sensor_number=item["sensor_number"], min_diameter=item["min_diameter"], username=item["username"], ) @router.post("/sensor-placement-schemes", summary="传感器放置方案创建", description="创建新的传感器放置方案,支持灵敏度分析和KMeans聚类两种方法。根据指定的方法自动计算最优的传感器放置位置。") async def fastapi_pressure_sensor_placement( network: str = Query(..., description="管网名称(或数据库名称)"), scheme_name: str = Query(..., description="放置方案名称"), sensor_type: str = Query(..., description="传感器类型"), method: str = Query(..., description="放置方法('sensitivity'或'kmeans')"), sensor_count: int = Query(..., description="传感器数量"), min_diameter: int = Query(0, description="最小管径限制(毫米),默认0"), user_name: str = Query(..., description="用户名"), ) -> str: """ 传感器放置方案创建 - **network**: 管网名称(或数据库名称) - **scheme_name**: 放置方案名称 - **sensor_type**: 传感器类型 - **method**: 放置方法('sensitivity'或'kmeans') - **sensor_count**: 传感器数量 - **min_diameter**: 最小管径限制(毫米,默认0) - **user_name**: 用户名 支持两种放置方法: - sensitivity: 基于灵敏度分析 - kmeans: 基于KMeans聚类 """ if method not in ["sensitivity", "kmeans"]: raise HTTPException( status_code=400, detail="Invalid method. Must be 'sensitivity' or 'kmeans'" ) if method == "sensitivity": pressure_sensor_placement_sensitivity( name=network, scheme_name=scheme_name, sensor_number=sensor_count, min_diameter=min_diameter, username=user_name, ) elif method == "kmeans": pressure_sensor_placement_kmeans( name=network, scheme_name=scheme_name, sensor_number=sensor_count, min_diameter=min_diameter, username=user_name, ) return "success" @router.post("/simulation-runs", summary="手动运行日期指定模拟", description="根据指定的开始时间和持续时间,手动运行水力模拟。开始时间必须是显式带时区的 ISO 8601 / RFC3339 时间。") async def fastapi_run_simulation_manually_by_date( data: RunSimulationManuallyByDate = Body(..., description="模拟运行参数"), ) -> dict[str, str]: """ 手动运行日期指定模拟 请求体参数: - **name**: 管网名称(或数据库名称) - **start_time**: 开始时间(ISO 8601 / RFC3339,必须显式带时区) - **duration**: 模拟持续时间(分钟) 系统将从指定时间开始,按15分钟间隔多次运行模拟。 每次模拟间隔15分钟,直至达到指定的总持续时间。 """ item = data.model_dump() try: simulation.query_corresponding_element_id_and_query_id(item["name"]) simulation.query_corresponding_pattern_id_and_query_id(item["name"]) region_result = simulation.query_non_realtime_region(item["name"]) globals.source_outflow_region_id = simulation.get_source_outflow_region_id( item["name"], region_result ) globals.realtime_region_pipe_flow_and_demand_id = ( simulation.query_realtime_region_pipe_flow_and_demand_id( item["name"], region_result ) ) globals.pipe_flow_region_patterns = simulation.query_pipe_flow_region_patterns( item["name"] ) globals.non_realtime_region_patterns = ( simulation.query_non_realtime_region_patterns(item["name"], region_result) ) ( globals.source_outflow_region_patterns, globals.realtime_region_pipe_flow_and_demand_patterns, ) = simulation.get_realtime_region_patterns( item["name"], globals.source_outflow_region_id, globals.realtime_region_pipe_flow_and_demand_id, ) start_time = parse_utc_time(item["start_time"], field_name="start_time") run_simulation_manually_by_date( item["name"], start_time, item["duration"] ) return {"status": "success"} except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc