调整epanet工具目录结构;联通前端水质分析模块功能;新建 readme.md

This commit is contained in:
2026-01-30 15:24:56 +08:00
parent 9d7a9fb2fd
commit 9037bf317b
12 changed files with 128 additions and 69 deletions

View File

@@ -1,4 +0,0 @@
当前 适配 szh 项目的分支 是 dingsu/szh
Binary 适配的是 代码 中dingsu/szh 的部分
当前只是把 API目录也就是TJNetwork的部分加密了

View File

@@ -5,7 +5,10 @@ from math import pi, sqrt
import pytz import pytz
import app.services.simulation as simulation import app.services.simulation as simulation
from app.algorithms.api_ex.run_simulation import run_simulation_ex, from_clock_to_seconds_2 from app.algorithms.api_ex.run_simulation import (
run_simulation_ex,
from_clock_to_seconds_2,
)
from app.native.api.project import copy_project from app.native.api.project import copy_project
from app.services.epanet.epanet import Output from app.services.epanet.epanet import Output
from app.services.scheme_management import store_scheme_info from app.services.scheme_management import store_scheme_info
@@ -43,7 +46,7 @@ def burst_analysis(
modify_fixed_pump_pattern: dict[str, list] = None, modify_fixed_pump_pattern: dict[str, list] = None,
modify_variable_pump_pattern: dict[str, list] = None, modify_variable_pump_pattern: dict[str, list] = None,
modify_valve_opening: dict[str, float] = None, modify_valve_opening: dict[str, float] = None,
scheme_Name: str = None, scheme_name: str = None,
) -> None: ) -> None:
""" """
爆管模拟 爆管模拟
@@ -55,7 +58,7 @@ def burst_analysis(
:param modify_fixed_pump_pattern: dict中包含多个水泵模式str为工频水泵的idlist为修改后的pattern :param modify_fixed_pump_pattern: dict中包含多个水泵模式str为工频水泵的idlist为修改后的pattern
:param modify_variable_pump_pattern: dict中包含多个水泵模式str为变频水泵的idlist为修改后的pattern :param modify_variable_pump_pattern: dict中包含多个水泵模式str为变频水泵的idlist为修改后的pattern
:param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度 :param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度
:param scheme_Name: 方案名称 :param scheme_name: 方案名称
:return: :return:
""" """
scheme_detail: dict = { scheme_detail: dict = {
@@ -169,19 +172,19 @@ def burst_analysis(
modify_fixed_pump_pattern=modify_fixed_pump_pattern, modify_fixed_pump_pattern=modify_fixed_pump_pattern,
modify_variable_pump_pattern=modify_variable_pump_pattern, modify_variable_pump_pattern=modify_variable_pump_pattern,
modify_valve_opening=modify_valve_opening, modify_valve_opening=modify_valve_opening,
scheme_Type="burst_Analysis", scheme_type="burst_analysis",
scheme_Name=scheme_Name, scheme_name=scheme_name,
) )
# step 3. restore the base model status # step 3. restore the base model status
# execute_undo(name) #有疑惑 # execute_undo(name) #有疑惑
if is_project_open(new_name): if is_project_open(new_name):
close_project(new_name) close_project(new_name)
delete_project(new_name) delete_project(new_name)
# return result # 存储方案信息到 PG 数据库
store_scheme_info( store_scheme_info(
name=name, name=name,
scheme_name=scheme_Name, scheme_name=scheme_name,
scheme_type="burst_Analysis", scheme_type="burst_analysis",
username="admin", username="admin",
scheme_start_time=modify_pattern_start_time, scheme_start_time=modify_pattern_start_time,
scheme_detail=scheme_detail, scheme_detail=scheme_detail,
@@ -400,11 +403,11 @@ def flushing_analysis(
def contaminant_simulation( def contaminant_simulation(
name: str, name: str,
modify_pattern_start_time: str, # 模拟开始时间,格式为'2024-11-25T09:00:00+08:00' modify_pattern_start_time: str, # 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
modify_total_duration: int = 900, # 模拟总历时,秒 modify_total_duration: int, # 模拟总历时,秒
source: str = None, # 污染源节点ID source: str, # 污染源节点ID
concentration: float = None, # 污染源浓度单位mg/L concentration: float, # 污染源浓度单位mg/L
scheme_name: str = None,
source_pattern: str = None, # 污染源时间变化模式名称 source_pattern: str = None, # 污染源时间变化模式名称
scheme_Name: str = None,
) -> None: ) -> None:
""" """
污染模拟 污染模拟
@@ -418,6 +421,12 @@ def contaminant_simulation(
:param scheme_Name: 方案名称 :param scheme_Name: 方案名称
:return: :return:
""" """
scheme_detail: dict = {
"source": source,
"concentration": concentration,
"duration": modify_total_duration,
"pattern": source_pattern,
}
print( print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S") datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis." + " -- Start Analysis."
@@ -520,8 +529,8 @@ def contaminant_simulation(
simulation_type="extended", simulation_type="extended",
modify_pattern_start_time=modify_pattern_start_time, modify_pattern_start_time=modify_pattern_start_time,
modify_total_duration=modify_total_duration, modify_total_duration=modify_total_duration,
scheme_Type="contaminant_Analysis", scheme_type="contaminant_analysis",
scheme_Name=scheme_Name, scheme_name=scheme_name,
) )
# for i in range(1,operation_step): # for i in range(1,operation_step):
@@ -529,7 +538,15 @@ def contaminant_simulation(
if is_project_open(new_name): if is_project_open(new_name):
close_project(new_name) close_project(new_name)
delete_project(new_name) delete_project(new_name)
# return result # 存储方案信息到 PG 数据库
store_scheme_info(
name=name,
scheme_name=scheme_name,
scheme_type="contaminant_analysis",
username="admin",
scheme_start_time=modify_pattern_start_time,
scheme_detail=scheme_detail,
)
############################################################ ############################################################

View File

@@ -192,19 +192,24 @@ async def burst_analysis_endpoint(
return burst_analysis(network, pipe_id, start_time, end_time, burst_flow) return burst_analysis(network, pipe_id, start_time, end_time, burst_flow)
@router.post("/burst_analysis/") @router.get("/burst_analysis/")
async def fastapi_burst_analysis(data: BurstAnalysis) -> str: async def fastapi_burst_analysis(
item = data.dict() network: str = Query(...),
modify_pattern_start_time: str = Query(...),
burst_ID: list | str = Query(..., alias="burst_ID[]"), # 添加别名以匹配 URL
burst_size: list | float | int = Query(
..., alias="burst_size[]"
), # 添加别名以匹配 URL
modify_total_duration: int = Query(...),
scheme_name: str = Query(...),
) -> str:
burst_analysis( burst_analysis(
name=item["name"], name=network,
modify_pattern_start_time=item["modify_pattern_start_time"], modify_pattern_start_time=modify_pattern_start_time,
burst_ID=item["burst_ID"], burst_ID=burst_ID,
burst_size=item["burst_size"], burst_size=burst_size,
modify_total_duration=item["modify_total_duration"], modify_total_duration=modify_total_duration,
modify_fixed_pump_pattern=item["modify_fixed_pump_pattern"], scheme_name=scheme_name,
modify_variable_pump_pattern=item["modify_variable_pump_pattern"],
modify_valve_opening=item["modify_valve_opening"],
scheme_Name=item["scheme_Name"],
) )
return "success" return "success"
@@ -254,7 +259,9 @@ async def fastapi_flushing_analysis(
flush_flow: float = 0, flush_flow: float = 0,
duration: int | None = None, duration: int | None = None,
) -> str: ) -> str:
valve_opening = {valve_id: float(valves_k[idx]) for idx, valve_id in enumerate(valves)} valve_opening = {
valve_id: float(valves_k[idx]) for idx, valve_id in enumerate(valves)
}
result = flushing_analysis( result = flushing_analysis(
name=network, name=network,
modify_pattern_start_time=start_time, modify_pattern_start_time=start_time,
@@ -266,25 +273,20 @@ async def fastapi_flushing_analysis(
return result or "success" return result or "success"
@router.get("/contaminantsimulation/")
async def contaminant_simulation_endpoint(
network: str, node_id: str, start_time: str, duration: float, concentration: float
):
return contaminant_simulation(network, node_id, start_time, duration, concentration)
@router.get("/contaminant_simulation/", response_class=PlainTextResponse) @router.get("/contaminant_simulation/", response_class=PlainTextResponse)
async def fastapi_contaminant_simulation( async def fastapi_contaminant_simulation(
network: str, network: str,
start_time: str, start_time: str,
source: str, source: str,
concentration: float, concentration: float,
duration: int = 900, duration: int,
scheme_name: str | None = None,
pattern: str | None = None, pattern: str | None = None,
) -> str: ) -> str:
result = contaminant_simulation( result = contaminant_simulation(
name=network, name=network,
modify_pattern_start_time=start_time, modify_pattern_start_time=start_time,
scheme_name=scheme_name,
modify_total_duration=duration, modify_total_duration=duration,
source=source, source=source,
concentration=concentration, concentration=concentration,
@@ -431,9 +433,7 @@ async def fastapi_network_update(file: UploadFile = File()) -> str:
async def fastapi_pump_failure(data: PumpFailureState) -> str: async def fastapi_pump_failure(data: PumpFailureState) -> str:
item = data.dict() item = data.dict()
with open("./pump_failure_message.txt", "a", encoding="utf-8-sig") as f1: with open("./pump_failure_message.txt", "a", encoding="utf-8-sig") as f1:
f1.write( f1.write("[{}] {}\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), item))
"[{}] {}\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: with open("./pump_failure_status.txt", "r", encoding="utf-8-sig") as f2:
lines = f2.readlines() lines = f2.readlines()
first_stage_pump_status_dict = json.loads(json.dumps(eval(lines[0]))) first_stage_pump_status_dict = json.loads(json.dumps(eval(lines[0])))

View File

@@ -56,4 +56,4 @@ app.add_middleware(GZipMiddleware, minimum_size=1000)
# Include Routers # Include Routers
app.include_router(api_router, prefix="/api/v1") app.include_router(api_router, prefix="/api/v1")
# Legcy Routers without version prefix # Legcy Routers without version prefix
# app.include_router(api_router) app.include_router(api_router)

View File

@@ -30,11 +30,11 @@ class Output:
if platform.system() == "Windows": if platform.system() == "Windows":
self._lib = ctypes.CDLL( self._lib = ctypes.CDLL(
os.path.join(os.getcwd(), "epanet", "epanet-output.dll") os.path.join(os.path.dirname(__file__), "windows", "epanet-output.dll")
) )
else: else:
self._lib = ctypes.CDLL( self._lib = ctypes.CDLL(
os.path.join(os.getcwd(), "epanet", "linux", "libepanet-output.so") os.path.join(os.path.dirname(__file__), "linux", "libepanet-output.so")
) )
self._handle = ctypes.c_void_p() self._handle = ctypes.c_void_p()
@@ -314,9 +314,9 @@ def run_project_return_dict(name: str, readable_output: bool = False) -> dict[st
input = name + ".db" input = name + ".db"
if platform.system() == "Windows": if platform.system() == "Windows":
exe = os.path.join(os.path.join(dir, "epanet"), "runepanet.exe") exe = os.path.join(os.path.dirname(__file__), "windows", "runepanet.exe")
else: else:
exe = os.path.join(os.path.join(dir, "epanet"), "linux", "runepanet") exe = os.path.join(os.path.dirname(__file__), "linux", "runepanet")
inp = os.path.join(os.path.join(dir, "db_inp"), input + ".inp") inp = os.path.join(os.path.join(dir, "db_inp"), input + ".inp")
rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt") rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt")
opt = os.path.join(os.path.join(dir, "temp"), input + ".opt") opt = os.path.join(os.path.join(dir, "temp"), input + ".opt")
@@ -364,9 +364,9 @@ def run_project(name: str, readable_output: bool = False) -> str:
input = name + ".db" input = name + ".db"
if platform.system() == "Windows": if platform.system() == "Windows":
exe = os.path.join(os.path.join(dir, "epanet"), "runepanet.exe") exe = os.path.join(os.path.dirname(__file__), "windows", "runepanet.exe")
else: else:
exe = os.path.join(os.path.join(dir, "epanet"), "linux", "runepanet") exe = os.path.join(os.path.dirname(__file__), "linux", "runepanet")
inp = os.path.join(os.path.join(dir, "db_inp"), input + ".inp") inp = os.path.join(os.path.join(dir, "db_inp"), input + ".inp")
rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt") rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt")
opt = os.path.join(os.path.join(dir, "temp"), input + ".opt") opt = os.path.join(os.path.join(dir, "temp"), input + ".opt")
@@ -416,9 +416,9 @@ def run_inp(name: str) -> str:
dir = os.path.abspath(os.getcwd()) dir = os.path.abspath(os.getcwd())
if platform.system() == "Windows": if platform.system() == "Windows":
exe = os.path.join(os.path.join(dir, "epanet"), "runepanet.exe") exe = os.path.join(os.path.dirname(__file__), "windows", "runepanet.exe")
else: else:
exe = os.path.join(os.path.join(dir, "epanet"), "linux", "runepanet") exe = os.path.join(os.path.dirname(__file__), "linux", "runepanet")
inp = os.path.join(os.path.join(dir, "inp"), name + ".inp") inp = os.path.join(os.path.join(dir, "inp"), name + ".inp")
rpt = os.path.join(os.path.join(dir, "temp"), name + ".rpt") rpt = os.path.join(os.path.join(dir, "temp"), name + ".rpt")
opt = os.path.join(os.path.join(dir, "temp"), name + ".opt") opt = os.path.join(os.path.join(dir, "temp"), name + ".opt")

View File

@@ -21,8 +21,12 @@ import app.services.globals as globals
import uuid import uuid
import app.services.project_info as project_info import app.services.project_info as project_info
from app.native.api.postgresql_info import get_pgconn_string from app.native.api.postgresql_info import get_pgconn_string
from app.infra.db.timescaledb.internal_queries import InternalQueries as TimescaleInternalQueries from app.infra.db.timescaledb.internal_queries import (
from app.infra.db.timescaledb.internal_queries import InternalStorage as TimescaleInternalStorage InternalQueries as TimescaleInternalQueries,
)
from app.infra.db.timescaledb.internal_queries import (
InternalStorage as TimescaleInternalStorage,
)
logging.basicConfig( logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
@@ -679,8 +683,8 @@ def run_simulation(
modify_fixed_pump_pattern: dict[str, list] = None, modify_fixed_pump_pattern: dict[str, list] = None,
modify_variable_pump_pattern: dict[str, list] = None, modify_variable_pump_pattern: dict[str, list] = None,
modify_valve_opening: dict[str, float] = None, modify_valve_opening: dict[str, float] = None,
scheme_Type: str = None, scheme_type: str = None,
scheme_Name: str = None, scheme_name: str = None,
) -> None: ) -> None:
""" """
传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果 传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果
@@ -695,8 +699,8 @@ def run_simulation(
:param modify_fixed_pump_pattern: dict中包含多个水泵模式str为工频水泵的idlist为修改后的pattern :param modify_fixed_pump_pattern: dict中包含多个水泵模式str为工频水泵的idlist为修改后的pattern
:param modify_variable_pump_pattern: dict中包含多个水泵模式str为变频水泵的idlist为修改后的pattern :param modify_variable_pump_pattern: dict中包含多个水泵模式str为变频水泵的idlist为修改后的pattern
:param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度 :param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度
:param scheme_Type: 模拟方案类型 :param scheme_type: 模拟方案类型
:param scheme_Name模拟方案名称 :param scheme_name模拟方案名称
:return: :return:
""" """
# 记录开始时间 # 记录开始时间
@@ -1235,8 +1239,8 @@ def run_simulation(
) )
elif simulation_type.upper() == "EXTENDED": elif simulation_type.upper() == "EXTENDED":
TimescaleInternalStorage.store_scheme_simulation( TimescaleInternalStorage.store_scheme_simulation(
scheme_Type, scheme_type,
scheme_Name, scheme_name,
node_result, node_result,
link_result, link_result,
modify_pattern_start_time, modify_pattern_start_time,

41
readme.md Normal file
View File

@@ -0,0 +1,41 @@
# TJWater Server (FastAPI)
基于 FastAPI 的水务业务服务端提供模拟计算、SCADA 数据、网络元素、项目管理等接口。
## 目录结构
```
app/
main.py # FastAPI 入口lifespan、CORS、路由挂载
api/
v1/
router.py # API 路由汇总(/api/v1 前缀)
endpoints/ # 业务接口实现auth、simulation、scada 等)
endpoints/network/ # 管网要素与特性接口
endpoints/components/ # 组件/控制相关接口
services/ # 业务服务层simulation、tjnetwork 等)
infra/
db/ # 数据库访问层timescaledb / postgresql / influxdb
cache/ # 缓存与 Redis 客户端
algorithms/ # 算法与分析模块
core/ # 配置与安全相关
configs/
project_info.yml # 默认工程配置(启动时自动打开)
scripts/
run_server.py # Uvicorn 启动脚本
tests/ # 测试
```
## 启动方式
1. 安装依赖(示例):
```bash
pip install -r requirements.txt
```
2. 启动服务:
```bash
python scripts/run_server.py
```
默认监听:`http://0.0.0.0:8000`
API 前缀:`/api/v1`(见 `app/main.py` 与 `app/api/v1/router.py`

View File

@@ -3710,8 +3710,9 @@ async def fastapi_contaminant_simulation(
start_time: str, start_time: str,
source: str, source: str,
concentration: float, concentration: float,
duration: int = 900, duration: int,
pattern: str = None, pattern: str = None,
scheme_Name: str = None,
) -> str: ) -> str:
filename = "c:/lock.simulation" filename = "c:/lock.simulation"
filename2 = "c:/lock.simulation2" filename2 = "c:/lock.simulation2"

View File

@@ -56,7 +56,7 @@ def burst_analysis(
modify_fixed_pump_pattern: dict[str, list] = None, modify_fixed_pump_pattern: dict[str, list] = None,
modify_variable_pump_pattern: dict[str, list] = None, modify_variable_pump_pattern: dict[str, list] = None,
modify_valve_opening: dict[str, float] = None, modify_valve_opening: dict[str, float] = None,
scheme_Name: str = None, scheme_name: str = None,
) -> None: ) -> None:
""" """
爆管模拟 爆管模拟
@@ -182,8 +182,8 @@ def burst_analysis(
modify_fixed_pump_pattern=modify_fixed_pump_pattern, modify_fixed_pump_pattern=modify_fixed_pump_pattern,
modify_variable_pump_pattern=modify_variable_pump_pattern, modify_variable_pump_pattern=modify_variable_pump_pattern,
modify_valve_opening=modify_valve_opening, modify_valve_opening=modify_valve_opening,
scheme_Type="burst_Analysis", scheme_type="burst_Analysis",
scheme_Name=scheme_Name, scheme_name=scheme_name,
) )
# step 3. restore the base model status # step 3. restore the base model status
# execute_undo(name) #有疑惑 # execute_undo(name) #有疑惑
@@ -193,7 +193,7 @@ def burst_analysis(
# return result # return result
store_scheme_info( store_scheme_info(
name=name, name=name,
scheme_name=scheme_Name, scheme_name=scheme_name,
scheme_type="burst_Analysis", scheme_type="burst_Analysis",
username="admin", username="admin",
scheme_start_time=modify_pattern_start_time, scheme_start_time=modify_pattern_start_time,
@@ -209,7 +209,7 @@ def valve_close_analysis(
modify_pattern_start_time: str, modify_pattern_start_time: str,
modify_total_duration: int = 900, modify_total_duration: int = 900,
modify_valve_opening: dict[str, float] = None, modify_valve_opening: dict[str, float] = None,
scheme_Name: str = None, scheme_name: str = None,
) -> None: ) -> None:
""" """
关阀模拟 关阀模拟
@@ -217,7 +217,7 @@ def valve_close_analysis(
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00' :param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
:param modify_total_duration: 模拟总历时,秒 :param modify_total_duration: 模拟总历时,秒
:param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度 :param modify_valve_opening: dict中包含多个阀门开启度str为阀门的idfloat为修改后的阀门开启度
:param scheme_Name: 方案名称 :param scheme_name: 方案名称
:return: :return:
""" """
print( print(
@@ -271,8 +271,8 @@ def valve_close_analysis(
modify_pattern_start_time=modify_pattern_start_time, modify_pattern_start_time=modify_pattern_start_time,
modify_total_duration=modify_total_duration, modify_total_duration=modify_total_duration,
modify_valve_opening=modify_valve_opening, modify_valve_opening=modify_valve_opening,
scheme_Type="valve_close_Analysis", scheme_type="valve_close_Analysis",
scheme_Name=scheme_Name, scheme_name=scheme_name,
) )
# step 3. restore the base model # step 3. restore the base model
# for valve in valves: # for valve in valves: