diff --git a/ReadMe.md b/ReadMe.md deleted file mode 100644 index 50771f4..0000000 --- a/ReadMe.md +++ /dev/null @@ -1,4 +0,0 @@ -当前 适配 szh 项目的分支 是 dingsu/szh - -Binary 适配的是 代码 中dingsu/szh 的部分 -当前只是把 API目录(也就是TJNetwork的部分)加密了 diff --git a/app/algorithms/simulations.py b/app/algorithms/simulations.py index 24f3d60..3e28445 100644 --- a/app/algorithms/simulations.py +++ b/app/algorithms/simulations.py @@ -5,7 +5,10 @@ from math import pi, sqrt import pytz 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.services.epanet.epanet import Output 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_variable_pump_pattern: dict[str, list] = None, modify_valve_opening: dict[str, float] = None, - scheme_Name: str = None, + scheme_name: str = None, ) -> None: """ 爆管模拟 @@ -55,7 +58,7 @@ def burst_analysis( :param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern :param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern :param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度 - :param scheme_Name: 方案名称 + :param scheme_name: 方案名称 :return: """ scheme_detail: dict = { @@ -169,19 +172,19 @@ def burst_analysis( modify_fixed_pump_pattern=modify_fixed_pump_pattern, modify_variable_pump_pattern=modify_variable_pump_pattern, modify_valve_opening=modify_valve_opening, - scheme_Type="burst_Analysis", - scheme_Name=scheme_Name, + scheme_type="burst_analysis", + scheme_name=scheme_name, ) # step 3. restore the base model status # execute_undo(name) #有疑惑 if is_project_open(new_name): close_project(new_name) delete_project(new_name) - # return result + # 存储方案信息到 PG 数据库 store_scheme_info( name=name, - scheme_name=scheme_Name, - scheme_type="burst_Analysis", + scheme_name=scheme_name, + scheme_type="burst_analysis", username="admin", scheme_start_time=modify_pattern_start_time, scheme_detail=scheme_detail, @@ -400,11 +403,11 @@ def flushing_analysis( def contaminant_simulation( name: str, modify_pattern_start_time: str, # 模拟开始时间,格式为'2024-11-25T09:00:00+08:00' - modify_total_duration: int = 900, # 模拟总历时,秒 - source: str = None, # 污染源节点ID - concentration: float = None, # 污染源浓度,单位mg/L + modify_total_duration: int, # 模拟总历时,秒 + source: str, # 污染源节点ID + concentration: float, # 污染源浓度,单位mg/L + scheme_name: str = None, source_pattern: str = None, # 污染源时间变化模式名称 - scheme_Name: str = None, ) -> None: """ 污染模拟 @@ -418,6 +421,12 @@ def contaminant_simulation( :param scheme_Name: 方案名称 :return: """ + scheme_detail: dict = { + "source": source, + "concentration": concentration, + "duration": modify_total_duration, + "pattern": source_pattern, + } print( datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S") + " -- Start Analysis." @@ -520,8 +529,8 @@ def contaminant_simulation( simulation_type="extended", modify_pattern_start_time=modify_pattern_start_time, modify_total_duration=modify_total_duration, - scheme_Type="contaminant_Analysis", - scheme_Name=scheme_Name, + scheme_type="contaminant_analysis", + scheme_name=scheme_name, ) # for i in range(1,operation_step): @@ -529,7 +538,15 @@ def contaminant_simulation( if is_project_open(new_name): close_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, + ) ############################################################ diff --git a/app/api/v1/endpoints/simulation.py b/app/api/v1/endpoints/simulation.py index afb52ce..b2893f8 100644 --- a/app/api/v1/endpoints/simulation.py +++ b/app/api/v1/endpoints/simulation.py @@ -192,19 +192,24 @@ async def burst_analysis_endpoint( return burst_analysis(network, pipe_id, start_time, end_time, burst_flow) -@router.post("/burst_analysis/") -async def fastapi_burst_analysis(data: BurstAnalysis) -> str: - item = data.dict() +@router.get("/burst_analysis/") +async def fastapi_burst_analysis( + 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( - name=item["name"], - modify_pattern_start_time=item["modify_pattern_start_time"], - burst_ID=item["burst_ID"], - burst_size=item["burst_size"], - modify_total_duration=item["modify_total_duration"], - modify_fixed_pump_pattern=item["modify_fixed_pump_pattern"], - modify_variable_pump_pattern=item["modify_variable_pump_pattern"], - modify_valve_opening=item["modify_valve_opening"], - scheme_Name=item["scheme_Name"], + 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" @@ -254,7 +259,9 @@ async def fastapi_flushing_analysis( flush_flow: float = 0, duration: int | None = None, ) -> 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( name=network, modify_pattern_start_time=start_time, @@ -266,25 +273,20 @@ async def fastapi_flushing_analysis( 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) async def fastapi_contaminant_simulation( network: str, start_time: str, source: str, concentration: float, - duration: int = 900, + duration: int, + scheme_name: str | None = None, pattern: str | None = None, ) -> str: result = contaminant_simulation( name=network, modify_pattern_start_time=start_time, + scheme_name=scheme_name, modify_total_duration=duration, source=source, concentration=concentration, @@ -431,9 +433,7 @@ async def fastapi_network_update(file: UploadFile = File()) -> str: async def fastapi_pump_failure(data: PumpFailureState) -> str: 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) - ) + 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]))) diff --git a/app/main.py b/app/main.py index 86a9c91..b784e47 100644 --- a/app/main.py +++ b/app/main.py @@ -56,4 +56,4 @@ app.add_middleware(GZipMiddleware, minimum_size=1000) # Include Routers app.include_router(api_router, prefix="/api/v1") # Legcy Routers without version prefix -# app.include_router(api_router) +app.include_router(api_router) diff --git a/app/services/epanet/epanet.py b/app/services/epanet/epanet.py index 092f840..6c8943a 100644 --- a/app/services/epanet/epanet.py +++ b/app/services/epanet/epanet.py @@ -30,11 +30,11 @@ class Output: if platform.system() == "Windows": 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: 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() @@ -314,9 +314,9 @@ def run_project_return_dict(name: str, readable_output: bool = False) -> dict[st input = name + ".db" 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: - 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") rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt") 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" 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: - 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") rpt = os.path.join(os.path.join(dir, "temp"), input + ".rpt") 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()) 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: - 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") rpt = os.path.join(os.path.join(dir, "temp"), name + ".rpt") opt = os.path.join(os.path.join(dir, "temp"), name + ".opt") diff --git a/app/services/epanet/epanet-output.dll b/app/services/epanet/windows/epanet-output.dll similarity index 100% rename from app/services/epanet/epanet-output.dll rename to app/services/epanet/windows/epanet-output.dll diff --git a/app/services/epanet/epanet2.dll b/app/services/epanet/windows/epanet2.dll similarity index 100% rename from app/services/epanet/epanet2.dll rename to app/services/epanet/windows/epanet2.dll diff --git a/app/services/epanet/runepanet.exe b/app/services/epanet/windows/runepanet.exe similarity index 100% rename from app/services/epanet/runepanet.exe rename to app/services/epanet/windows/runepanet.exe diff --git a/app/services/simulation.py b/app/services/simulation.py index 915e902..a0f8fdf 100644 --- a/app/services/simulation.py +++ b/app/services/simulation.py @@ -21,8 +21,12 @@ import app.services.globals as globals import uuid import app.services.project_info as project_info 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 InternalStorage as TimescaleInternalStorage +from app.infra.db.timescaledb.internal_queries import ( + InternalQueries as TimescaleInternalQueries, +) +from app.infra.db.timescaledb.internal_queries import ( + InternalStorage as TimescaleInternalStorage, +) logging.basicConfig( 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_variable_pump_pattern: dict[str, list] = None, modify_valve_opening: dict[str, float] = None, - scheme_Type: str = None, - scheme_Name: str = None, + scheme_type: str = None, + scheme_name: str = None, ) -> None: """ 传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果 @@ -695,8 +699,8 @@ def run_simulation( :param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern :param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern :param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度 - :param scheme_Type: 模拟方案类型 - :param scheme_Name:模拟方案名称 + :param scheme_type: 模拟方案类型 + :param scheme_name:模拟方案名称 :return: """ # 记录开始时间 @@ -1235,8 +1239,8 @@ def run_simulation( ) elif simulation_type.upper() == "EXTENDED": TimescaleInternalStorage.store_scheme_simulation( - scheme_Type, - scheme_Name, + scheme_type, + scheme_name, node_result, link_result, modify_pattern_start_time, diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..88ced01 --- /dev/null +++ b/readme.md @@ -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`) diff --git a/scripts/main.py b/scripts/main.py index 5be0bbd..2541bc4 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -3710,8 +3710,9 @@ async def fastapi_contaminant_simulation( start_time: str, source: str, concentration: float, - duration: int = 900, + duration: int, pattern: str = None, + scheme_Name: str = None, ) -> str: filename = "c:/lock.simulation" filename2 = "c:/lock.simulation2" diff --git a/scripts/online_Analysis.py b/scripts/online_Analysis.py index 011bd48..08fd0c6 100644 --- a/scripts/online_Analysis.py +++ b/scripts/online_Analysis.py @@ -56,7 +56,7 @@ def burst_analysis( modify_fixed_pump_pattern: dict[str, list] = None, modify_variable_pump_pattern: dict[str, list] = None, modify_valve_opening: dict[str, float] = None, - scheme_Name: str = None, + scheme_name: str = None, ) -> None: """ 爆管模拟 @@ -182,8 +182,8 @@ def burst_analysis( modify_fixed_pump_pattern=modify_fixed_pump_pattern, modify_variable_pump_pattern=modify_variable_pump_pattern, modify_valve_opening=modify_valve_opening, - scheme_Type="burst_Analysis", - scheme_Name=scheme_Name, + scheme_type="burst_Analysis", + scheme_name=scheme_name, ) # step 3. restore the base model status # execute_undo(name) #有疑惑 @@ -193,7 +193,7 @@ def burst_analysis( # return result store_scheme_info( name=name, - scheme_name=scheme_Name, + scheme_name=scheme_name, scheme_type="burst_Analysis", username="admin", scheme_start_time=modify_pattern_start_time, @@ -209,7 +209,7 @@ def valve_close_analysis( modify_pattern_start_time: str, modify_total_duration: int = 900, modify_valve_opening: dict[str, float] = None, - scheme_Name: str = None, + scheme_name: str = None, ) -> None: """ 关阀模拟 @@ -217,7 +217,7 @@ def valve_close_analysis( :param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00' :param modify_total_duration: 模拟总历时,秒 :param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度 - :param scheme_Name: 方案名称 + :param scheme_name: 方案名称 :return: """ print( @@ -271,8 +271,8 @@ def valve_close_analysis( modify_pattern_start_time=modify_pattern_start_time, modify_total_duration=modify_total_duration, modify_valve_opening=modify_valve_opening, - scheme_Type="valve_close_Analysis", - scheme_Name=scheme_Name, + scheme_type="valve_close_Analysis", + scheme_name=scheme_name, ) # step 3. restore the base model # for valve in valves: