refactor(db)!: finalize pooled WNDB v2 migration

This commit is contained in:
2026-08-27 17:26:22 +08:00
parent fa188af0b1
commit b74799a39d
105 changed files with 4988 additions and 5565 deletions
+20 -24
View File
@@ -1,9 +1,7 @@
import numpy as np
from functools import wraps
from app.services.tjnetwork import (
ChangeSet,
close_project,
copy_project,
delete_project,
get_pattern,
get_patterns,
get_pump,
@@ -11,9 +9,6 @@ from app.services.tjnetwork import (
get_status,
get_tank,
get_time,
have_project,
is_project_open,
open_project,
read_all,
run_project,
set_pattern,
@@ -29,6 +24,7 @@ import pytz
import requests
import time
import app.services.project_info as project_info
from app.native.wndb.core.projects import temporary_project_database
from app.services.time_api import parse_clock_duration_seconds
url_path = 'http://10.101.15.16:9000/loong' # 内网
@@ -572,9 +568,6 @@ def trim_time_flag(url_date_time:str)->str:
# 单时间步长模拟
def run_simulation(name:str,start_datetime:str,end_datetime:str=None, duration:int=900)->str:
if(is_project_open(name)):
close_project(name)
open_project(name)
#get_current_data(cur_datetime)
#extract the patternindex from datetime
#e.g. 0: the first time step for 00:00-00:14; 1: the second step for 00:15-00:30
@@ -654,32 +647,38 @@ def run_simulation(name:str,start_datetime:str,end_datetime:str=None, duration:i
# 在线模拟
def _clean_extended_simulation(func):
@wraps(func)
def wrapper(name: str, simulation_type: str, *args, **kwargs):
if simulation_type.upper() != "EXTENDED":
return func(name, simulation_type, *args, **kwargs)
with temporary_project_database(name, "extended_simulation") as temporary:
kwargs["_temporary_project"] = temporary
return func(name, simulation_type, *args, **kwargs)
return wrapper
@_clean_extended_simulation
def run_simulation_ex(name: str, simulation_type: str, start_datetime: str,
end_datetime: str = None, duration: int = 0,
pump_control: dict[str, list] = None, tank_initial_level_control: dict[str, float] = None,
region_demand_control: dict[str, float] = None, valve_control: dict[str, dict] = None,
downloading_prohibition: bool = False) -> str:
downloading_prohibition: bool = False,
_temporary_project: str | None = None) -> str:
time_cost_start = time.perf_counter()
print('{} -- Hydraulic simulation started.'.format(
datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S')))
if is_project_open(name):
close_project(name)
if simulation_type.upper() == 'REALTIME': # 实时模拟(修改原数据库)
name_c = name
elif simulation_type.upper() == 'EXTENDED': # 扩展模拟(复制数据库)
name_c = '_'.join([name, 'c'])
if have_project(name_c):
if is_project_open(name_c):
close_project(name_c)
delete_project(name_c)
copy_project(name, name_c) # 备份项目
if _temporary_project is None:
raise RuntimeError("Extended simulation isolation was not prepared")
name_c = _temporary_project
else:
raise Exception('Incorrect simulation type, choose in (realtime, extended)')
open_project(name_c)
# 时间处理
# extract the pattern index from datetime
# e.g. 0: the first time step for 00:00-00:14; 1: the second step for 00:15-00:30
@@ -848,8 +847,6 @@ def run_simulation_ex(name: str, simulation_type: str, start_datetime: str,
datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S'),
time_cost_end - time_cost_start))
close_project(name_c)
return result
@@ -858,7 +855,6 @@ if __name__ == '__main__':
# tQ=get_current_total_Q()
# print(f"the current tQ is {tQ}\n")
# data=get_hist_data(ids,conver_beingtime_to_ucttime('2024-04-10 15:05:00'),conver_beingtime_to_ucttime('2024-04-10 15:10:00'))
# open_project("beibeizone")
# read_inp("beibeizone","beibeizone-export_nochinese.inp")
# run_simulation("beibeizone","2024-04-01T08:00:00Z")
# read_inp('bb_server', 'model20_en.inp')
+52 -101
View File
@@ -1,5 +1,6 @@
import json
from datetime import datetime
from functools import wraps
from math import pi, sqrt
import pytz
@@ -9,6 +10,7 @@ from app.algorithms.simulation.runner import (
run_simulation_ex,
from_clock_to_seconds_2,
)
from app.native.wndb.core.projects import temporary_project_database
from app.services.tjnetwork import (
ChangeSet,
OPTION_DEMAND_MODEL_PDA,
@@ -16,9 +18,6 @@ from app.services.tjnetwork import (
SOURCE_TYPE_SETPOINT,
add_pattern,
add_source,
close_project,
copy_project,
delete_project,
get_demand,
get_emitter,
get_node_links,
@@ -27,10 +26,7 @@ from app.services.tjnetwork import (
get_pipe,
get_source,
get_time,
have_project,
is_junction,
is_project_open,
open_project,
set_demand,
set_emitter,
set_option,
@@ -39,11 +35,23 @@ from app.services.tjnetwork import (
)
def _isolated_analysis(purpose: str):
def decorator(func):
@wraps(func)
def wrapper(name: str, *args, **kwargs):
with temporary_project_database(name, purpose) as temporary:
kwargs["_temporary_project"] = temporary
return func(name, *args, **kwargs)
return wrapper
return decorator
############################################################
# burst analysis 01
############################################################
def convert_to_local_unit(proj: str, emitters: float) -> float:
open_project(proj)
proj_opt = get_option(proj)
str_unit = proj_opt.get("UNITS")
@@ -61,6 +69,7 @@ def convert_to_local_unit(proj: str, emitters: float) -> float:
return emitters
@_isolated_analysis("burst_analysis")
def burst_analysis(
name: str,
modify_pattern_start_time: str,
@@ -72,6 +81,7 @@ def burst_analysis(
modify_valve_opening: dict[str, float] = None,
scheme_name: str = None,
username: str | None = None,
_temporary_project: str | None = None,
) -> None:
"""
爆管模拟
@@ -101,21 +111,17 @@ def burst_analysis(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"burst_Anal_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
if _temporary_project is None:
raise RuntimeError("Burst analysis isolation was not prepared")
new_name = _temporary_project
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
simulation.run_simulation(
name=new_name,
simulation_type="manually_temporary",
@@ -207,20 +213,19 @@ def burst_analysis(
)
# step 3. restore the base model status
# execute_undo(name) #有疑惑
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
############################################################
# valve closing analysis 02
############################################################
@_isolated_analysis("valve_close_analysis")
def valve_close_analysis(
name: str,
modify_pattern_start_time: str,
modify_total_duration: int = 900,
modify_valve_opening: dict[str, float] = None,
scheme_name: str = None,
_temporary_project: str | None = None,
) -> None:
"""
关阀模拟
@@ -235,21 +240,17 @@ def valve_close_analysis(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"valve_close_Anal_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
if _temporary_project is None:
raise RuntimeError("Valve-close analysis isolation was not prepared")
new_name = _temporary_project
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Database Loading OK."
@@ -287,9 +288,6 @@ def valve_close_analysis(
# step 3. restore the base model
# for valve in valves:
# execute_undo(name)
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# return result
@@ -297,6 +295,7 @@ def valve_close_analysis(
# flushing analysis 03
# Pipe_Flushing_Analysis(prj_name,date_time, Valve_id_list, Drainage_Node_Id, Flushing_flow[opt], Flushing_duration[opt])->out_file:string
############################################################
@_isolated_analysis("flushing_analysis")
def flushing_analysis(
name: str,
modify_pattern_start_time: str,
@@ -307,6 +306,7 @@ def flushing_analysis(
scheme_name: str = None,
username: str | None = None,
valve_control: dict[str, dict] = None,
_temporary_project: str | None = None,
) -> None:
"""
管道冲洗模拟
@@ -334,23 +334,17 @@ def flushing_analysis(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"flushing_Anal_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# if is_project_open(name):
# close_project(name)
if _temporary_project is None:
raise RuntimeError("Flushing analysis isolation was not prepared")
new_name = _temporary_project
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Database Loading OK."
@@ -449,9 +443,6 @@ def flushing_analysis(
scheme_detail=scheme_detail,
)
# step 4. restore the base model
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# return result
@@ -459,6 +450,7 @@ def flushing_analysis(
# Contaminant simulation 04
#
############################################################
@_isolated_analysis("contaminant_simulation")
def contaminant_simulation(
name: str,
modify_pattern_start_time: str, # 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
@@ -468,6 +460,7 @@ def contaminant_simulation(
scheme_name: str = None,
source_pattern: str = None, # 污染源时间变化模式名称
username: str | None = None,
_temporary_project: str | None = None,
) -> None:
"""
污染模拟
@@ -494,23 +487,17 @@ def contaminant_simulation(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"contaminant_Sim_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# if is_project_open(name):
# close_project(name)
if _temporary_project is None:
raise RuntimeError("Contaminant simulation isolation was not prepared")
new_name = _temporary_project
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Database Loading OK."
@@ -599,9 +586,6 @@ def contaminant_simulation(
# for i in range(1,operation_step):
# execute_undo(name)
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
############################################################
@@ -623,45 +607,19 @@ def age_analysis(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"age_Anal_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# if is_project_open(name):
# close_project(name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Database Loading OK."
)
# step 1. run simulation
with temporary_project_database(name, "age_analysis") as new_name:
result = run_simulation_ex(
new_name,
"realtime",
modify_pattern_start_time,
duration=modify_total_duration,
downloading_prohibition=True,
)
simulation_result = json.loads(result)
output_data = simulation_result.get("output")
if not isinstance(output_data, dict):
raise RuntimeError("run_simulation_ex did not return JSON output content")
result = run_simulation_ex(
new_name,
"realtime",
modify_pattern_start_time,
duration=modify_total_duration,
downloading_prohibition=True,
)
simulation_result = json.loads(result)
output_data = simulation_result.get("output")
if not isinstance(output_data, dict):
raise RuntimeError("run_simulation_ex did not return JSON output content")
# step 2. restore the base model status
# execute_undo(name) #有疑惑
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
nodes_age = []
node_result = output_data.get("node_results") or []
for node in node_result:
@@ -680,6 +638,7 @@ def age_analysis(
############################################################
@_isolated_analysis("pressure_regulation")
def pressure_regulation(
name: str,
modify_pattern_start_time: str,
@@ -688,6 +647,7 @@ def pressure_regulation(
modify_fixed_pump_pattern: dict[str, list] = None,
modify_variable_pump_pattern: dict[str, list] = None,
scheme_name: str = None,
_temporary_project: str | None = None,
) -> None:
"""
区域调压模拟,用来模拟未来15分钟内,开关水泵对区域压力的影响
@@ -704,23 +664,17 @@ def pressure_regulation(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Analysis."
)
new_name = f"pressure_regulation_{name}"
if have_project(new_name):
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# if is_project_open(name):
# close_project(name)
if _temporary_project is None:
raise RuntimeError("Pressure-regulation isolation was not prepared")
new_name = _temporary_project
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Copying Database."
)
copy_project(name + "_template", new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Start Opening Database."
)
open_project(new_name)
print(
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
+ " -- Database Loading OK."
@@ -751,7 +705,4 @@ def pressure_regulation(
scheme_name=scheme_name,
result_db_name=name,
)
if is_project_open(new_name):
close_project(new_name)
delete_project(new_name)
# return result
+14 -14
View File
@@ -1,8 +1,11 @@
from app.native.wndb.commands.executor import execute_batch_command
from app.native.wndb.core.database import ChangeSet
from app.native.wndb.gis.region_geometry import Topology, get_nodes_in_region
from app.native.wndb.model.demands import get_demand
from app.native.wndb.model.elements import get_nodes, is_junction
from app.native.wndb.gis.network_views import (
get_junction_demands,
sum_junction_base_demand,
)
from app.native.wndb.model.elements import get_nodes
DISTRIBUTION_TYPE_ADD = 'ADD'
@@ -28,7 +31,7 @@ def calculate_demand_to_nodes(name: str, demand: float, nodes: list[str]) -> dic
result: dict[str, float] = {}
for node, value in t_nodes.items():
if not is_junction(name, node):
if value["type"] != "junction":
continue
demand_per_node = 0.0
for link in value['links']:
@@ -68,15 +71,19 @@ def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type:
demand_per_length = demand / length_sum
cs = ChangeSet()
demands_by_junction = get_junction_demands(
name,
[node for node, value in t_nodes.items() if value["type"] == "junction"],
)
for node, value in t_nodes.items():
if not is_junction(name, node):
if value["type"] != "junction":
continue
demand_per_node = 0.0
for link in value['links']:
demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5
ds = get_demand(name, node)['demands']
ds = demands_by_junction.get(node, [])
if len(ds) == 0:
ds = [{'demand': demand_per_node, 'pattern': None, 'category': None}]
elif type == DISTRIBUTION_TYPE_ADD:
@@ -92,13 +99,6 @@ def distribute_demand_to_region(name: str, demand: float, region: str, type: str
nodes = get_nodes_in_region(name, region)
return distribute_demand_to_nodes(name, demand, nodes, type)
def get_total_base_demand(name:str,region:str)->float:
def get_total_base_demand(name: str, region: str) -> float:
nodes = get_nodes_in_region(name, region)
t_demands=0.0
for node in nodes:
if not is_junction(name, node):
continue
ds = get_demand(name, node)['demands']
t_demands= t_demands+ds[0]['demand']
return t_demands
return sum_junction_base_demand(name, nodes)