refactor(db)!: adopt project-routed pooled databases
Reorganize WNDB by responsibility and remove legacy scheme endpoints.\n\nRoute analysis and time-series access through project pools, preserve transactional realtime replacement, and refresh GIS materialized views after writes.\n\nAdd database architecture documentation, live pooling coverage, API contract updates, and executable container verification.\n\nBREAKING CHANGE: legacy scheme APIs and flat app.native.wndb module imports are removed.
This commit is contained in:
@@ -5,7 +5,7 @@ from typing import Any
|
||||
|
||||
from app.algorithms.sensor import kmeans as kmeans_sensor
|
||||
from app.algorithms.sensor import sensitivity
|
||||
from app.native.wndb.s42_sensor_placement import create_sensor_placement
|
||||
from app.infra.db.postgresql.sensor_placement import create_sensor_placement
|
||||
from app.services.sensor_placement import (
|
||||
SensorPlacementConflictError,
|
||||
SensorPlacementValidationError,
|
||||
@@ -50,18 +50,18 @@ def _sensor_inp_lock(name: str):
|
||||
def _create_validated_placement(
|
||||
name: str,
|
||||
*,
|
||||
scheme_name: str,
|
||||
run_name: str,
|
||||
min_diameter: int,
|
||||
username: str,
|
||||
sensor_location: list[str],
|
||||
created_by: str,
|
||||
sensor_locations: list[str],
|
||||
) -> dict[str, Any]:
|
||||
validate_sensor_placement_nodes(name, sensor_location)
|
||||
validate_sensor_placement_nodes(name, sensor_locations)
|
||||
return create_sensor_placement(
|
||||
name,
|
||||
scheme_name=scheme_name,
|
||||
run_name=run_name,
|
||||
min_diameter=min_diameter,
|
||||
username=username,
|
||||
sensor_location=sensor_location,
|
||||
created_by=created_by,
|
||||
sensor_locations=sensor_locations,
|
||||
)
|
||||
|
||||
|
||||
@@ -89,10 +89,10 @@ def pressure_sensor_placement_sensitivity(
|
||||
)
|
||||
return _create_validated_placement(
|
||||
name,
|
||||
scheme_name=scheme_name,
|
||||
run_name=scheme_name,
|
||||
min_diameter=min_diameter,
|
||||
username=username,
|
||||
sensor_location=sensor_location,
|
||||
created_by=username,
|
||||
sensor_locations=sensor_location,
|
||||
)
|
||||
|
||||
|
||||
@@ -124,8 +124,8 @@ def pressure_sensor_placement_kmeans(
|
||||
)
|
||||
return _create_validated_placement(
|
||||
name,
|
||||
scheme_name=scheme_name,
|
||||
run_name=scheme_name,
|
||||
min_diameter=min_diameter,
|
||||
username=username,
|
||||
sensor_location=sensor_location,
|
||||
created_by=username,
|
||||
sensor_locations=sensor_location,
|
||||
)
|
||||
|
||||
@@ -454,8 +454,12 @@ class DataLoader:
|
||||
"""读取选定pattern的保存的历史pattern信息(flow, factor)"""
|
||||
factors_list = []
|
||||
flow_list = []
|
||||
patterns_info = read_all(project_name,
|
||||
f"select * from history_patterns_flows where id = '{pattern_name}' order by _order")
|
||||
patterns_info = read_all(
|
||||
project_name,
|
||||
"select flow, factor from network.pattern_flow_samples "
|
||||
"where pattern_id = %s order by sequence_no",
|
||||
(pattern_name,),
|
||||
)
|
||||
for item in patterns_info:
|
||||
flow_list.append(float(item['flow']))
|
||||
factors_list.append(float(item['factor']))
|
||||
|
||||
@@ -9,7 +9,6 @@ from app.algorithms.simulation.runner import (
|
||||
run_simulation_ex,
|
||||
from_clock_to_seconds_2,
|
||||
)
|
||||
from app.services.scheme_management import store_scheme_info
|
||||
from app.services.tjnetwork import (
|
||||
ChangeSet,
|
||||
OPTION_DEMAND_MODEL_PDA,
|
||||
@@ -111,8 +110,6 @@ def burst_analysis(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -204,21 +201,15 @@ def burst_analysis(
|
||||
modify_valve_opening=modify_valve_opening,
|
||||
scheme_type="burst_analysis",
|
||||
scheme_name=scheme_name,
|
||||
result_db_name=name,
|
||||
scheme_username=username,
|
||||
scheme_detail=scheme_detail,
|
||||
)
|
||||
# step 3. restore the base model status
|
||||
# execute_undo(name) #有疑惑
|
||||
if is_project_open(new_name):
|
||||
close_project(new_name)
|
||||
delete_project(new_name)
|
||||
# 存储方案信息到 PG 数据库
|
||||
store_scheme_info(
|
||||
name=name,
|
||||
scheme_name=scheme_name,
|
||||
scheme_type="burst_analysis",
|
||||
username=username,
|
||||
scheme_start_time=modify_pattern_start_time,
|
||||
scheme_detail=scheme_detail,
|
||||
)
|
||||
|
||||
|
||||
############################################################
|
||||
@@ -253,8 +244,6 @@ def valve_close_analysis(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -293,6 +282,7 @@ def valve_close_analysis(
|
||||
modify_valve_opening=modify_valve_opening,
|
||||
scheme_type="valve_close_Analysis",
|
||||
scheme_name=scheme_name,
|
||||
result_db_name=name,
|
||||
)
|
||||
# step 3. restore the base model
|
||||
# for valve in valves:
|
||||
@@ -355,8 +345,6 @@ def flushing_analysis(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -456,21 +444,15 @@ def flushing_analysis(
|
||||
valve_control=valve_control,
|
||||
scheme_type="flushing_analysis",
|
||||
scheme_name=scheme_name,
|
||||
result_db_name=name,
|
||||
scheme_username=username,
|
||||
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
|
||||
# 存储方案信息到 PG 数据库
|
||||
store_scheme_info(
|
||||
name=name,
|
||||
scheme_name=scheme_name,
|
||||
scheme_type="flushing_analysis",
|
||||
username=username,
|
||||
scheme_start_time=modify_pattern_start_time,
|
||||
scheme_detail=scheme_detail,
|
||||
)
|
||||
|
||||
|
||||
############################################################
|
||||
@@ -523,8 +505,6 @@ def contaminant_simulation(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -612,6 +592,9 @@ def contaminant_simulation(
|
||||
modify_total_duration=modify_total_duration,
|
||||
scheme_type="contaminant_analysis",
|
||||
scheme_name=scheme_name,
|
||||
result_db_name=name,
|
||||
scheme_username=username,
|
||||
scheme_detail=scheme_detail,
|
||||
)
|
||||
|
||||
# for i in range(1,operation_step):
|
||||
@@ -619,15 +602,6 @@ def contaminant_simulation(
|
||||
if is_project_open(new_name):
|
||||
close_project(new_name)
|
||||
delete_project(new_name)
|
||||
# 存储方案信息到 PG 数据库
|
||||
store_scheme_info(
|
||||
name=name,
|
||||
scheme_name=scheme_name,
|
||||
scheme_type="contaminant_analysis",
|
||||
username=username,
|
||||
scheme_start_time=modify_pattern_start_time,
|
||||
scheme_detail=scheme_detail,
|
||||
)
|
||||
|
||||
|
||||
############################################################
|
||||
@@ -660,8 +634,6 @@ def age_analysis(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -743,8 +715,6 @@ def pressure_regulation(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
+ " -- Start Copying Database."
|
||||
)
|
||||
# CopyProjectEx()(name, new_name,
|
||||
# ['operation', 'current_operation', 'restore_operation', 'batch_operation', 'operation_table'])
|
||||
copy_project(name + "_template", new_name)
|
||||
print(
|
||||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -779,6 +749,7 @@ def pressure_regulation(
|
||||
modify_variable_pump_pattern=modify_variable_pump_pattern,
|
||||
scheme_type="pressure_regulation",
|
||||
scheme_name=scheme_name,
|
||||
result_db_name=name,
|
||||
)
|
||||
if is_project_open(new_name):
|
||||
close_project(new_name)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Water-demand distribution algorithms."""
|
||||
|
||||
from .service import (
|
||||
calculate_demand_to_network,
|
||||
calculate_demand_to_nodes,
|
||||
calculate_demand_to_region,
|
||||
distribute_demand_to_nodes,
|
||||
distribute_demand_to_region,
|
||||
get_total_base_demand,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"calculate_demand_to_network",
|
||||
"calculate_demand_to_nodes",
|
||||
"calculate_demand_to_region",
|
||||
"distribute_demand_to_nodes",
|
||||
"distribute_demand_to_region",
|
||||
"get_total_base_demand",
|
||||
]
|
||||
@@ -0,0 +1,104 @@
|
||||
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
|
||||
|
||||
|
||||
DISTRIBUTION_TYPE_ADD = 'ADD'
|
||||
DISTRIBUTION_TYPE_OVERRIDE = 'OVERRIDE'
|
||||
|
||||
|
||||
def calculate_demand_to_nodes(name: str, demand: float, nodes: list[str]) -> dict[str, float]:
|
||||
if len(nodes) == 0 or demand == 0.0:
|
||||
return {}
|
||||
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
length_sum = 0.0
|
||||
for value in t_links.values():
|
||||
length_sum += abs(value['length'])
|
||||
|
||||
if length_sum <= 0.0:
|
||||
return {}
|
||||
|
||||
demand_per_length = demand / length_sum
|
||||
|
||||
result: dict[str, float] = {}
|
||||
for node, value in t_nodes.items():
|
||||
if not is_junction(name, node):
|
||||
continue
|
||||
demand_per_node = 0.0
|
||||
for link in value['links']:
|
||||
demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5
|
||||
result[node] = demand_per_node
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def calculate_demand_to_region(name: str, demand: float, region: str) -> dict[str, float]:
|
||||
nodes = get_nodes_in_region(name, region)
|
||||
return calculate_demand_to_nodes(name, demand, nodes)
|
||||
|
||||
|
||||
def calculate_demand_to_network(name: str, demand: float) -> dict[str, float]:
|
||||
nodes = get_nodes(name)
|
||||
return calculate_demand_to_nodes(name, demand, nodes)
|
||||
|
||||
|
||||
def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
|
||||
if len(nodes) == 0 or demand == 0.0:
|
||||
return ChangeSet()
|
||||
if type != DISTRIBUTION_TYPE_ADD and type != DISTRIBUTION_TYPE_OVERRIDE:
|
||||
return ChangeSet()
|
||||
|
||||
topology = Topology(name, nodes)
|
||||
t_nodes = topology.nodes()
|
||||
t_links = topology.links()
|
||||
|
||||
length_sum = 0.0
|
||||
for value in t_links.values():
|
||||
length_sum += abs(value['length'])
|
||||
|
||||
if length_sum <= 0.0:
|
||||
return ChangeSet()
|
||||
|
||||
demand_per_length = demand / length_sum
|
||||
|
||||
cs = ChangeSet()
|
||||
|
||||
for node, value in t_nodes.items():
|
||||
if not is_junction(name, node):
|
||||
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']
|
||||
if len(ds) == 0:
|
||||
ds = [{'demand': demand_per_node, 'pattern': None, 'category': None}]
|
||||
elif type == DISTRIBUTION_TYPE_ADD:
|
||||
ds[0]['demand'] += demand_per_node
|
||||
else:
|
||||
ds[0]['demand'] = demand_per_node
|
||||
cs.update({'type': 'demand', 'junction': node, 'demands': ds})
|
||||
|
||||
return execute_batch_command(name, cs)
|
||||
|
||||
|
||||
def distribute_demand_to_region(name: str, demand: float, region: str, type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet:
|
||||
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:
|
||||
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
|
||||
Reference in New Issue
Block a user