820 lines
35 KiB
Python
820 lines
35 KiB
Python
from typing import Any, List, Optional
|
||
from datetime import datetime, timedelta
|
||
import json
|
||
import os
|
||
import shutil
|
||
import threading
|
||
from fastapi import APIRouter, HTTPException, File, UploadFile, Query, Path, Body
|
||
from fastapi.responses import PlainTextResponse
|
||
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.network_import import network_update
|
||
from app.services.simulation_ops import (
|
||
project_management,
|
||
scheduling_simulation,
|
||
daily_scheduling_simulation,
|
||
)
|
||
from app.services.valve_isolation import analyze_valve_isolation
|
||
from pydantic import BaseModel, Field
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
class RunSimulationManuallyByDate(BaseModel):
|
||
name: str = Field(..., description="管网名称(或数据库名称)")
|
||
simulation_date: str = Field(..., description="模拟基准日期 (YYYY-MM-DD)")
|
||
start_time: str = Field(..., description="开始时间 (HH:MM 或 HH:MM:SS)")
|
||
duration: int = Field(..., description="持续时间 (分钟)")
|
||
|
||
|
||
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, base_date: datetime, start_time: str, duration: int
|
||
) -> None:
|
||
time_parts = list(map(int, start_time.split(":")))
|
||
if len(time_parts) == 2:
|
||
start_hour, start_minute = time_parts
|
||
start_second = 0
|
||
elif len(time_parts) == 3:
|
||
start_hour, start_minute, start_second = time_parts
|
||
else:
|
||
raise ValueError("Invalid start_time format. Use HH:MM or HH:MM:SS")
|
||
|
||
start_datetime = base_date.replace(
|
||
hour=start_hour, minute=start_minute, second=start_second
|
||
)
|
||
end_datetime = start_datetime + timedelta(minutes=duration)
|
||
current_time = start_datetime
|
||
while current_time < end_datetime:
|
||
iso_time = current_time.strftime("%Y-%m-%dT%H:%M:%S") + "+08:00"
|
||
simulation.run_simulation(
|
||
name=network_name,
|
||
simulation_type="realtime",
|
||
modify_pattern_start_time=iso_time,
|
||
)
|
||
current_time += timedelta(minutes=15)
|
||
|
||
|
||
# 必须用这个PlainTextResponse,不然每个key都有引号
|
||
@router.get("/runproject/", 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.get("/runprojectreturndict/", 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.get("/runinp/", 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("/dumpoutput/", summary="导出模拟输出", description="导出指定路径的模拟输出文件内容。参数应为绝对路径。")
|
||
async def dump_output_endpoint(output: str = Query(..., description="模拟输出文件的绝对路径")) -> str:
|
||
"""
|
||
导出模拟输出
|
||
|
||
- **output**: 模拟输出文件的绝对路径
|
||
|
||
读取并返回指定路径的模拟输出内容。
|
||
"""
|
||
return dump_output(output)
|
||
|
||
|
||
# Analysis Endpoints
|
||
@router.get("/burst_analysis/", 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="分析方案名称"),
|
||
) -> 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,
|
||
)
|
||
return "success"
|
||
|
||
|
||
@router.get("/valve_close_analysis/", 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秒"),
|
||
) -> str:
|
||
"""
|
||
阀门关闭分析(高级版本)
|
||
|
||
- **network**: 管网名称(或数据库名称)
|
||
- **start_time**: 阀门关闭开始时间
|
||
- **valves**: 要关闭的阀门ID列表
|
||
- **duration**: 模拟持续时间(秒,可选,默认900)
|
||
|
||
支持同时关闭多个阀门进行分析。
|
||
"""
|
||
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},
|
||
)
|
||
return result or "success"
|
||
|
||
|
||
@router.get("/valve_isolation_analysis/", 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: 受影响的节点列表
|
||
- 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.get("/flushing_analysis/", 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 | None = Query(None, description="冲洗方案名称(可选)"),
|
||
) -> 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,
|
||
)
|
||
return result or "success"
|
||
|
||
|
||
@router.get("/contaminant_simulation/", 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 | None = Query(None, description="模拟方案名称(可选)"),
|
||
pattern: str | None = Query(None, description="污染源模式ID(可选)"),
|
||
) -> 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,
|
||
)
|
||
return result or "success"
|
||
|
||
|
||
@router.get("/age_analysis/", 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.get("/pressureregulation/", 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/", 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_management/", 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_analysis/", 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_analysis/", 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.post("/network_project/", summary="导入网络项目", description="通过上传INP格式的管网文件导入新的网络项目。系统将自动处理文件并执行模拟。")
|
||
async def fastapi_network_project(file: UploadFile = File(..., description="INP格式的管网文件")) -> str:
|
||
"""
|
||
导入网络项目
|
||
|
||
- **file**: 上传的INP格式管网文件
|
||
|
||
系统将上传的文件保存到inp文件夹并执行模拟。
|
||
"""
|
||
temp_file_dir = "./inp/"
|
||
if not os.path.exists(temp_file_dir):
|
||
os.mkdir(temp_file_dir)
|
||
temp_file_name = f'network_project_{datetime.now().strftime("%Y%m%d")}'
|
||
temp_file_path = f"{temp_file_dir}{temp_file_name}.inp"
|
||
with open(temp_file_path, "wb") as buffer:
|
||
shutil.copyfileobj(file.file, buffer)
|
||
return run_inp(temp_file_name)
|
||
|
||
|
||
@router.post("/network_update/", summary="管网更新(高级)", description="通过上传更新文件对管网进行高级的更新操作。系统将处理更新文件并应用到数据库。")
|
||
async def fastapi_network_update(file: UploadFile = File(..., description="包含管网更新信息的文件")) -> str:
|
||
"""
|
||
管网更新(高级版本)
|
||
|
||
- **file**: 包含管网更新信息的文件
|
||
|
||
系统将处理上传的文件并应用管网更新。
|
||
"""
|
||
default_folder = "./"
|
||
temp_file_name = f'network_update_{datetime.now().strftime("%Y%m%d")}'
|
||
temp_file_path = os.path.join(default_folder, temp_file_name)
|
||
try:
|
||
with open(temp_file_path, "wb") as buffer:
|
||
shutil.copyfileobj(file.file, buffer)
|
||
network_update(temp_file_path)
|
||
return json.dumps({"message": "管网更新成功"})
|
||
except Exception as exc:
|
||
raise HTTPException(status_code=500, detail=f"数据库操作失败: {exc}")
|
||
|
||
|
||
# @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/", 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.get("/pressuresensorplacementsensitivity/", 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_sensitivity/", 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.get("/pressuresensorplacementkmeans/", 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("/sensorplacementscheme/create", 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("/runsimulationmanuallybydate/", summary="手动运行日期指定模拟", description="根据指定的日期、开始时间和持续时间,手动运行水力模拟。系统将自动查询管网参数并执行模拟。")
|
||
async def fastapi_run_simulation_manually_by_date(
|
||
data: RunSimulationManuallyByDate = Body(..., description="模拟运行参数"),
|
||
) -> dict[str, str]:
|
||
"""
|
||
手动运行日期指定模拟
|
||
|
||
请求体参数:
|
||
- **name**: 管网名称(或数据库名称)
|
||
- **simulation_date**: 模拟基准日期(YYYY-MM-DD格式)
|
||
- **start_time**: 开始时间(HH:MM或HH:MM:SS格式)
|
||
- **duration**: 模拟持续时间(分钟)
|
||
|
||
系统将从指定日期和时间开始,按15分钟间隔多次运行模拟。
|
||
每次模拟间隔15分钟,直至达到指定的总持续时间。
|
||
"""
|
||
item = data.dict()
|
||
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,
|
||
)
|
||
base_date = datetime.strptime(item["simulation_date"], "%Y-%m-%d")
|
||
run_simulation_manually_by_date(
|
||
item["name"], base_date, item["start_time"], item["duration"]
|
||
)
|
||
return {"status": "success"}
|
||
except Exception as exc:
|
||
return {"status": "error", "message": str(exc)}
|