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:
2026-08-25 18:35:05 +08:00
parent fdbcc5c033
commit fa188af0b1
181 changed files with 8446 additions and 33546 deletions
+4 -478
View File
@@ -1,480 +1,6 @@
"""`app.native.wndb` 的公共 API 门面。
"""Water-network database package.
调用建议:
- 推荐使用模块方式导入,保持调用点清晰:
`import app.native.wndb as wndb`
- 典型流程:
1) 项目生命周期:`open_project(...)` / `close_project(...)`
2) 模型数据读写:`get_*`, `set_*`, `add_*`, `delete_*`
3) 持久化与恢复:`take_snapshot(...)`, `execute_undo()`, `restore(...)`
该文件刻意保持平铺导出,以兼容历史调用。
Import from the responsibility-specific modules under ``core``, ``model``,
``gis``, ``inp``, and ``commands``. This package intentionally does not expose
the former flat compatibility API.
"""
# -----------------------------------------------------------------------------
# 项目生命周期与 INP 导入导出
# -----------------------------------------------------------------------------
from .project import (
list_project,
have_project,
create_project,
delete_project,
clean_project,
)
from .project import is_project_open, open_project, close_project
from .project import copy_project
# DingZQ, 2024-12-28: 将 INP v3 转换为 v2
from .inp_in import read_inp, import_inp, convert_inp_v3_to_v2
from .inp_out import dump_inp, export_inp
# -----------------------------------------------------------------------------
# 数据库操作、快照与撤销重做
# -----------------------------------------------------------------------------
from .database import API_ADD, API_UPDATE, API_DELETE
from .database import ChangeSet
from .database import get_current_operation
from .database import execute_undo, execute_redo
from .database import list_snapshot
from .database import (
have_snapshot,
have_snapshot_for_operation,
have_snapshot_for_current_operation,
)
from .database import (
take_snapshot_for_operation,
take_snapshot_for_current_operation,
take_snapshot,
)
from .database import update_snapshot, update_snapshot_for_current_operation
from .database import delete_snapshot, delete_snapshot_by_operation
from .database import get_operation_by_snapshot, get_snapshot_by_operation
from .database import pick_snapshot
from .database import pick_operation, sync_with_server
from .database import (
get_restore_operation,
set_restore_operation,
set_restore_operation_to_current,
restore,
)
from .database import read, try_read, read_all, write
# -----------------------------------------------------------------------------
# 批处理执行与扩展数据
# -----------------------------------------------------------------------------
from .batch_exe import execute_batch_commands, execute_batch_command
from .extension_data import (
get_all_extension_data_keys,
get_all_extension_data,
get_extension_data,
set_extension_data,
)
# -----------------------------------------------------------------------------
# 核心网络模型基础类型与辅助方法
# -----------------------------------------------------------------------------
from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE
from .s0_base import is_node, is_junction, is_reservoir, is_tank
from .s0_base import is_link, is_pipe, is_pump, is_valve
from .s0_base import is_curve
from .s0_base import is_pattern
from .s0_base import (
get_nodes,
get_nodes_id_and_type,
get_junctions,
get_reservoirs,
get_tanks,
get_links,
get_links_id_and_type,
get_pipes,
get_pumps,
get_valves,
get_curves,
get_patterns,
)
from .s0_base import (
get_node_type,
get_link_type,
get_element_type,
get_element_type_value,
)
from .s0_base import get_node_links, get_link_nodes
from .s0_base import get_major_nodes, get_major_pipes
# -----------------------------------------------------------------------------
# EPANET 基础分段(S1-S27
# -----------------------------------------------------------------------------
from .s1_title import get_title_schema, get_title, set_title
from .s2_junctions import (
get_junction_schema,
add_junction,
get_junction,
set_junction,
get_all_junctions,
)
from .batch_api import delete_junction_cascade
from .s3_reservoirs import (
get_reservoir_schema,
add_reservoir,
get_reservoir,
set_reservoir,
get_all_reservoirs,
)
from .batch_api import delete_reservoir_cascade
from .s4_tanks import OVERFLOW_YES, OVERFLOW_NO
from .s4_tanks import get_tank_schema, add_tank, get_tank, set_tank, get_all_tanks
from .batch_api import delete_tank_cascade
from .s5_pipes import PIPE_STATUS_OPEN, PIPE_STATUS_CLOSED, PIPE_STATUS_CV
from .s5_pipes import (
get_pipe_schema,
add_pipe,
get_pipe,
set_pipe,
get_all_pipes,
get_pipes_by_property,
)
from .batch_api import delete_pipe_cascade
from .s6_pumps import get_pump_schema, add_pump, get_pump, set_pump, get_all_pumps
from .batch_api import delete_pump_cascade
from .s7_valves import (
VALVES_TYPE_PRV,
VALVES_TYPE_PSV,
VALVES_TYPE_PBV,
VALVES_TYPE_FCV,
VALVES_TYPE_TCV,
VALVES_TYPE_GPV,
)
from .s7_valves import get_valve_schema, add_valve, get_valve, set_valve, get_all_valves
from .batch_api import delete_valve_cascade
from .s8_tags import TAG_TYPE_NODE, TAG_TYPE_LINK
from .s8_tags import get_tag_schema, get_tags, get_tag, set_tag
from .s9_demands import get_demand_schema, get_demand, set_demand
from .s10_status import LINK_STATUS_OPEN, LINK_STATUS_CLOSED, LINK_STATUS_ACTIVE
from .s10_status import get_status_schema, get_status, set_status
from .s11_patterns import get_pattern_schema, get_pattern, set_pattern, add_pattern
from .batch_api import delete_pattern_cascade
from .s12_curves import (
CURVE_TYPE_PUMP,
CURVE_TYPE_EFFICIENCY,
CURVE_TYPE_VOLUME,
CURVE_TYPE_HEADLOSS,
)
from .s12_curves import get_curve_schema, get_curve, set_curve, add_curve
from .batch_api import delete_curve_cascade
from .s13_controls import get_control_schema, get_control, set_control
from .s14_rules import get_rule_schema, get_rule, set_rule
from .s15_energy import get_energy_schema, get_energy, set_energy
from .s15_energy import get_pump_energy_schema, get_pump_energy, set_pump_energy
from .s16_emitters import get_emitter_schema, get_emitter, set_emitter
from .s17_quality import get_quality_schema, get_quality, set_quality
from .s18_sources import (
SOURCE_TYPE_CONCEN,
SOURCE_TYPE_MASS,
SOURCE_TYPE_FLOWPACED,
SOURCE_TYPE_SETPOINT,
)
from .s18_sources import (
get_source_schema,
get_source,
set_source,
add_source,
delete_source,
)
from .s19_reactions import get_reaction_schema, get_reaction, set_reaction
from .s19_reactions import (
get_pipe_reaction_schema,
get_pipe_reaction,
set_pipe_reaction,
)
from .s19_reactions import (
get_tank_reaction_schema,
get_tank_reaction,
set_tank_reaction,
)
from .s20_mixing import (
MIXING_MODEL_MIXED,
MIXING_MODEL_2COMP,
MIXING_MODEL_FIFO,
MIXING_MODEL_LIFO,
)
from .s20_mixing import (
get_mixing_schema,
get_mixing,
set_mixing,
add_mixing,
delete_mixing,
)
from .s21_times import (
TIME_STATISTIC_NONE,
TIME_STATISTIC_AVERAGED,
TIME_STATISTIC_MINIMUM,
TIME_STATISTIC_MAXIMUM,
TIME_STATISTIC_RANGE,
)
from .s21_times import get_time_schema, get_time, set_time
from .s23_options_util import (
OPTION_UNITS_CFS,
OPTION_UNITS_GPM,
OPTION_UNITS_MGD,
OPTION_UNITS_IMGD,
OPTION_UNITS_AFD,
OPTION_UNITS_LPS,
OPTION_UNITS_LPM,
OPTION_UNITS_MLD,
OPTION_UNITS_CMH,
OPTION_UNITS_CMD,
)
from .s23_options_util import (
OPTION_PRESSURE_PSI,
OPTION_PRESSURE_KPA,
OPTION_PRESSURE_METERS,
)
from .s23_options_util import OPTION_HEADLOSS_HW, OPTION_HEADLOSS_DW, OPTION_HEADLOSS_CM
from .s23_options_util import OPTION_UNBALANCED_STOP, OPTION_UNBALANCED_CONTINUE
from .s23_options_util import OPTION_DEMAND_MODEL_DDA, OPTION_DEMAND_MODEL_PDA
from .s23_options_util import (
OPTION_QUALITY_NONE,
OPTION_QUALITY_CHEMICAL,
OPTION_QUALITY_AGE,
OPTION_QUALITY_TRACE,
)
from .s23_options_util import get_option_schema, get_option
from .batch_api import set_option_ex
from .s23_options_util import (
OPTION_V3_FLOW_UNITS_CFS,
OPTION_V3_FLOW_UNITS_GPM,
OPTION_V3_FLOW_UNITS_MGD,
OPTION_V3_FLOW_UNITS_IMGD,
OPTION_V3_FLOW_UNITS_AFD,
OPTION_V3_FLOW_UNITS_LPS,
OPTION_V3_FLOW_UNITS_LPM,
OPTION_V3_FLOW_UNITS_MLD,
OPTION_V3_FLOW_UNITS_CMH,
OPTION_V3_FLOW_UNITS_CMD,
)
from .s23_options_util import (
OPTION_V3_PRESSURE_UNITS_PSI,
OPTION_V3_PRESSURE_UNITS_KPA,
OPTION_V3_PRESSURE_UNITS_METERS,
)
from .s23_options_util import (
OPTION_V3_HEADLOSS_MODEL_HW,
OPTION_V3_HEADLOSS_MODEL_DW,
OPTION_V3_HEADLOSS_MODEL_CM,
)
from .s23_options_util import (
OPTION_V3_STEP_SIZING_FULL,
OPTION_V3_STEP_SIZING_RELAXATION,
OPTION_V3_STEP_SIZING_LINESEARCH,
)
from .s23_options_util import (
OPTION_V3_IF_UNBALANCED_STOP,
OPTION_V3_IF_UNBALANCED_CONTINUE,
)
from .s23_options_util import (
OPTION_V3_DEMAND_MODEL_FIXED,
OPTION_V3_DEMAND_MODEL_CONSTRAINED,
OPTION_V3_DEMAND_MODEL_POWER,
OPTION_V3_DEMAND_MODEL_LOGISTIC,
)
from .s23_options_util import (
OPTION_V3_LEAKAGE_MODEL_NONE,
OPTION_V3_LEAKAGE_MODEL_POWER,
OPTION_V3_LEAKAGE_MODEL_FAVAD,
)
from .s23_options_util import (
OPTION_V3_QUALITY_MODEL_NONE,
OPTION_V3_QUALITY_MODEL_CHEMICAL,
OPTION_V3_QUALITY_MODEL_AGE,
OPTION_V3_QUALITY_MODEL_TRACE,
)
from .s23_options_util import (
OPTION_V3_QUALITY_UNITS_HRS,
OPTION_V3_QUALITY_UNITS_PCNT,
OPTION_V3_QUALITY_UNITS_MGL,
OPTION_V3_QUALITY_UNITS_UGL,
)
from .s23_options_util import get_option_v3_schema, get_option_v3
from .batch_api import set_option_v3_ex
from .s24_coordinates import (
get_links_in_extent,
get_node_coord,
get_nodes_in_extent,
)
from .s25_vertices import (
get_vertex_schema,
get_vertex,
set_vertex,
add_vertex,
delete_vertex,
)
from .s25_vertices import get_all_vertex_links, get_all_vertices
from .s26_labels import get_label_schema, get_label, set_label, add_label, delete_label
from .s27_backdrop import get_backdrop_schema, get_backdrop, set_backdrop
# -----------------------------------------------------------------------------
# SCADA 映射与遥测实体
# -----------------------------------------------------------------------------
from .s29_scada_device import (
SCADA_DEVICE_TYPE_PRESSURE,
SCADA_DEVICE_TYPE_DEMAND,
SCADA_DEVICE_TYPE_QUALITY,
SCADA_DEVICE_TYPE_LEVEL,
SCADA_DEVICE_TYPE_FLOW,
SCADA_DEVICE_TYPE_UNKNOWN,
)
from .s29_scada_device import (
get_scada_device_schema,
get_scada_device,
set_scada_device,
add_scada_device,
delete_scada_device,
)
from .s29_scada_device import get_all_scada_device_ids, get_all_scada_devices
from .clean_api import clean_scada_device
from .s30_scada_device_data import (
get_scada_device_data_schema,
get_scada_device_data,
set_scada_device_data,
add_scada_device_data,
delete_scada_device_data,
)
from .clean_api import clean_scada_device_data
from .s31_scada_element import (
SCADA_MODEL_TYPE_JUNCTION,
SCADA_MODEL_TYPE_RESERVOIR,
SCADA_MODEL_TYPE_TANK,
SCADA_MODEL_TYPE_PIPE,
SCADA_MODEL_TYPE_PUMP,
SCADA_MODEL_TYPE_VALVE,
)
from .s31_scada_element import SCADA_ELEMENT_STATUS_OFFLINE, SCADA_ELEMENT_STATUS_ONLINE
from .s31_scada_element import (
get_scada_element_schema,
get_scada_element,
set_scada_element,
add_scada_element,
delete_scada_element,
)
from .s31_scada_element import get_all_scada_element_ids, get_all_scada_elements
from .clean_api import clean_scada_element
# -----------------------------------------------------------------------------
# 区域、DMA、服务分区、虚拟分区与需水分配
# -----------------------------------------------------------------------------
from .s32_region_util import (
get_nodes_in_boundary,
get_nodes_in_region,
get_links_on_region_boundary,
calculate_convex_hull,
calculate_boundary,
inflate_boundary,
inflate_region,
)
from .s32_region import (
get_region_schema,
get_region,
set_region,
add_region,
delete_region,
)
from .s33_dma_cal import PARTITION_TYPE_RB, PARTITION_TYPE_KWAY
from .s33_dma_cal import (
calculate_district_metering_area_for_nodes,
calculate_district_metering_area_for_region,
calculate_district_metering_area_for_network,
)
from .s33_dma import (
get_district_metering_area_schema,
get_district_metering_area,
set_district_metering_area,
add_district_metering_area,
delete_district_metering_area,
)
from .s33_dma import get_all_district_metering_area_ids, get_all_district_metering_areas
from .s33_dma_gen import (
generate_district_metering_area,
generate_sub_district_metering_area,
)
from .s34_sa_cal import calculate_service_area
from .s34_sa import (
get_service_area_schema,
get_service_area,
set_service_area,
add_service_area,
delete_service_area,
)
from .s34_sa import get_all_service_area_ids, get_all_service_areas
from .s34_sa_gen import generate_service_area
from .s35_vd_cal import calculate_virtual_district
from .s35_vd import (
get_virtual_district_schema,
get_virtual_district,
set_virtual_district,
add_virtual_district,
delete_virtual_district,
)
from .s35_vd import get_all_virtual_district_ids, get_all_virtual_districts
from .s35_vd_gen import generate_virtual_district
from .s36_wda_cal import (
calculate_demand_to_nodes,
calculate_demand_to_region,
calculate_demand_to_network,
)
# -----------------------------------------------------------------------------
# 元数据与高级分析
# -----------------------------------------------------------------------------
from .s38_scada_info import get_scada_info_schema, get_scada_info, get_all_scada_info
from .s40_schema import get_scheme_schema, get_scheme, get_all_schemes
from .s41_pipe_risk_probability import (
get_pipe_risk_probability_now,
get_pipe_risk_probability,
get_network_pipe_risk_probability_now,
get_pipes_risk_probability,
get_pipe_risk_probability_geometries,
)
from .s42_sensor_placement import (
get_all_sensor_placements,
get_sensor_placement,
get_sensor_placement_nodes,
update_sensor_placement,
)
from .s43_burst_locate_result import get_all_burst_locate_results
-53
View File
@@ -1,53 +0,0 @@
from .sections import *
from .database import ChangeSet, API_DELETE, API_UPDATE
from .batch_exe import execute_batch_command
def delete_junction_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s2_junction }
return execute_batch_command(name, cs)
def delete_reservoir_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s3_reservoir }
return execute_batch_command(name, cs)
def delete_tank_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s4_tank }
return execute_batch_command(name, cs)
def delete_pipe_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s5_pipe }
return execute_batch_command(name, cs)
def delete_pump_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s6_pump }
return execute_batch_command(name, cs)
def delete_valve_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s7_valve }
return execute_batch_command(name, cs)
def delete_pattern_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s11_pattern }
return execute_batch_command(name, cs)
def delete_curve_cascade(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s12_curve }
return execute_batch_command(name, cs)
def set_option_ex(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option }
return execute_batch_command(name, cs)
def set_option_v3_ex(name: str, cs: ChangeSet) -> ChangeSet:
cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option_v3 }
return execute_batch_command(name, cs)
-238
View File
@@ -1,238 +0,0 @@
from .database import ChangeSet, g_delete_prefix, API_DELETE, API_UPDATE, try_read
from .sections import *
from .s0_base import *
from .s3_reservoirs import unset_reservoir_by_pattern
from .s4_tanks import unset_tank_by_curve
from .s6_pumps import unset_pump_by_curve, unset_pump_by_pattern
from .s8_tags import delete_tag_by_node, delete_tag_by_link
from .s9_demands import delete_demand_by_junction, unset_demand_by_pattern
from .s10_status import delete_status_by_link
from .s15_energy import delete_pump_energy_by_pump, unset_pump_energy_by_pattern, unset_pump_energy_by_curve
from .s16_emitters import delete_emitter_by_junction
from .s17_quality import delete_quality_by_node
from .s18_sources import delete_source_by_node, unset_source_by_pattern
from .s19_reactions import delete_pipe_reaction_by_pipe, delete_tank_reaction_by_tank
from .s20_mixing import delete_mixing_by_tank
from .s25_vertices import delete_vertex_by_link
from .s26_labels import unset_label_by_node
from .s23_options_util import generate_v2, generate_v3
def delete_junction_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from junctions where id = '{id}'")
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_demand_by_junction(name, id))
result.merge(delete_emitter_by_junction(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def delete_reservoir_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from reservoirs where id = '{id}'")
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def delete_tank_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from tanks where id = '{id}'")
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(delete_tank_reaction_by_tank(name, id))
result.merge(delete_mixing_by_tank(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def delete_pipe_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from pipes where id = '{id}'")
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_pipe_reaction_by_pipe(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def delete_pump_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from pumps where id = '{id}'")
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_pump_energy_by_pump(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def delete_valve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from valves where id = '{id}'")
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def delete_pattern_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from _pattern where id = '{id}'")
if row == None:
return result
result.merge(unset_reservoir_by_pattern(name, id))
result.merge(unset_pump_by_pattern(name, id))
result.merge(unset_demand_by_pattern(name, id))
result.merge(unset_pump_energy_by_pattern(name, id))
result.merge(unset_source_by_pattern(name, id))
result.merge(cs)
return result
def delete_curve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, f"select * from _curve where id = '{id}'")
if row == None:
return result
result.merge(unset_tank_by_curve(name, id))
result.merge(unset_pump_by_curve(name, id))
result.merge(unset_pump_energy_by_curve(name, id))
result.merge(cs)
return result
def set_option_cs(cs: ChangeSet) -> ChangeSet:
cs.operations[0]['operation'] = API_UPDATE
cs.operations[0]['type'] = 'option'
new_cs = cs
new_cs.merge(generate_v3(cs))
return new_cs
def set_option_v3_cs(cs: ChangeSet) -> ChangeSet:
cs.operations[0]['operation'] = API_UPDATE
cs.operations[0]['type'] = 'option_v3'
new_cs = cs
new_cs.merge(generate_v2(cs))
return new_cs
def rewrite_batch_api(name: str, cs: ChangeSet) -> ChangeSet:
op = cs.operations[0]
api = op['operation']
type = op['type']
if api == API_DELETE:
if type == s2_junction:
return delete_junction_cascade_batch_cs(name, cs)
elif type == s3_reservoir:
return delete_reservoir_cascade_batch_cs(name, cs)
elif type == s4_tank:
return delete_tank_cascade_batch_cs(name, cs)
elif type == s5_pipe:
return delete_pipe_cascade_batch_cs(name, cs)
elif type == s6_pump:
return delete_pump_cascade_batch_cs(name, cs)
elif type == s7_valve:
return delete_valve_cascade_batch_cs(name, cs)
elif type == s11_pattern:
return delete_pattern_cascade_batch_cs(name, cs)
elif type == s12_curve:
return delete_curve_cascade_batch_cs(name, cs)
elif api == API_UPDATE:
if type == s23_option:
return set_option_cs(cs)
elif type == s23_option_v3:
return set_option_v3_cs(cs)
return cs
-380
View File
@@ -1,380 +0,0 @@
from typing import Any
from .sections import *
from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet, write, read, read_all, get_current_operation
from .extension_data import set_extension_data
from .s1_title import set_title
from .s2_junctions import set_junction, add_junction, delete_junction
from .s3_reservoirs import set_reservoir, add_reservoir, delete_reservoir
from .s4_tanks import set_tank, add_tank, delete_tank
from .s5_pipes import set_pipe, add_pipe, delete_pipe
from .s6_pumps import set_pump, add_pump, delete_pump
from .s7_valves import set_valve, add_valve, delete_valve
from .s8_tags import set_tag
from .s9_demands import set_demand
from .s10_status import set_status
from .s11_patterns import set_pattern, add_pattern, delete_pattern
from .s12_curves import set_curve, add_curve, delete_curve
from .s13_controls import set_control
from .s14_rules import set_rule
from .s15_energy import set_energy, set_pump_energy
from .s16_emitters import set_emitter
from .s17_quality import set_quality
from .s18_sources import set_source, add_source, delete_source
from .s19_reactions import set_reaction, set_pipe_reaction, set_tank_reaction
from .s20_mixing import set_mixing, add_mixing, delete_mixing
from .s21_times import set_time
from .s23_options_util import set_option, set_option_v3
from .s25_vertices import set_vertex, add_vertex, delete_vertex
from .s26_labels import set_label, add_label, delete_label
from .s27_backdrop import set_backdrop
from .s29_scada_device import set_scada_device, add_scada_device, delete_scada_device
from .s30_scada_device_data import set_scada_device_data, add_scada_device_data, delete_scada_device_data
from .s31_scada_element import set_scada_element, add_scada_element, delete_scada_element
from .s32_region import set_region, add_region, delete_region
from .s33_dma import set_district_metering_area, add_district_metering_area, delete_district_metering_area
from .s34_sa import set_service_area, add_service_area, delete_service_area
from .s35_vd import set_virtual_district, add_virtual_district, delete_virtual_district
from .batch_api_cs import rewrite_batch_api
def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == s1_title:
return ChangeSet()
if type == s2_junction:
return add_junction(name, cs)
elif type == s3_reservoir:
return add_reservoir(name, cs)
elif type == s4_tank:
return add_tank(name, cs)
elif type == s5_pipe:
return add_pipe(name, cs)
elif type == s6_pump:
return add_pump(name, cs)
elif type == s7_valve:
return add_valve(name, cs)
elif type == s8_tag:
return ChangeSet()
elif type == s9_demand:
return ChangeSet()
elif type == s10_status:
return ChangeSet()
elif type == s11_pattern:
return add_pattern(name, cs)
elif type == s12_curve:
return add_curve(name, cs)
elif type == s13_control:
return ChangeSet()
elif type == s14_rule:
return ChangeSet()
elif type == s15_energy:
return ChangeSet()
elif type == s15_pump_energy:
return ChangeSet()
elif type == s16_emitter:
return ChangeSet()
elif type == s17_quality:
return ChangeSet()
elif type == s18_source:
return add_source(name, cs)
elif type == s19_reaction:
return ChangeSet()
elif type == s19_pipe_reaction:
return ChangeSet()
elif type == s19_tank_reaction:
return ChangeSet()
elif type == s20_mixing:
return add_mixing(name, cs)
elif type == s21_time:
return ChangeSet()
elif type == s22_report:
return ChangeSet()
elif type == s23_option:
return ChangeSet()
elif type == s23_option_v3:
return ChangeSet()
elif type == s24_coordinate:
return ChangeSet()
elif type == s25_vertex:
return add_vertex(name, cs)
elif type == s26_label:
return add_label(name, cs)
elif type == s27_backdrop:
return ChangeSet()
elif type == s28_end:
return ChangeSet()
elif type == s29_scada_device:
return add_scada_device(name, cs)
elif type == s30_scada_device_data:
return add_scada_device_data(name, cs)
elif type == s31_scada_element:
return add_scada_element(name, cs)
elif type == s32_region:
return add_region(name, cs)
elif type == s33_dma:
return add_district_metering_area(name, cs)
elif type == s34_sa:
return add_service_area(name, cs)
elif type == s35_vd:
return add_virtual_district(name, cs)
return ChangeSet()
def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == 'extension_data':
return set_extension_data(name, cs)
if type == s1_title:
return set_title(name, cs)
if type == s2_junction:
return set_junction(name, cs)
elif type == s3_reservoir:
return set_reservoir(name, cs)
elif type == s4_tank:
return set_tank(name, cs)
elif type == s5_pipe:
return set_pipe(name, cs)
elif type == s6_pump:
return set_pump(name, cs)
elif type == s7_valve:
return set_valve(name, cs)
elif type == s8_tag:
return set_tag(name, cs)
elif type == s9_demand:
return set_demand(name, cs)
elif type == s10_status:
return set_status(name, cs)
elif type == s11_pattern:
return set_pattern(name, cs)
elif type == s12_curve:
return set_curve(name, cs)
elif type == s13_control:
return set_control(name, cs)
elif type == s14_rule:
return set_rule(name, cs)
elif type == s15_energy:
return set_energy(name, cs)
elif type == s15_pump_energy:
return set_pump_energy(name, cs)
elif type == s16_emitter:
return set_emitter(name, cs)
elif type == s17_quality:
return set_quality(name, cs)
elif type == s18_source:
return set_source(name, cs)
elif type == s19_reaction:
return set_reaction(name, cs)
elif type == s19_pipe_reaction:
return set_pipe_reaction(name, cs)
elif type == s19_tank_reaction:
return set_tank_reaction(name, cs)
elif type == s20_mixing:
return set_mixing(name, cs)
elif type == s21_time:
return set_time(name, cs)
elif type == s22_report: # no api now
return ChangeSet()
elif type == s23_option:
return set_option(name, cs)
elif type == s23_option_v3:
return set_option_v3(name, cs)
elif type == s24_coordinate: # do not support update here
return ChangeSet()
elif type == s25_vertex:
return set_vertex(name, cs)
elif type == s26_label:
return set_label(name, cs)
elif type == s27_backdrop:
return set_backdrop(name, cs)
elif type == s28_end: # end
return ChangeSet()
elif type == s29_scada_device:
return set_scada_device(name, cs)
elif type == s30_scada_device_data:
return set_scada_device_data(name, cs)
elif type == s31_scada_element:
return set_scada_element(name, cs)
elif type == s32_region:
return set_region(name, cs)
elif type == s33_dma:
return set_district_metering_area(name, cs)
elif type == s34_sa:
return set_service_area(name, cs)
elif type == s35_vd:
return set_virtual_district(name, cs)
return ChangeSet()
def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet:
type = cs.operations[0]['type']
if type == s1_title:
return ChangeSet()
if type == s2_junction:
return delete_junction(name, cs)
elif type == s3_reservoir:
return delete_reservoir(name, cs)
elif type == s4_tank:
return delete_tank(name, cs)
elif type == s5_pipe:
return delete_pipe(name, cs)
elif type == s6_pump:
return delete_pump(name, cs)
elif type == s7_valve:
return delete_valve(name, cs)
elif type == s8_tag:
return ChangeSet()
elif type == s9_demand:
return ChangeSet()
elif type == s10_status:
return ChangeSet()
elif type == s11_pattern:
return delete_pattern(name, cs)
elif type == s12_curve:
return delete_curve(name, cs)
elif type == s13_control:
return ChangeSet()
elif type == s14_rule:
return ChangeSet()
elif type == s15_energy:
return ChangeSet()
elif type == s15_pump_energy:
return ChangeSet()
elif type == s16_emitter:
return ChangeSet()
elif type == s17_quality:
return ChangeSet()
elif type == s18_source:
return delete_source(name, cs)
elif type == s19_reaction:
return ChangeSet()
elif type == s19_pipe_reaction:
return ChangeSet()
elif type == s19_tank_reaction:
return ChangeSet()
elif type == s20_mixing:
return delete_mixing(name, cs)
elif type == s21_time:
return ChangeSet()
elif type == s22_report:
return ChangeSet()
elif type == s23_option:
return ChangeSet()
elif type == s23_option_v3:
return ChangeSet()
elif type == s24_coordinate:
return ChangeSet()
elif type == s25_vertex:
return delete_vertex(name, cs)
elif type == s26_label:
return delete_label(name, cs)
elif type == s27_backdrop:
return ChangeSet()
elif type == s28_end:
return ChangeSet()
elif type == s29_scada_device:
return delete_scada_device(name, cs)
elif type == s30_scada_device_data:
return delete_scada_device_data(name, cs)
elif type == s31_scada_element:
return delete_scada_element(name, cs)
elif type == s32_region:
return delete_region(name, cs)
elif type == s33_dma:
return delete_district_metering_area(name, cs)
elif type == s34_sa:
return delete_service_area(name, cs)
elif type == s35_vd:
return delete_virtual_district(name, cs)
return ChangeSet()
def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet:
new_cs = ChangeSet()
for op in cs.operations:
new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))
result = ChangeSet()
todo = {}
try:
for op in new_cs.operations:
todo = op
operation = op['operation']
if operation == API_ADD:
result.merge(_execute_add_command(name, ChangeSet(op)))
elif operation == API_UPDATE:
result.merge(_execute_update_command(name, ChangeSet(op)))
elif operation == API_DELETE:
result.merge(_execute_delete_command(name, ChangeSet(op)))
except:
print(f'ERROR: Fail to execute {todo}')
return result
def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet:
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'batch_operation' where option = 'operation'")
new_cs = ChangeSet()
for op in cs.operations:
new_cs.merge(rewrite_batch_api(name, ChangeSet(op)))
result = ChangeSet()
todo = {}
try:
for op in new_cs.operations:
todo = op
operation = op['operation']
if operation == API_ADD:
result.merge(_execute_add_command(name, ChangeSet(op)))
elif operation == API_UPDATE:
result.merge(_execute_update_command(name, ChangeSet(op)))
elif operation == API_DELETE:
result.merge(_execute_delete_command(name, ChangeSet(op)))
except:
print(f'ERROR: Fail to execute {todo}')
count = read(name, 'select count(*) as count from batch_operation')['count']
if count == 1:
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
return ChangeSet()
redo_list: list[str] = []
redo_cs_list: list[dict[str, Any]] = []
redo_rows = read_all(name, 'select redo, redo_cs from batch_operation where id > 0 order by id asc')
for row in redo_rows:
redo_list.append(row['redo'])
redo_cs_list += eval(row['redo_cs'])
undo_list: list[str] = []
undo_cs_list: list[dict[str, Any]] = []
undo_rows = read_all(name, 'select undo, undo_cs from batch_operation where id > 0 order by id desc')
for row in undo_rows:
undo_list.append(row['undo'])
undo_cs_list += eval(row['undo_cs'])
redo = '\n'.join(redo_list).replace("'", "''")
redo_cs = str(redo_cs_list).replace("'", "''")
undo = '\n'.join(undo_list).replace("'", "''")
undo_cs = str(undo_cs_list).replace("'", "''")
parent = get_current_operation(name)
write(name, f"insert into operation (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo}', '{undo}', {parent}, '{redo_cs}', '{undo_cs}')")
current = read(name, 'select max(id) as id from operation')['id']
write(name, f"update current_operation set id = {current}")
write(name, 'delete from batch_operation where id > 0')
write(name, "update operation_table set option = 'operation' where option = 'batch_operation'")
return result
-45
View File
@@ -1,45 +0,0 @@
from .database import ChangeSet, read_all
from .batch_exe import execute_batch_command
# TODO: merge to batch_api
def clean_scada_device_cs(name: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, 'select id from scada_device acs')
for row in rows:
cs.delete({ 'type': 'scada_device', 'id': row['id'] })
return cs
def clean_scada_device_data_cs(name: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, 'select distinct device_id from scada_device_data acs')
for row in rows:
cs.update({ 'type': 'scada_device_data', 'device_id': row['device_id'], 'data': [] })
return cs
def clean_scada_element_cs(name: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, 'select id from scada_element acs')
for row in rows:
cs.delete({ 'type': 'scada_element', 'id': row['id'] })
return cs
def clean_scada_device(name: str) -> ChangeSet:
return execute_batch_command(name, clean_scada_device_cs(name))
def clean_scada_device_data(name: str) -> ChangeSet:
return execute_batch_command(name, clean_scada_device_data_cs(name))
def clean_scada_element(name: str) -> ChangeSet:
return execute_batch_command(name, clean_scada_element_cs(name))
+1
View File
@@ -0,0 +1 @@
"""Model command rewriting, cascade handling, and execution."""
+65
View File
@@ -0,0 +1,65 @@
"""Convenience entry points for cascade deletes and option synchronization."""
from ..core.database import API_DELETE, API_UPDATE, ChangeSet
from .executor import execute_batch_command
def _execute(
name: str,
change_set: ChangeSet,
*,
operation: str,
element_type: str,
) -> ChangeSet:
change_set.operations[0].update(
{"operation": operation, "type": element_type}
)
return execute_batch_command(name, change_set)
def delete_junction_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(
name, change_set, operation=API_DELETE, element_type="junction"
)
def delete_reservoir_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(
name, change_set, operation=API_DELETE, element_type="reservoir"
)
def delete_tank_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_DELETE, element_type="tank")
def delete_pipe_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_DELETE, element_type="pipe")
def delete_pump_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_DELETE, element_type="pump")
def delete_valve_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_DELETE, element_type="valve")
def delete_pattern_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(
name, change_set, operation=API_DELETE, element_type="pattern"
)
def delete_curve_cascade(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_DELETE, element_type="curve")
def set_option_ex(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(name, change_set, operation=API_UPDATE, element_type="option")
def set_option_v3_ex(name: str, change_set: ChangeSet) -> ChangeSet:
return _execute(
name, change_set, operation=API_UPDATE, element_type="option_v3"
)
+244
View File
@@ -0,0 +1,244 @@
from collections.abc import Callable
from ..core.database import API_DELETE, API_UPDATE, ChangeSet, g_delete_prefix, try_read
from ..model.elements import get_node_links, is_pipe, is_pump, is_valve
from ..model.reservoirs import unset_reservoir_by_pattern
from ..model.tanks import unset_tank_by_curve
from ..model.pumps import unset_pump_by_curve, unset_pump_by_pattern
from ..model.tags import delete_tag_by_node, delete_tag_by_link
from ..model.demands import delete_demand_by_junction, unset_demand_by_pattern
from ..model.status import delete_status_by_link
from ..model.energy import delete_pump_energy_by_pump, unset_pump_energy_by_pattern, unset_pump_energy_by_curve
from ..model.emitters import delete_emitter_by_junction
from ..model.quality import delete_quality_by_node
from ..model.sources import delete_source_by_node, unset_source_by_pattern
from ..model.reactions import delete_pipe_reaction_by_pipe, delete_tank_reaction_by_tank
from ..model.mixing import delete_mixing_by_tank
from ..gis.vertices import delete_vertex_by_link
from ..gis.labels import unset_label_by_node
from ..model.options import generate_v2, generate_v3
def expand_junction_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.junctions where node_id = %s", (id,))
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_demand_by_junction(name, id))
result.merge(delete_emitter_by_junction(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def expand_reservoir_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.reservoirs where node_id = %s", (id,))
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def expand_tank_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.tanks where node_id = %s", (id,))
if row == None:
return result
links = get_node_links(name, id)
for link in links:
if is_pipe(name, link):
result.merge(expand_pipe_delete(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link})))
if is_pump(name, link):
result.merge(expand_pump_delete(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link})))
if is_valve(name, link):
result.merge(expand_valve_delete(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link})))
result.merge(delete_tag_by_node(name, id))
result.merge(delete_quality_by_node(name, id))
result.merge(delete_source_by_node(name, id))
result.merge(delete_tank_reaction_by_tank(name, id))
result.merge(delete_mixing_by_tank(name, id))
result.merge(unset_label_by_node(name, id))
result.merge(cs)
return result
def expand_pipe_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.pipes where link_id = %s", (id,))
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_pipe_reaction_by_pipe(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def expand_pump_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.pumps where link_id = %s", (id,))
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_pump_energy_by_pump(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def expand_valve_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.valves where link_id = %s", (id,))
if row == None:
return result
result.merge(delete_tag_by_link(name, id))
result.merge(delete_status_by_link(name, id))
result.merge(delete_vertex_by_link(name, id))
result.merge(cs)
return result
def expand_pattern_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.patterns where id = %s", (id,))
if row == None:
return result
result.merge(unset_reservoir_by_pattern(name, id))
result.merge(unset_pump_by_pattern(name, id))
result.merge(unset_demand_by_pattern(name, id))
result.merge(unset_pump_energy_by_pattern(name, id))
result.merge(unset_source_by_pattern(name, id))
result.merge(cs)
return result
def expand_curve_delete(name: str, cs: ChangeSet) -> ChangeSet:
result = ChangeSet()
id = cs.operations[0]['id']
row = try_read(name, "select 1 from network.curves where id = %s", (id,))
if row == None:
return result
result.merge(unset_tank_by_curve(name, id))
result.merge(unset_pump_by_curve(name, id))
result.merge(unset_pump_energy_by_curve(name, id))
result.merge(cs)
return result
def expand_legacy_options_update(cs: ChangeSet) -> ChangeSet:
cs.operations[0]['operation'] = API_UPDATE
cs.operations[0]['type'] = 'option'
new_cs = cs
new_cs.merge(generate_v3(cs))
return new_cs
def expand_v3_options_update(cs: ChangeSet) -> ChangeSet:
cs.operations[0]['operation'] = API_UPDATE
cs.operations[0]['type'] = 'option_v3'
new_cs = cs
new_cs.merge(generate_v2(cs))
return new_cs
def expand_command(name: str, cs: ChangeSet) -> ChangeSet:
op = cs.operations[0]
operation = op['operation']
element_type = op['type']
if operation == API_DELETE:
handler = _DELETE_REWRITERS.get(element_type)
if handler:
return handler(name, cs)
elif operation == API_UPDATE:
handler = _UPDATE_REWRITERS.get(element_type)
if handler:
return handler(cs)
return cs
DeleteRewriter = Callable[[str, ChangeSet], ChangeSet]
UpdateRewriter = Callable[[ChangeSet], ChangeSet]
_DELETE_REWRITERS: dict[str, DeleteRewriter] = {
"junction": expand_junction_delete,
"reservoir": expand_reservoir_delete,
"tank": expand_tank_delete,
"pipe": expand_pipe_delete,
"pump": expand_pump_delete,
"valve": expand_valve_delete,
"pattern": expand_pattern_delete,
"curve": expand_curve_delete,
}
_UPDATE_REWRITERS: dict[str, UpdateRewriter] = {
"option": expand_legacy_options_update,
"option_v3": expand_v3_options_update,
}
+155
View File
@@ -0,0 +1,155 @@
"""Transactional command dispatch for WNDB model mutations."""
from collections.abc import Callable
from ..core.connection import project_transaction
from ..core.database import (
API_ADD,
API_DELETE,
API_UPDATE,
ChangeSet,
refresh_materialized_views,
)
from ..gis.backdrop import set_backdrop
from ..gis.labels import add_label, delete_label, set_label
from ..gis.regions import add_region, delete_region, set_region
from ..gis.vertices import add_vertex, delete_vertex, set_vertex
from ..model.controls import set_control
from ..model.curves import add_curve, delete_curve, set_curve
from ..model.demands import set_demand
from ..model.emitters import set_emitter
from ..model.energy import set_energy, set_pump_energy
from ..model.junctions import add_junction, delete_junction, set_junction
from ..model.mixing import add_mixing, delete_mixing, set_mixing
from ..model.options import set_option, set_option_v3
from ..model.patterns import add_pattern, delete_pattern, set_pattern
from ..model.pipes import add_pipe, delete_pipe, set_pipe
from ..model.pumps import add_pump, delete_pump, set_pump
from ..model.quality import set_quality
from ..model.reactions import (
set_pipe_reaction,
set_reaction,
set_tank_reaction,
)
from ..model.reservoirs import add_reservoir, delete_reservoir, set_reservoir
from ..model.rules import set_rule
from ..model.sources import add_source, delete_source, set_source
from ..model.status import set_status
from ..model.tags import set_tag
from ..model.tanks import add_tank, delete_tank, set_tank
from ..model.times import set_time
from ..model.title import set_title
from ..model.valves import add_valve, delete_valve, set_valve
from .cascade import expand_command
CommandHandler = Callable[[str, ChangeSet], ChangeSet]
_ADD_HANDLERS: dict[str, CommandHandler] = {
"junction": add_junction,
"reservoir": add_reservoir,
"tank": add_tank,
"pipe": add_pipe,
"pump": add_pump,
"valve": add_valve,
"pattern": add_pattern,
"curve": add_curve,
"source": add_source,
"mixing": add_mixing,
"vertex": add_vertex,
"label": add_label,
"region": add_region,
}
_UPDATE_HANDLERS: dict[str, CommandHandler] = {
"title": set_title,
"junction": set_junction,
"reservoir": set_reservoir,
"tank": set_tank,
"pipe": set_pipe,
"pump": set_pump,
"valve": set_valve,
"tag": set_tag,
"demand": set_demand,
"status": set_status,
"pattern": set_pattern,
"curve": set_curve,
"control": set_control,
"rule": set_rule,
"energy": set_energy,
"pump_energy": set_pump_energy,
"emitter": set_emitter,
"quality": set_quality,
"source": set_source,
"reaction": set_reaction,
"pipe_reaction": set_pipe_reaction,
"tank_reaction": set_tank_reaction,
"mixing": set_mixing,
"time": set_time,
"option": set_option,
"option_v3": set_option_v3,
"vertex": set_vertex,
"label": set_label,
"backdrop": set_backdrop,
"region": set_region,
}
_DELETE_HANDLERS: dict[str, CommandHandler] = {
"junction": delete_junction,
"reservoir": delete_reservoir,
"tank": delete_tank,
"pipe": delete_pipe,
"pump": delete_pump,
"valve": delete_valve,
"pattern": delete_pattern,
"curve": delete_curve,
"source": delete_source,
"mixing": delete_mixing,
"vertex": delete_vertex,
"label": delete_label,
"region": delete_region,
}
def _dispatch(
handlers: dict[str, CommandHandler], name: str, change_set: ChangeSet
) -> ChangeSet:
element_type = change_set.operations[0]["type"]
handler = handlers.get(element_type)
return handler(name, change_set) if handler else ChangeSet()
def _execute_add_command(name: str, change_set: ChangeSet) -> ChangeSet:
return _dispatch(_ADD_HANDLERS, name, change_set)
def _execute_update_command(name: str, change_set: ChangeSet) -> ChangeSet:
return _dispatch(_UPDATE_HANDLERS, name, change_set)
def _execute_delete_command(name: str, change_set: ChangeSet) -> ChangeSet:
return _dispatch(_DELETE_HANDLERS, name, change_set)
def execute_batch_commands(name: str, change_set: ChangeSet) -> ChangeSet:
with project_transaction(name):
rewritten = ChangeSet()
for operation in change_set.operations:
rewritten.merge(expand_command(name, ChangeSet(operation)))
result = ChangeSet()
for operation in rewritten.operations:
operation_type = operation["operation"]
if operation_type == API_ADD:
result.merge(_execute_add_command(name, ChangeSet(operation)))
elif operation_type == API_UPDATE:
result.merge(_execute_update_command(name, ChangeSet(operation)))
elif operation_type == API_DELETE:
result.merge(_execute_delete_command(name, ChangeSet(operation)))
if rewritten.operations:
refresh_materialized_views(name)
return result
def execute_batch_command(name: str, change_set: ChangeSet) -> ChangeSet:
return execute_batch_commands(name, change_set)
-90
View File
@@ -1,90 +0,0 @@
from collections.abc import Iterator
from contextlib import contextmanager
from threading import RLock
import psycopg as pg
from app.infra.db.project_routing import get_project_pgconn_string
g_conn_dict: dict[str, pg.Connection] = {}
g_conninfo_dict: dict[str, str] = {}
_registry_lock = RLock()
_project_locks: dict[str, RLock] = {}
def _is_closed(connection: pg.Connection) -> bool:
return bool(getattr(connection, "closed", False))
def _close_connection(connection: pg.Connection) -> None:
if not _is_closed(connection):
connection.close()
def _is_healthy(connection: pg.Connection) -> bool:
if _is_closed(connection):
return False
try:
with connection.cursor() as cur:
cur.execute("SELECT 1")
except pg.Error:
return False
return True
def _get_project_lock(name: str) -> RLock:
with _registry_lock:
lock = _project_locks.get(name)
if lock is None:
lock = RLock()
_project_locks[name] = lock
return lock
def open_connection(name: str) -> pg.Connection:
with _get_project_lock(name):
conninfo = get_project_pgconn_string(db_name=name)
connection = g_conn_dict.get(name)
if (
connection is None
or g_conninfo_dict.get(name) != conninfo
or not _is_healthy(connection)
):
if connection is not None:
_close_connection(connection)
connection = pg.connect(conninfo=conninfo, autocommit=True)
g_conn_dict[name] = connection
g_conninfo_dict[name] = conninfo
return connection
def is_connection_open(name: str) -> bool:
with _get_project_lock(name):
connection = g_conn_dict.get(name)
if connection is None:
return False
if not _is_healthy(connection):
del g_conn_dict[name]
g_conninfo_dict.pop(name, None)
_close_connection(connection)
return False
if g_conninfo_dict.get(name) != get_project_pgconn_string(db_name=name):
del g_conn_dict[name]
g_conninfo_dict.pop(name, None)
_close_connection(connection)
return False
return True
def close_connection(name: str) -> None:
with _get_project_lock(name):
connection = g_conn_dict.pop(name, None)
g_conninfo_dict.pop(name, None)
if connection is not None:
_close_connection(connection)
@contextmanager
def project_connection(name: str) -> Iterator[pg.Connection]:
with _get_project_lock(name):
yield open_connection(name)
+1
View File
@@ -0,0 +1 @@
"""WNDB connection, transaction, and project lifecycle infrastructure."""
+224
View File
@@ -0,0 +1,224 @@
from collections import OrderedDict
from collections.abc import Iterator
from contextlib import contextmanager
from contextvars import ContextVar
from threading import RLock
from psycopg import Connection
from psycopg.rows import dict_row
from psycopg_pool import ConnectionPool
from app.core.config import settings
from app.infra.db.project_routing import get_project_pgconn_string
_pools: OrderedDict[str, ConnectionPool] = OrderedDict()
_pool_conninfo: dict[str, str] = {}
_pool_borrows: dict[str, int] = {}
_admin_pools: OrderedDict[str, ConnectionPool] = OrderedDict()
_admin_pool_borrows: dict[str, int] = {}
_registry_lock = RLock()
_active_project_connection: ContextVar[tuple[str, Connection] | None] = ContextVar(
"wndb_active_project_connection",
default=None,
)
def _close_pool(pool: ConnectionPool) -> None:
if not pool.closed:
pool.close()
def _evict_idle_project_pools(*, protected: str | None = None) -> None:
limit = max(1, settings.PROJECT_PG_CACHE_SIZE)
while len(_pools) > limit:
candidate = next(
(key for key in _pools if key != protected and _pool_borrows.get(key, 0) == 0),
None,
)
if candidate is None:
return
pool = _pools.pop(candidate)
_pool_conninfo.pop(candidate, None)
_pool_borrows.pop(candidate, None)
_close_pool(pool)
def _evict_idle_admin_pools(*, protected: str | None = None) -> None:
limit = max(1, settings.PROJECT_PG_CACHE_SIZE)
while len(_admin_pools) > limit:
candidate = next(
(
key
for key in _admin_pools
if key != protected and _admin_pool_borrows.get(key, 0) == 0
),
None,
)
if candidate is None:
return
pool = _admin_pools.pop(candidate)
_admin_pool_borrows.pop(candidate, None)
_close_pool(pool)
def get_project_pool(name: str) -> ConnectionPool:
"""Return the routed synchronous pool used by native WNDB operations."""
conninfo = get_project_pgconn_string(db_name=name)
with _registry_lock:
pool = _pools.get(name)
if pool is not None and _pool_conninfo.get(name) == conninfo and not pool.closed:
_pools.move_to_end(name)
return pool
if pool is not None:
if _pool_borrows.get(name, 0):
raise RuntimeError(f"Cannot replace active project pool {name!r}")
_close_pool(pool)
pool = ConnectionPool(
conninfo=conninfo,
min_size=settings.PROJECT_PG_POOL_MIN_SIZE,
max_size=settings.PROJECT_PG_POOL_SIZE + settings.PROJECT_PG_MAX_OVERFLOW,
kwargs={"autocommit": True, "row_factory": dict_row},
open=True,
)
_pools[name] = pool
_pool_conninfo[name] = conninfo
_pool_borrows.setdefault(name, 0)
_evict_idle_project_pools(protected=name)
return pool
def is_project_pool_open(name: str) -> bool:
with _registry_lock:
pool = _pools.get(name)
if pool is None or pool.closed:
return False
if _pool_conninfo.get(name) != get_project_pgconn_string(db_name=name):
if _pool_borrows.get(name, 0):
return False
_close_pool(pool)
_pools.pop(name, None)
_pool_conninfo.pop(name, None)
_pool_borrows.pop(name, None)
return False
return True
def close_project_pool(name: str) -> None:
with _registry_lock:
if _pool_borrows.get(name, 0):
raise RuntimeError(f"Cannot close active project pool {name!r}")
pool = _pools.pop(name, None)
_pool_conninfo.pop(name, None)
_pool_borrows.pop(name, None)
if pool is not None:
_close_pool(pool)
def close_all_project_pools() -> None:
"""Close every WNDB project pool and the administration pool."""
with _registry_lock:
pools = [*_pools.values(), *_admin_pools.values()]
_pools.clear()
_pool_conninfo.clear()
_pool_borrows.clear()
_admin_pools.clear()
_admin_pool_borrows.clear()
for pool in pools:
_close_pool(pool)
def get_admin_pool() -> ConnectionPool:
"""Return the administration pool for the current routed PostgreSQL host."""
conninfo = get_project_pgconn_string(db_name="postgres")
with _registry_lock:
pool = _admin_pools.get(conninfo)
if pool is not None and not pool.closed:
_admin_pools.move_to_end(conninfo)
return pool
pool = ConnectionPool(
conninfo=conninfo,
min_size=settings.PROJECT_PG_POOL_MIN_SIZE,
max_size=settings.PROJECT_PG_POOL_SIZE,
kwargs={"autocommit": True, "row_factory": dict_row},
open=True,
)
_admin_pools[conninfo] = pool
_admin_pool_borrows.setdefault(conninfo, 0)
_evict_idle_admin_pools(protected=conninfo)
return pool
@contextmanager
def project_connection(name: str) -> Iterator[Connection]:
"""Borrow one routed WNDB connection and return it to its pool on exit."""
active = _active_project_connection.get()
if active is not None:
active_name, conn = active
if active_name != name:
raise RuntimeError(
f"Cannot access project {name!r} inside transaction for {active_name!r}"
)
yield conn
return
with _registry_lock:
pool = get_project_pool(name)
_pool_borrows[name] = _pool_borrows.get(name, 0) + 1
try:
with pool.connection() as conn:
yield conn
finally:
with _registry_lock:
_pool_borrows[name] -= 1
_evict_idle_project_pools()
@contextmanager
def project_transaction(name: str) -> Iterator[Connection]:
"""Run all nested WNDB operations on one pooled connection and transaction."""
active = _active_project_connection.get()
if active is not None:
active_name, conn = active
if active_name != name:
raise RuntimeError(
f"Cannot nest project {name!r} inside transaction for {active_name!r}"
)
with conn.transaction():
yield conn
return
with _registry_lock:
pool = get_project_pool(name)
_pool_borrows[name] = _pool_borrows.get(name, 0) + 1
try:
with pool.connection() as conn:
token = _active_project_connection.set((name, conn))
try:
with conn.transaction():
yield conn
finally:
_active_project_connection.reset(token)
finally:
with _registry_lock:
_pool_borrows[name] -= 1
_evict_idle_project_pools()
def is_project_transaction_active(name: str) -> bool:
active = _active_project_connection.get()
return active is not None and active[0] == name
@contextmanager
def admin_connection() -> Iterator[Connection]:
"""Borrow a PostgreSQL administration connection from its pool."""
with _registry_lock:
pool = get_admin_pool()
conninfo = pool.conninfo
_admin_pool_borrows[conninfo] = _admin_pool_borrows.get(conninfo, 0) + 1
try:
with pool.connection() as conn:
yield conn
finally:
with _registry_lock:
_admin_pool_borrows[conninfo] -= 1
_evict_idle_admin_pools()
+143
View File
@@ -0,0 +1,143 @@
from collections.abc import Mapping, Sequence
from typing import Any
from psycopg import sql
from psycopg.rows import Row, dict_row
from .connection import is_project_transaction_active, project_connection
API_ADD = "add"
API_UPDATE = "update"
API_DELETE = "delete"
g_add_prefix = {"operation": API_ADD}
g_update_prefix = {"operation": API_UPDATE}
g_delete_prefix = {"operation": API_DELETE}
class ChangeSet:
def __init__(self, ps: dict[str, Any] | None = None):
self.operations: list[dict[str, Any]] = []
if ps is not None:
self.append(ps)
@staticmethod
def from_list(ps: list[dict[str, Any]]):
change_set = ChangeSet()
for item in ps:
change_set.append(item)
return change_set
def add(self, ps: dict[str, Any]):
self.operations.append(g_add_prefix | ps)
return self
def update(self, ps: dict[str, Any]):
self.operations.append(g_update_prefix | ps)
return self
def delete(self, ps: dict[str, Any]):
self.operations.append(g_delete_prefix | ps)
return self
def append(self, ps: dict[str, Any]):
self.operations.append(ps)
return self
def merge(self, change_set):
self.operations.extend(change_set.operations)
return self
def dump(self):
for operation in self.operations:
print(operation)
def compress(self):
return self
class DatabaseCommand:
def __init__(self, statement: str, changes: list[dict[str, Any]]) -> None:
self.sql = statement
self.changes = changes
QueryParams = Sequence[Any] | Mapping[str, Any]
def sql_literal(value: Any) -> str:
"""Render one PostgreSQL literal for legacy WNDB SQL batch builders.
WNDB still assembles multi-statement model changes before executing them as
one transaction. Every interpolated value must pass through this helper;
identifiers remain static strings owned by the backend.
"""
return sql.Literal(value).as_string()
def _execute(cur, query: str, params: QueryParams | None = None):
return cur.execute(query, params) if params is not None else cur.execute(query)
def read(name: str, query: str, params: QueryParams | None = None) -> Row:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
row = cur.fetchone()
if row is None:
raise LookupError(query)
return row
def read_all(
name: str, query: str, params: QueryParams | None = None
) -> list[Row]:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
return cur.fetchall()
def try_read(
name: str, query: str, params: QueryParams | None = None
) -> Row | None:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
_execute(cur, query, params)
return cur.fetchone()
def write(name: str, query: str, params: QueryParams | None = None) -> None:
with project_connection(name) as conn, conn.cursor() as cur:
_execute(cur, query, params)
def refresh_materialized_views(name: str, *, concurrently: bool = True) -> None:
"""Refresh the GIS query layer after committed model or asset changes."""
with project_connection(name) as conn, conn.cursor() as cur:
cur.execute("CALL gis.refresh_all_materialized_views(%s)", (concurrently,))
_MATERIALIZED_VIEW_SOURCES = (
"network.nodes",
"network.junctions",
"network.reservoirs",
"network.tanks",
"network.links",
"network.pipes",
"network.pumps",
"network.valves",
"network.demands",
"gis.node_geometries",
"gis.link_vertices",
"asset.scada_devices",
)
def _affects_materialized_views(command: DatabaseCommand) -> bool:
statement = command.sql.lower()
return any(source in statement for source in _MATERIALIZED_VIEW_SOURCES)
def execute_command(name: str, command: DatabaseCommand) -> ChangeSet:
"""Apply a model mutation without the removed database undo/redo journal."""
write(name, command.sql)
if _affects_materialized_views(command) and not is_project_transaction_active(name):
refresh_materialized_views(name)
return ChangeSet.from_list(command.changes)
+107
View File
@@ -0,0 +1,107 @@
from psycopg import sql
from psycopg.rows import dict_row
from .connection import (
admin_connection,
close_project_pool,
get_project_pool,
is_project_pool_open,
)
_server_databases = ["template0", "template1", "postgres", "project"]
def list_project() -> list[str]:
ps = []
with admin_connection() as conn:
with conn.cursor(row_factory=dict_row) as cur:
for p in cur.execute(
"select datname from pg_database where datname <> all(%s) order by datname",
(_server_databases,),
):
ps.append(p["datname"])
return ps
def have_project(name: str) -> bool:
with admin_connection() as conn:
with conn.cursor() as cur:
cur.execute("select 1 from pg_database where datname = %s", (name,))
return cur.fetchone() is not None
def copy_project(source: str, new: str) -> None:
close_project_pool(source)
with admin_connection() as admin_conn:
with admin_conn.cursor() as cur:
cur.execute(
"update pg_database set datallowconn = false where datname = %s",
(source,),
)
try:
cur.execute(
"select pg_terminate_backend(pid) from pg_stat_activity where datname = %s and pid <> pg_backend_pid()",
(source,),
)
cur.execute(
sql.SQL("create database {} with template = {}").format(
sql.Identifier(new), sql.Identifier(source)
)
)
finally:
cur.execute(
"update pg_database set datallowconn = true where datname = %s",
(source,),
)
def create_project(name: str) -> None:
return copy_project("project", name)
def delete_project(name: str) -> None:
close_project_pool(name)
with admin_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"select pg_terminate_backend(pid) from pg_stat_activity "
"where datname = %s and pid <> pg_backend_pid()",
(name,),
)
cur.execute(
sql.SQL("drop database {}").format(sql.Identifier(name))
)
def clean_project(excluded: list[str] = []) -> None:
projects = list_project()
with admin_connection() as conn:
with conn.cursor(row_factory=dict_row) as cur:
row = cur.execute("select current_database()").fetchone()
if row != None:
current_db = row["current_database"]
if current_db in projects:
projects.remove(current_db)
for project in projects:
if project in _server_databases or project in excluded:
continue
cur.execute(
"select pg_terminate_backend(pid) from pg_stat_activity "
"where datname = %s and pid <> pg_backend_pid()",
(project,),
)
cur.execute(
sql.SQL("drop database {}").format(sql.Identifier(project))
)
def open_project(name: str) -> None:
get_project_pool(name)
def is_project_open(name: str) -> bool:
return is_project_pool_open(name)
def close_project(name: str) -> None:
close_project_pool(name)
-368
View File
@@ -1,368 +0,0 @@
from collections.abc import Mapping, Sequence
from typing import Any
from psycopg.rows import dict_row, Row
from .connection import project_connection
API_ADD = 'add'
API_UPDATE = 'update'
API_DELETE = 'delete'
g_add_prefix = { 'operation': API_ADD }
g_update_prefix = { 'operation': API_UPDATE }
g_delete_prefix = { 'operation': API_DELETE }
class ChangeSet:
def __init__(self, ps: dict[str, Any] | None = None):
self.operations : list[dict[str, Any]] = []
if ps != None:
self.append(ps)
@staticmethod
def from_list(ps: list[dict[str, Any]]):
cs = ChangeSet()
for _cs in ps:
cs.append(_cs)
return cs
def add(self, ps: dict[str, Any]):
self.operations.append(g_add_prefix | ps)
return self
def update(self, ps: dict[str, Any]):
self.operations.append(g_update_prefix | ps)
return self
def delete(self, ps: dict[str, Any]):
self.operations.append(g_delete_prefix | ps)
return self
def append(self, ps: dict[str, Any]):
self.operations.append(ps)
return self
def merge(self, cs):
if len(cs.operations) > 0:
self.operations += cs.operations
return self
def dump(self):
for op in self.operations:
print(op)
def compress(self):
return self
class DbChangeSet:
def __init__(self, redo_sql: str, undo_sql: str, redo_cs: list[dict[str, Any]], undo_cs: list[dict[str, Any]]) -> None:
self.redo_sql = redo_sql
self.undo_sql = undo_sql
self.redo_cs = redo_cs
self.undo_cs = undo_cs
@staticmethod
def from_list(css):
redo_sql_s : list[str] = []
undo_sql_s : list[str] = []
redo_cs_s : list[dict[str, Any]] = []
undo_cs_s : list[dict[str, Any]] = []
for r in css:
redo_sql_s.append(r.redo_sql)
undo_sql_s.append(r.undo_sql)
redo_cs_s += r.redo_cs
r.undo_cs.reverse() # reverse again...
undo_cs_s += r.undo_cs
redo_sql = '\n'.join(redo_sql_s)
undo_sql_s.reverse()
undo_sql = '\n'.join(undo_sql_s)
undo_cs_s.reverse()
return DbChangeSet(redo_sql, undo_sql, redo_cs_s, undo_cs_s)
QueryParams = Sequence[Any] | Mapping[str, Any]
def _execute(cur, sql: str, params: QueryParams | None = None):
return cur.execute(sql, params) if params is not None else cur.execute(sql)
def read(name: str, sql: str, params: QueryParams | None = None) -> Row:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
_execute(cur, sql, params)
row = cur.fetchone()
if row == None:
raise Exception(sql)
return row
def read_all(
name: str, sql: str, params: QueryParams | None = None
) -> list[Row]:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
_execute(cur, sql, params)
return cur.fetchall()
def try_read(
name: str, sql: str, params: QueryParams | None = None
) -> Row | None:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
_execute(cur, sql, params)
return cur.fetchone()
def write(name: str, sql: str) -> None:
with project_connection(name) as conn:
with conn.cursor() as cur:
cur.execute(sql)
def get_current_operation(name: str) -> int:
return int(read(name, 'select id from current_operation')['id'])
def execute_command(name: str, command: DbChangeSet, undo_redo: bool = True) -> ChangeSet:
write(name, command.redo_sql)
if undo_redo:
op_table = read(name, "select * from operation_table")['option']
parent = get_current_operation(name)
redo_sql = command.redo_sql.replace("'", "''")
undo_sql = command.undo_sql.replace("'", "''")
redo_cs_str = str(command.redo_cs).replace("'", "''")
undo_cs_str = str(command.undo_cs).replace("'", "''")
write(name, f"insert into {op_table} (id, redo, undo, parent, redo_cs, undo_cs) values (default, '{redo_sql}', '{undo_sql}', {parent}, '{redo_cs_str}', '{undo_cs_str}')")
if op_table == 'operation':
current = read(name, 'select max(id) as id from operation')['id']
write(name, f"update current_operation set id = {current}")
return ChangeSet.from_list(command.redo_cs)
def execute_undo(name: str, discard: bool = False) -> ChangeSet:
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
write(name, row['undo'])
parent = row['parent'] if row['parent'] != None else 0
# update foreign key
write(name, f"update current_operation set id = {parent} where id = {row['id']}")
if discard:
# update foreign key
write(name, f"update operation set redo_child = null where id = {parent}")
# on delete cascade => child & snapshot
write(name, f"delete from operation where id = {row['id']}")
else:
write(name, f"update operation set redo_child = {row['id']} where id = {parent}")
e = eval(row['undo_cs']) if row['undo_cs'] not in [None, ''] else []
return ChangeSet.from_list(e)
def execute_redo(name: str) -> ChangeSet:
row = read(name, f'select * from operation where id = {get_current_operation(name)}')
if row['redo_child'] == None:
return ChangeSet()
row = read(name, f"select * from operation where id = {row['redo_child']}")
write(name, row['redo'])
parent = row['parent'] if row['parent'] != None else 0
write(name, f"update current_operation set id = {row['id']} where id = {parent}")
e = eval(row['redo_cs']) if row['redo_cs'] not in [None, ''] else []
return ChangeSet.from_list(e)
def list_snapshot(name: str) -> list[tuple[int, str]]:
rows = read_all(name, f'select * from snapshot_operation order by id')
result = []
for row in rows:
result.append((int(row['id']), str(row['tag'])))
return result
def have_snapshot(name: str, tag: str) -> bool:
return try_read(name, f"select id from snapshot_operation where tag = '{tag}'") != None
def have_snapshot_for_operation(name: str, operation: int) -> bool:
return try_read(name, f"select id from snapshot_operation where id = {operation}") != None
def have_snapshot_for_current_operation(name: str) -> bool:
return have_snapshot_for_operation(name, get_current_operation(name))
def take_snapshot_for_operation(name: str, operation: int, tag: str) -> None:
if tag == None or tag == '':
return None
write(name, f"insert into snapshot_operation (id, tag) values ({operation}, '{tag}')")
def take_snapshot_for_current_operation(name: str, tag: str) -> None:
take_snapshot_for_operation(name, get_current_operation(name), tag)
# deprecated ! use take_snapshot_for_current_operation instead
def take_snapshot(name: str, tag: str) -> None:
take_snapshot_for_current_operation(name, tag)
def update_snapshot(name: str, operation: int, tag: str) -> None:
if tag == None or tag == '':
return None
if have_snapshot_for_operation(name, operation):
write(name, f"update snapshot_operation set tag = '{tag}' where id = {operation}")
else:
take_snapshot_for_operation(name, operation, tag)
def update_snapshot_for_current_operation(name: str, tag: str) -> None:
return update_snapshot(name, get_current_operation(name), tag)
def delete_snapshot(name: str, tag: str) -> None:
write(name, f"delete from snapshot_operation where tag = '{tag}'")
def delete_snapshot_by_operation(name: str, operation: int) -> None:
write(name, f"delete from snapshot_operation where id = {operation}")
def get_operation_by_snapshot(name: str, tag: str) -> int | None:
row = try_read(name, f"select id from snapshot_operation where tag = '{tag}'")
return int(row['id']) if row != None else None
def get_snapshot_by_operation(name: str, operation: int) -> str | None:
row = try_read(name, f"select tag from snapshot_operation where id = {operation}")
return str(row['tag']) if row != None else None
def _get_parents(name: str, id: int) -> list[int]:
ids = [id]
while ids[-1] != 0:
row = read(name, f'select parent from operation where id = {ids[-1]}')
ids.append(int(row['parent']))
return ids
def pick_operation(name: str, operation: int, discard: bool) -> ChangeSet:
target = operation
curr = get_current_operation(name)
curr_parents = _get_parents(name, curr)
target_parents = _get_parents(name, target)
change = ChangeSet()
if target in curr_parents:
for _ in range(curr_parents.index(target)):
change.merge(execute_undo(name, discard))
elif curr in target_parents:
target_parents.reverse()
curr_index = target_parents.index(curr)
for i in range(curr_index, len(target_parents) - 1):
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
change.merge(execute_redo(name))
else:
ancestor_index = -1
while curr_parents[ancestor_index] == target_parents[ancestor_index]:
ancestor_index -= 1
ancestor = curr_parents[ancestor_index + 1]
for _ in range(curr_parents.index(ancestor)):
change.merge(execute_undo(name, discard))
target_parents.reverse()
curr_index = target_parents.index(ancestor)
for i in range(curr_index, len(target_parents) - 1):
write(name, f"update operation set redo_child = '{target_parents[i + 1]}' where id = '{target_parents[i]}'")
change.merge(execute_redo(name))
return change.compress()
def pick_snapshot(name: str, tag: str, discard: bool) -> ChangeSet:
if not have_snapshot(name, tag):
return ChangeSet()
target = int(read(name, f"select id from snapshot_operation where tag = '{tag}'")['id'])
return pick_operation(name, target, discard)
def _get_change_set(name: str, operation: int, undo: bool) -> ChangeSet:
row = read(name, f'select * from operation where id = {operation}')
field= 'undo_cs' if undo else 'redo_cs'
return ChangeSet.from_list(eval(row[field]))
def sync_with_server(name: str, operation: int) -> ChangeSet:
fr = operation
to = get_current_operation(name)
fr_parents = _get_parents(name, fr)
to_parents = _get_parents(name, to)
change = ChangeSet()
if fr in to_parents:
index = to_parents.index(fr) - 1
while index >= 0:
change.merge(_get_change_set(name, to_parents[index], False)) #redo
index -= 1
elif to in fr_parents:
index = 0
while index <= fr_parents.index(to) - 1:
change.merge(_get_change_set(name, fr_parents[index], True))
index += 1
else:
ancestor_index = -1
while fr_parents[ancestor_index] == to_parents[ancestor_index]:
ancestor_index -= 1
ancestor = fr_parents[ancestor_index + 1]
index = 0
while index <= fr_parents.index(ancestor) - 1:
change.merge(_get_change_set(name, fr_parents[index], True))
index += 1
index = to_parents.index(ancestor) - 1
while index >= 0:
change.merge(_get_change_set(name, to_parents[index], False))
index -= 1
return change.compress()
def get_restore_operation(name: str) -> int:
return read(name, f'select * from restore_operation')['id']
def set_restore_operation(name: str, operation: int) -> None:
write(name, f'update restore_operation set id = {operation}')
def set_restore_operation_to_current(name: str) -> None:
return set_restore_operation(name, get_current_operation(name))
def restore(name: str, discard: bool) -> ChangeSet:
op = get_restore_operation(name)
return pick_operation(name, op, discard)
-62
View File
@@ -1,62 +0,0 @@
from .database import *
def get_all_extension_data_keys(name: str) -> list[str]:
result: list[str] = []
for row in read_all(name, 'select key from extension_data'):
result.append(row['key'])
return result
def get_all_extension_data(name: str) -> dict[str, Any]:
result: dict[str, Any] = {}
for row in read_all(name, 'select key, value from extension_data'):
result[row['key']] = row['value']
return result
def get_extension_data(name: str, key: str) -> str | None:
if key == None or key == '':
return None
row = try_read(name, f"select value from extension_data where key = '{key}'")
if row == None:
return None
return row['value']
def _set_extension_data(name: str, cs: ChangeSet) -> DbChangeSet:
op = cs.operations[0]
key, new_val = op['key'], op['value']
f_new_val = f"'{new_val}'" if new_val != None else 'null'
old_val = get_extension_data(name, key)
f_old_val = f"'{old_val}'" if old_val != None else 'null'
redo_sql = f"delete from extension_data where key = '{key}';"
if new_val != None:
redo_sql += f"insert into extension_data (key, value) values ('{key}', {f_new_val});"
undo_sql = f"delete from extension_data where key = '{key}';"
if old_val != None:
undo_sql += f"insert into extension_data (key, value) values ('{key}', {f_old_val});"
redo_cs = g_update_prefix | { 'type': 'extension_data', 'key': key, 'value': new_val }
undo_cs = g_update_prefix | { 'type': 'extension_data', 'key': key, 'value': old_val }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_extension_data(name: str, cs: ChangeSet) -> ChangeSet:
if len(cs.operations) != 1:
return ChangeSet()
op = cs.operations[0]
if 'key' not in op or 'value' not in op:
return ChangeSet()
key = op['key']
if key == None or key == '':
return ChangeSet()
return execute_command(name, _set_extension_data(name, cs))
+1
View File
@@ -0,0 +1 @@
"""GIS persistence and network geometry operations."""
+44
View File
@@ -0,0 +1,44 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read,
sql_literal,
)
def get_backdrop_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'content' : {'type': 'str' , 'optional': False , 'readonly': False} }
def get_backdrop(name: str) -> dict[str, Any]:
e = read(name, "select content from gis.backdrops where id = true")
return { 'content': e['content'] }
def _set_backdrop(name: str, cs: ChangeSet) -> DatabaseCommand:
statement = f"update gis.backdrops set content = {sql_literal(cs.operations[0]['content'])} where id = true;"
change = g_update_prefix | { 'type': 'backdrop', 'content': cs.operations[0]['content'] }
return DatabaseCommand(statement, [change])
def set_backdrop(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_backdrop(name, cs))
def inp_in_backdrop(section: list[str]) -> str:
if section == []:
return str('')
content = '\n'.join(section)
return str(f"update gis.backdrops set content = {sql_literal(content)} where id = true;")
def inp_out_backdrop(name: str) -> list[str]:
obj = str(get_backdrop(name)['content'])
return obj.split('\n')
@@ -1,20 +1,23 @@
from .database import *
from .connection import project_connection
from .s0_base import get_link_nodes
from psycopg.rows import dict_row
from ..core.connection import project_connection
from ..core.database import read_all, sql_literal, try_read, write
from ..core.connection import project_connection
from ..model.elements import get_link_nodes
from psycopg.rows import dict_row
def sql_update_coord(node: str, x: float, y: float) -> str:
coord = f"st_geomfromtext('point({x} {y})')"
return str(f"update coordinates set coord = {coord} where node = '{node}';")
geom = f"st_setsrid(st_makepoint({sql_literal(x)}, {sql_literal(y)}), 900914)"
return f"update gis.node_geometries set geom = {geom} where node_id = {sql_literal(node)};"
def sql_insert_coord(node: str, x: float, y: float) -> str:
coord = f"st_geomfromtext('point({x} {y})')"
return str(f"insert into coordinates (node, coord) values ('{node}', {coord});")
geom = f"st_setsrid(st_makepoint({sql_literal(x)}, {sql_literal(y)}), 900914)"
return f"insert into gis.node_geometries (node_id, geom) values ({sql_literal(node)}, {geom});"
def sql_delete_coord(node: str) -> str:
return str(f"delete from coordinates where node = '{node}';")
return f"delete from gis.node_geometries where node_id = {sql_literal(node)};"
def from_postgis_point(coord: str) -> dict[str, float]:
@@ -25,7 +28,7 @@ def from_postgis_point(coord: str) -> dict[str, float]:
def get_node_coord(name: str, node: str) -> dict[str, float]:
row = try_read(
name,
"select st_astext(coord) as coord_geom from coordinates where node = %s",
"select st_astext(geom) as coord_geom from gis.node_geometries where node_id = %s",
(node,),
)
if row == None:
@@ -38,9 +41,9 @@ def get_node_coord(name: str, node: str) -> dict[str, float]:
# node_id:junction:x:y
def get_nodes_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -> list[str]:
nodes = []
objs = read_all(name, 'select node, st_astext(coord) as coord_geom from coordinates')
objs = read_all(name, 'select node_id, st_astext(geom) as coord_geom from gis.node_geometries')
for obj in objs:
node_id = obj['node']
node_id = obj['node_id']
coord = from_postgis_point(obj['coord_geom'])
x = coord['x']
y = coord['y']
@@ -57,9 +60,9 @@ def get_links_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -
all_link_ids = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from pipes")
cur.execute("select link_id from network.pipes")
for record in cur:
all_link_ids.append(record['id'])
all_link_ids.append(record['link_id'])
links = []
for link_id in all_link_ids:
@@ -71,7 +74,7 @@ def get_links_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -
def node_has_coord(name: str, node: str) -> bool:
return try_read(
name, "select node from coordinates where node = %s", (node,)
name, "select node_id from gis.node_geometries where node_id = %s", (node,)
) != None
@@ -85,15 +88,15 @@ def node_has_coord(name: str, node: str) -> bool:
def inp_in_coord(line: str) -> str:
tokens = line.split()
node = tokens[0]
coord = f"st_geomfromtext('point({tokens[1]} {tokens[2]})')"
return str(f"insert into coordinates (node, coord) values ('{node}', {coord});")
x, y = float(tokens[1]), float(tokens[2])
return sql_insert_coord(node, x, y)
def inp_out_coord(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select node, st_astext(coord) as coord_geom from coordinates')
objs = read_all(name, 'select node_id, st_astext(geom) as coord_geom from gis.node_geometries')
for obj in objs:
node = obj['node']
node = obj['node_id']
coord = from_postgis_point(obj['coord_geom'])
x = coord['x']
y = coord['y']
@@ -1,4 +1,16 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
def get_label_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -12,7 +24,7 @@ def get_label(name: str, x: float, y: float) -> dict[str, Any]:
d = {}
d['x'] = x
d['y'] = y
l = try_read(name, f'select * from labels where x = {x} and y = {y}')
l = try_read(name, "select label, node_id as node from gis.labels where geom = st_setsrid(st_makepoint(%s, %s), 900914)", (x, y))
if l == None:
d['label'] = None
d['node'] = None
@@ -30,21 +42,16 @@ class Label(object):
self.label = str(input['label'])
self.node = str(input['node']) if 'node' in input and input['node'] != None else None
self.f_type = f"'{self.type}'"
self.f_x = self.x
self.f_y = self.y
self.f_label = f"'{self.label}'"
self.f_node = f"'{self.node}'" if self.node != None else 'null'
self.f_type = sql_literal(self.type)
self.f_x = sql_literal(self.x)
self.f_y = sql_literal(self.y)
self.f_label = sql_literal(self.label)
self.f_node = sql_literal(self.node)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'x': self.x, 'y': self.y, 'label': self.label, 'node': self.node }
def as_xy_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'x': self.x, 'y': self.y }
def _set_label(name: str, cs: ChangeSet) -> DbChangeSet:
old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y']))
def _set_label(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_label(name, cs.operations[0]['x'], cs.operations[0]['y'])
new_dict = cs.operations[0]
@@ -54,45 +61,42 @@ def _set_label(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Label(raw_new)
redo_sql = f"update labels set label = {new.f_label}, node = {new.f_node} where x = {new.f_x} and y = {new.f_y};"
undo_sql = f"update labels set label = {old.f_label}, node = {old.f_node} where x = {old.f_x} and y = {old.f_y};"
statement = f"update gis.labels set label = {new.f_label}, node_id = {new.f_node} where geom = st_setsrid(st_makepoint({new.f_x}, {new.f_y}), 900914);"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
change = g_update_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_label(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_label(name, cs))
def _add_label(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_label(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Label(cs.operations[0])
redo_sql = f"insert into labels (x, y, label, node) values ({new.f_x}, {new.f_y}, {new.f_label}, {new.f_node});"
undo_sql = f"delete from labels where x = {new.f_x} and y = {new.f_y};"
statement = f"insert into gis.labels (id, node_id, label, geom) values ((select coalesce(max(id) + 1, 1) from gis.labels), {new.f_node}, {new.f_label}, st_setsrid(st_makepoint({new.f_x}, {new.f_y}), 900914));"
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_xy_dict()
change = g_add_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_label(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_label(name, cs))
def _delete_label(name: str, cs: ChangeSet) -> DbChangeSet:
old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y']))
def _delete_label(name: str, cs: ChangeSet) -> DatabaseCommand:
x = float(cs.operations[0]['x'])
y = float(cs.operations[0]['y'])
f_x = sql_literal(x)
f_y = sql_literal(y)
redo_sql = f"delete from labels where x = {old.f_x} and y = {old.f_y};"
undo_sql = f"insert into labels (x, y, label, node) values ({old.f_x}, {old.f_y}, {old.f_label}, {old.f_node});"
statement = f"delete from gis.labels where geom = st_setsrid(st_makepoint({f_x}, {f_y}), 900914);"
redo_cs = g_delete_prefix | old.as_xy_dict()
undo_cs = g_add_prefix | old.as_dict()
change = g_delete_prefix | {'type': 'label', 'x': x, 'y': y}
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_label(name: str, cs: ChangeSet) -> ChangeSet:
@@ -110,14 +114,12 @@ def inp_in_label(line: str) -> str:
y = float(tokens[1])
label = str(tokens[2])
node = str(tokens[3]) if num >= 4 else None
node = f"'{node}'" if node != None else 'null'
return str(f"insert into labels (x, y, label, node) values ({x}, {y}, '{label}', {node});")
return str(f"insert into gis.labels (id, node_id, label, geom) values ((select coalesce(max(id) + 1, 1) from gis.labels), {sql_literal(node)}, {sql_literal(label)}, st_setsrid(st_makepoint({sql_literal(x)}, {sql_literal(y)}), 900914));")
def inp_out_label(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from labels')
objs = read_all(name, 'select st_x(geom) as x, st_y(geom) as y, label, node_id as node from gis.labels order by id')
for obj in objs:
x = obj['x']
y = obj['y']
@@ -130,7 +132,7 @@ def inp_out_label(name: str) -> list[str]:
def unset_label_by_node(name: str, node: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select x, y from labels where node = '{node}'")
rows = read_all(name, "select st_x(geom) as x, st_y(geom) as y from gis.labels where node_id = %s", (node,))
for row in rows:
cs.append(g_update_prefix | {'type': 'label', 'x': row['x'], 'y': row['y'], 'node': None})
@@ -1,12 +1,11 @@
import platform
import os
import math
from typing import Any
import pyclipper
from .s0_base import get_node_links, get_link_nodes, is_pipe
from .s5_pipes import get_pipe
from .database import read, try_read, read_all, write
from .s24_coordinates import node_has_coord, get_node_coord
from ..model.elements import get_node_links, get_link_nodes, is_pipe
from ..model.pipes import get_pipe
from ..core.database import read, try_read, read_all
from .coordinates import node_has_coord, get_node_coord
def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]:
@@ -33,17 +32,13 @@ def to_postgis_linestring(boundary: list[tuple[float, float]]) -> str:
def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]:
api = 'get_nodes_in_boundary'
write(name, f"delete from temp_region where id = '{api}'")
write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')")
nodes: list[str] = []
for row in read_all(name, f"select c.node from coordinates as c, temp_region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"):
nodes.append(row['node'])
write(name, f"delete from temp_region where id = '{api}'")
return nodes
rows = read_all(
name,
"select node_id from gis.node_geometries "
"where st_intersects(geom, st_geomfromtext(%s, 900914)) order by node_id",
(to_postgis_polygon(boundary),),
)
return [str(row["node_id"]) for row in rows]
def _get_links_on_boundary(name: str, nodes: list[str]) -> list[str]:
@@ -64,54 +59,36 @@ def _get_links_on_boundary(name: str, nodes: list[str]) -> list[str]:
return links
# if region is general or wda => get_nodes_in_boundary
# if region is dma, sa or vd => get stored nodes in table
def get_nodes_in_region(name: str, region_id: str) -> list[str]:
nodes: list[str] = []
row = try_read(name, f"select r_type from region where id = '{region_id}'")
if row == None:
return nodes
r_type = str(row['r_type'])
if r_type == 'DMA' or r_type == 'SA' or r_type == 'VD':
table = ''
if r_type == 'DMA':
table = 'region_dma'
elif r_type == 'SA':
table = 'region_sa'
elif r_type == 'VD':
table = 'region_vd'
if table != '':
row = try_read(name, f"select nodes from {table} where id = '{region_id}'")
if row != None:
nodes = eval(str(row['nodes']))
if nodes == []:
for row in read_all(name, f"select c.node from coordinates as c, region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{region_id}'"):
nodes.append(row['node'])
return nodes
stored = read_all(
name,
"select node_id from gis.region_nodes where region_id = %s order by node_id",
(region_id,),
)
if stored:
return [str(row["node_id"]) for row in stored]
rows = read_all(
name,
"select n.node_id from gis.node_geometries n join gis.regions r "
"on st_intersects(n.geom, r.boundary) where r.id = %s order by n.node_id",
(region_id,),
)
return [str(row["node_id"]) for row in rows]
def get_links_on_region_boundary(name: str, region_id: str) -> list[str]:
nodes = get_nodes_in_region(name, region_id)
print(nodes)
return _get_links_on_boundary(name, nodes)
def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]:
write(name, f'delete from temp_node')
for node in nodes:
write(name, f"insert into temp_node values ('{node}')")
# TODO: check none
polygon = read(name, f'select st_astext(st_convexhull(st_collect(array(select coord from coordinates where node in (select * from temp_node))))) as boundary' )['boundary']
write(name, f'delete from temp_node')
return from_postgis_polygon(polygon)
row = read(
name,
"select st_astext(st_convexhull(st_collect(geom))) as boundary "
"from gis.node_geometries where node_id = any(%s)",
(nodes,),
)
return from_postgis_polygon(str(row["boundary"]))
def _verify_platform():
@@ -292,115 +269,7 @@ def calculate_boundary(name: str, nodes: list[str], accurate = False) -> list[tu
topology = Topology(name, nodes)
t_nodes = topology.nodes()
t_links = topology.links()
vertices, path, boundary = _calculate_boundary(topology.max_x_node(), t_nodes, t_links)
if not accurate:
return boundary
api = 'calculate_boundary'
write(name, f"delete from temp_region where id = '{api}'")
# use linestring instead of polygon to reduce strict limitation
# TODO: linestring can not work well
write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')")
write(name, f'delete from temp_node')
for node in nodes:
write(name, f"insert into temp_node values ('{node}')")
for row in read_all(name, f"select n.node from coordinates as c, temp_node as n, temp_region as r where c.node = n.node and ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"):
node = row['node']
write(name, f"delete from temp_node where node = '{node}'")
outside_nodes: list[str] = []
for row in read_all(name, "select node from temp_node"):
outside_nodes.append(row['node'])
# no outside nodes, return
if len(outside_nodes) == 0:
write(name, f'delete from temp_node')
write(name, f"delete from temp_region where id = '{api}'")
return boundary
new_nodes: dict[str, Any] = {}
new_links: dict[str, Any] = {}
boundary_links: dict[str, list[str]] = {}
write(name, "delete from temp_link_2")
for node in outside_nodes:
for link in t_nodes[node]['links']:
node1 = t_links[link]['node1']
node2 = t_links[link]['node2']
if node1 in outside_nodes and node2 not in outside_nodes and node2 not in vertices and link:
if link not in boundary:
boundary_links[link] = []
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
if node2 in outside_nodes and node1 not in outside_nodes and node1 not in vertices:
if link not in boundary:
boundary_links[link] = []
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
write(name, f"insert into temp_link_2 values ('{link}', '{line}')")
if node1 in outside_nodes and node2 in outside_nodes:
x1, x2 = t_nodes[node1]['x'], t_nodes[node2]['x']
y1, y2 = t_nodes[node1]['y'], t_nodes[node2]['y']
if node1 not in new_nodes:
new_nodes[node1] = { 'x': x1, 'y': y1, 'links': [] }
if node2 not in new_nodes:
new_nodes[node2] = { 'x': x2, 'y': y2, 'links': [] }
if link not in new_links:
new_links[link] = t_links[link]
# no boundary links, return
if len(boundary_links) == 0:
write(name, "delete from temp_link_2")
write(name, f'delete from temp_node')
write(name, f"delete from temp_region where id = '{api}'")
return boundary
write(name, "delete from temp_link_1")
for link, _ in path.items():
node1 = t_links[link]['node1']
node2 = t_links[link]['node2']
line = f"LINESTRING({t_nodes[node1]['x']} {t_nodes[node1]['y']}, {t_nodes[node2]['x']} {t_nodes[node2]['y']})"
write(name, f"insert into temp_link_1 (link, geom) values ('{link}', '{line}')")
has_intersection = False
for row in read_all(name, f"select l1.link as l, l2.link as r, st_astext(st_intersection(l1.geom, l2.geom)) as p from temp_link_1 as l1, temp_link_2 as l2 where st_intersects(l1.geom, l2.geom)"):
has_intersection = True
link1, link2, pt = str(row['l']), str(row['r']), str(row['p'])
pts = pt.lower().removeprefix('point(').removesuffix(')').split(' ')
xy = (float(pts[0]), float(pts[1]))
new_node = f'NODE_[{link1}]_[{link2}]'
new_nodes[new_node] = { 'x': xy[0], 'y': xy[1], 'links': [] }
path[link1].append(new_node)
boundary_links[link2].append(new_node)
# no intersection, return
if not has_intersection:
write(name, "delete from temp_link_1")
write(name, "delete from temp_link_2")
write(name, 'delete from temp_node')
write(name, f"delete from temp_region where id = '{api}'")
return boundary
new_nodes, new_links = _collect_new_links(path, t_nodes, t_links, new_nodes, new_links)
new_nodes, new_links = _collect_new_links(boundary_links, t_nodes, t_links, new_nodes, new_links)
for link, values in new_links.items():
new_nodes[values['node1']]['links'].append(link)
new_nodes[values['node2']]['links'].append(link)
_, _, boundary = _calculate_boundary(topology.max_x_node(), new_nodes, new_links)
write(name, "delete from temp_link_1")
write(name, "delete from temp_link_2")
write(name, 'delete from temp_node')
write(name, f"delete from temp_region where id = '{api}'")
_, _, boundary = _calculate_boundary(topology.max_x_node(), t_nodes, t_links)
return boundary
@@ -432,7 +301,7 @@ def inflate_boundary(name: str, boundary: list[tuple[float, float]], delta: floa
def inflate_region(name: str, region_id: str, delta: float = 0.5) -> list[tuple[float, float]]:
r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{region_id}'")
r = try_read(name, "select id, st_astext(boundary) as boundary_geom from gis.regions where id = %s", (region_id,))
if r == None:
return []
boundary = from_postgis_polygon(str(r['boundary_geom']))
+127
View File
@@ -0,0 +1,127 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
sql_literal,
try_read,
)
from .region_geometry import from_postgis_polygon, to_postgis_polygon
def get_region_schema(name: str) -> dict[str, dict[str, Any]]:
return {
"id": {"type": "str", "optional": False, "readonly": True},
"region_type": {"type": "str", "optional": False, "readonly": False},
"boundary": {"type": "tuple_list", "optional": False, "readonly": False},
}
def get_region(name: str, id: str) -> dict[str, Any]:
row = try_read(
name,
"select id, region_type, st_astext(boundary) as boundary_geom "
"from gis.regions where id = %s",
(id,),
)
if row is None:
return {}
return {
"id": str(row["id"]),
"region_type": str(row["region_type"]),
"boundary": from_postgis_polygon(str(row["boundary_geom"])),
}
def _valid_boundary(boundary: list[Any]) -> bool:
return len(boundary) >= 4 and boundary[0] == boundary[-1]
def _set_region(name: str, cs: ChangeSet) -> DatabaseCommand:
region_id = cs.operations[0]["id"]
old = get_region(name, region_id)
new = old | {
key: cs.operations[0][key]
for key in ("region_type", "boundary")
if key in cs.operations[0]
}
statement = (
"update gis.regions set "
f"region_type = {sql_literal(new['region_type'])}, "
f"boundary = st_geomfromtext({sql_literal(to_postgis_polygon(new['boundary']))}, 900914) "
f"where id = {sql_literal(region_id)};"
)
return DatabaseCommand(
statement,
[g_update_prefix | {"type": "region"} | new],
)
def set_region(name: str, cs: ChangeSet) -> ChangeSet:
operation = cs.operations[0]
if "id" not in operation or get_region(name, operation["id"]) == {}:
return ChangeSet()
if "boundary" in operation and not _valid_boundary(operation["boundary"]):
return ChangeSet()
return execute_command(name, _set_region(name, cs))
def _add_region(name: str, cs: ChangeSet) -> DatabaseCommand:
operation = cs.operations[0]
region_id = operation["id"]
region_type = str(operation.get("region_type", "none"))
boundary = operation["boundary"]
statement = (
"insert into gis.regions (id, region_type, boundary) values "
f"({sql_literal(region_id)}, {sql_literal(region_type)}, "
f"st_geomfromtext({sql_literal(to_postgis_polygon(boundary))}, 900914));"
)
value = {"type": "region", "id": region_id, "region_type": region_type, "boundary": boundary}
return DatabaseCommand(
statement,
[g_add_prefix | value],
)
def add_region(name: str, cs: ChangeSet) -> ChangeSet:
operation = cs.operations[0]
if "id" not in operation or "boundary" not in operation:
return ChangeSet()
if not _valid_boundary(operation["boundary"]):
return ChangeSet()
if get_region(name, operation["id"]) != {}:
return ChangeSet()
return execute_command(name, _add_region(name, cs))
def _delete_region(name: str, cs: ChangeSet) -> DatabaseCommand:
region_id = cs.operations[0]["id"]
statement = f"delete from gis.regions where id = {sql_literal(region_id)};"
return DatabaseCommand(
statement,
[g_delete_prefix | {"type": "region", "id": region_id}],
)
def delete_region(name: str, cs: ChangeSet) -> ChangeSet:
if "id" not in cs.operations[0] or get_region(name, cs.operations[0]["id"]) == {}:
return ChangeSet()
return execute_command(name, _delete_region(name, cs))
def inp_in_region(line: str) -> str:
tokens = line.split()
return f"insert into gis.regions (id, region_type) values ({sql_literal(tokens[0])}, {sql_literal(tokens[1])});"
def inp_in_bound(line: str) -> str:
return line.split()[0]
def inp_in_regionnodes(line: str) -> str:
tokens = line.split()
return f"insert into gis.region_nodes (region_id, node_id) values ({sql_literal(tokens[0])}, {sql_literal(tokens[1])});"
@@ -1,4 +1,16 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
def get_vertex_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -9,59 +21,48 @@ def get_vertex_schema(name: str) -> dict[str, dict[str, Any]]:
def get_vertex(name: str, link: str) -> dict[str, Any]:
cus = read_all(name, f"select * from vertices where link = '{link}' order by _order")
cus = read_all(name, "select st_x(geom) as x, st_y(geom) as y from gis.link_vertices where link_id = %s order by sequence_no", (link,))
cs = []
for r in cus:
cs.append({ 'x': float(r['x']), 'y': float(r['y']) })
return { 'link': link, 'coords': cs }
def _set_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
def _set_vertex(name: str, cs: ChangeSet) -> DatabaseCommand:
link = cs.operations[0]['link']
old = get_vertex(name, link)
new = { 'link': link, 'coords': [] }
f_link = f"'{link}'"
f_link = sql_literal(link)
# TODO: transaction ?
redo_sql = f"delete from vertices where link = {f_link};"
for xy in cs.operations[0]['coords']:
statement = f"delete from gis.link_vertices where link_id = {f_link};"
for sequence_no, xy in enumerate(cs.operations[0]['coords']):
x, y = float(xy['x']), float(xy['y'])
f_x, f_y = x, y
redo_sql += f"\ninsert into vertices (link, x, y) values ({f_link}, {f_x}, {f_y});"
f_x, f_y = sql_literal(x), sql_literal(y)
statement += f"\ninsert into gis.link_vertices (link_id, sequence_no, geom) values ({f_link}, {sequence_no}, st_setsrid(st_makepoint({f_x}, {f_y}), 900914));"
new['coords'].append({ 'x': x, 'y': y })
undo_sql = f"delete from vertices where link = {f_link};"
for xy in old['coords']:
f_x, f_y = xy['x'], xy['y']
undo_sql += f"\ninsert into vertices (link, x, y) values ({f_link}, {f_x}, {f_y});"
change = { 'type': 'vertex' } | new
redo_cs = { 'type': 'vertex' } | new
undo_cs = { 'type': 'vertex' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_vertex(name: str, cs: ChangeSet) -> ChangeSet:
result = _set_vertex(name, cs)
result.redo_cs[0] |= g_update_prefix
result.undo_cs[0] |= g_update_prefix
result.changes[0] |= g_update_prefix
return execute_command(name, result)
def _add_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_vertex(name: str, cs: ChangeSet) -> DatabaseCommand:
result = _set_vertex(name, cs)
result.redo_cs[0] |= g_add_prefix
result.undo_cs[0] |= g_delete_prefix
result.changes[0] |= g_add_prefix
return result
def _delete_vertex(name: str, cs: ChangeSet) -> DbChangeSet:
def _delete_vertex(name: str, cs: ChangeSet) -> DatabaseCommand:
cs.operations[0]['coords'] = []
result = _set_vertex(name, cs)
result.redo_cs[0] |= g_delete_prefix
result.undo_cs[0] |= g_add_prefix
result.changes[0] |= g_delete_prefix
return result
@@ -77,14 +78,14 @@ def delete_vertex(name: str, cs: ChangeSet) -> ChangeSet:
def get_all_vertex_links(name: str) -> list[str]:
result : list[str] = []
rows = read_all(name, 'select link from vertices order by link')
rows = read_all(name, 'select distinct link_id from gis.link_vertices order by link_id')
for row in rows:
result.append(str(row['link']))
result.append(str(row['link_id']))
return result
def get_all_vertices(name: str) -> list[dict[str, Any]]:
return read_all(name, 'select * from vertices order by link')
return read_all(name, 'select link_id, sequence_no, st_x(geom) as x, st_y(geom) as y from gis.link_vertices order by link_id, sequence_no')
#--------------------------------------------------------------
@@ -99,14 +100,15 @@ def inp_in_vertex(line: str) -> str:
link = tokens[0]
x = float(tokens[1])
y = float(tokens[2])
return str(f"insert into vertices (link, x, y) values ('{link}', {x}, {y});")
link_sql = sql_literal(link)
return f"insert into gis.link_vertices (link_id, sequence_no, geom) values ({link_sql}, (select coalesce(max(sequence_no) + 1, 0) from gis.link_vertices where link_id = {link_sql}), st_setsrid(st_makepoint({sql_literal(x)}, {sql_literal(y)}), 900914));"
def inp_out_vertex(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from vertices order by _order")
objs = read_all(name, "select link_id, st_x(geom) as x, st_y(geom) as y from gis.link_vertices order by link_id, sequence_no")
for obj in objs:
link = obj['link']
link = obj['link_id']
x = obj['x']
y = obj['y']
lines.append(f"{link} {x} {y}")
@@ -114,7 +116,7 @@ def inp_out_vertex(name: str) -> list[str]:
def delete_vertex_by_link(name: str, link: str) -> ChangeSet:
row = try_read(name, f"select * from vertices where link = '{link}'")
row = try_read(name, "select * from gis.link_vertices where link_id = %s", (link,))
if row == None:
return ChangeSet()
return ChangeSet(g_delete_prefix | {'type': 'vertex', 'link' : link})
+1
View File
@@ -0,0 +1 @@
"""EPANET INP import, export, and section mapping."""
@@ -1,35 +1,67 @@
import os
from .project import *
from .database import ChangeSet
from .sections import *
from .s1_title import inp_out_title
from .s2_junctions import inp_out_junction
from .s3_reservoirs import inp_out_reservoir
from .s4_tanks import inp_out_tank
from .s5_pipes import inp_out_pipe
from .s6_pumps import inp_out_pump
from .s7_valves import inp_out_valve
from .s8_tags import inp_out_tag
from .s9_demands import inp_out_demand
from .s10_status import inp_out_status
from .s11_patterns import inp_out_pattern, inp_out_pattern_v3
from .s12_curves import inp_out_curve, inp_out_curve_v3
from .s13_controls import inp_out_control
from .s14_rules import inp_out_rule
from .s15_energy import inp_out_energy
from .s16_emitters import inp_out_emitter
from .s17_quality import inp_out_quality
from .s18_sources import inp_out_source
from .s19_reactions import inp_out_reaction
from .s20_mixing import inp_out_mixing
from .s21_times import inp_out_time
from .s22_report import inp_out_report
from .s23_options import inp_out_option
from .s23_options_v3 import inp_out_option_v3
from .s24_coordinates import inp_out_coord
from .s25_vertices import inp_out_vertex
from .s26_labels import inp_out_label
from .s27_backdrop import inp_out_backdrop
from ..core.projects import close_project, have_project, is_project_open, open_project
from ..core.database import ChangeSet
from .sections import (
BACKDROP,
CONTROLS,
COORDINATES,
CURVES,
DEMANDS,
EMITTERS,
END,
ENERGY,
JUNCTIONS,
LABELS,
MIXING,
OPTIONS,
PATTERNS,
PIPES,
PUMPS,
QUALITY,
REACTIONS,
REPORT,
RESERVOIRS,
RULES,
SOURCES,
STATUS,
TAGS,
TANKS,
TIMES,
TITLE,
VALVES,
VERTICES,
section_name,
section_names_for_epanetv2,
)
from ..model.title import inp_out_title
from ..model.junctions import inp_out_junction
from ..model.reservoirs import inp_out_reservoir
from ..model.tanks import inp_out_tank
from ..model.pipes import inp_out_pipe
from ..model.pumps import inp_out_pump
from ..model.valves import inp_out_valve
from ..model.tags import inp_out_tag
from ..model.demands import inp_out_demand
from ..model.status import inp_out_status
from ..model.patterns import inp_out_pattern, inp_out_pattern_v3
from ..model.curves import inp_out_curve, inp_out_curve_v3
from ..model.controls import inp_out_control
from ..model.rules import inp_out_rule
from ..model.energy import inp_out_energy
from ..model.emitters import inp_out_emitter
from ..model.quality import inp_out_quality
from ..model.sources import inp_out_source
from ..model.reactions import inp_out_reaction
from ..model.mixing import inp_out_mixing
from ..model.times import inp_out_time
from ..model.reports import inp_out_report
from ..model.options_legacy import inp_out_option
from ..model.options_v3 import inp_out_option_v3
from ..gis.coordinates import inp_out_coord
from ..gis.vertices import inp_out_vertex
from ..gis.labels import inp_out_label
from ..gis.backdrop import inp_out_backdrop
#from .s28_end import *
@@ -1,42 +1,84 @@
import datetime
import os
from .project import *
from .database import ChangeSet, write
from .sections import *
from .s0_base import get_region_type
from .s1_title import inp_in_title
from .s2_junctions import inp_in_junction
from .s3_reservoirs import inp_in_reservoir
from .s4_tanks import inp_in_tank
from .s5_pipes import inp_in_pipe
from .s6_pumps import inp_in_pump
from .s7_valves import inp_in_valve
from .s8_tags import inp_in_tag
from .s9_demands import inp_in_demand
from .s10_status import inp_in_status
from .s11_patterns import pattern_v3_types, inp_in_pattern
from .s12_curves import curve_types, inp_in_curve
from .s13_controls import inp_in_control
from .s14_rules import inp_in_rule
from .s15_energy import inp_in_energy
from .s16_emitters import inp_in_emitter
from .s17_quality import inp_in_quality
from .s18_sources import inp_in_source
from .s19_reactions import inp_in_reaction
from .s20_mixing import inp_in_mixing
from .s21_times import inp_in_time
from .s22_report import inp_in_report
from .s23_options import inp_in_option
from .s23_options_v3 import inp_in_option_v3
from .s24_coordinates import inp_in_coord
from .s25_vertices import inp_in_vertex
from .s26_labels import inp_in_label
from .s27_backdrop import inp_in_backdrop
from .s32_region import inp_in_region, inp_in_bound, inp_in_regionnodes
from .s32_region_util import from_postgis_polygon, to_postgis_polygon
from psycopg import sql
from ..core.projects import (
close_project,
create_project,
delete_project,
have_project,
is_project_open,
open_project,
)
from ..core.connection import project_transaction
from ..core.database import ChangeSet, refresh_materialized_views, sql_literal, write
from .sections import (
BACKDROP,
BOUND,
CONTROLS,
COORDINATES,
CURVES,
DEMANDS,
EMITTERS,
ENERGY,
JUNCTIONS,
LABELS,
MIXING,
OPTIONS,
PATTERNS,
PIPES,
PUMPS,
QUALITY,
REACTIONS,
REGION,
REGION_NODES,
REPORT,
RESERVOIRS,
RULES,
SOURCES,
STATUS,
TAGS,
TANKS,
TIMES,
TITLE,
VALVES,
VERTICES,
section_name,
)
from ..model.title import inp_in_title
from ..model.junctions import inp_in_junction
from ..model.reservoirs import inp_in_reservoir
from ..model.tanks import inp_in_tank
from ..model.pipes import inp_in_pipe
from ..model.pumps import inp_in_pump
from ..model.valves import inp_in_valve
from ..model.tags import inp_in_tag
from ..model.demands import inp_in_demand
from ..model.status import inp_in_status
from ..model.patterns import pattern_v3_types, inp_in_pattern
from ..model.curves import curve_types, inp_in_curve
from ..model.controls import inp_in_control
from ..model.rules import inp_in_rule
from ..model.energy import inp_in_energy
from ..model.emitters import inp_in_emitter
from ..model.quality import inp_in_quality
from ..model.sources import inp_in_source
from ..model.reactions import inp_in_reaction
from ..model.mixing import inp_in_mixing
from ..model.times import inp_in_time
from ..model.reports import inp_in_report
from ..model.options_legacy import inp_in_option
from ..model.options_v3 import inp_in_option_v3
from ..gis.coordinates import inp_in_coord
from ..gis.vertices import inp_in_vertex
from ..gis.labels import inp_in_label
from ..gis.backdrop import inp_in_backdrop
from ..gis.regions import inp_in_region, inp_in_bound, inp_in_regionnodes
from ..gis.region_geometry import to_postgis_polygon
# DingZQ, 2024-12-28, export inp
from .inp_out import export_inp
from .exporter import export_inp
_S = "S"
_L = "L"
@@ -205,8 +247,6 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
current_bound = []
current_bound.clear()
region_list = {}
current_region_nodes = []
current_region_nodes.clear()
sql_batch = SQLBatch(project)
_print_time("Second scan...")
@@ -254,7 +294,7 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
if tokens[1].upper() in pattern_v3_types: # v3
sql_batch.add(
f"insert into _pattern (id) values ('{tokens[0]}');"
f"insert into network.patterns (id) values ({sql_literal(tokens[0])});"
)
current_pattern = tokens[0]
if tokens[1].upper() == "VARIABLE":
@@ -263,7 +303,7 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
if current_pattern != tokens[0]:
sql_batch.add(
f"insert into _pattern (id) values ('{tokens[0]}');"
f"insert into network.patterns (id) values ({sql_literal(tokens[0])});"
)
current_pattern = tokens[0]
@@ -272,7 +312,7 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
if tokens[1].upper() in curve_types: # v3
sql_batch.add(
f"insert into _curve (id, type) values ('{tokens[0]}', '{tokens[1].upper()}');"
f"insert into network.curves (id, curve_type) values ({sql_literal(tokens[0])}, {sql_literal(tokens[1].upper())});"
)
current_curve = tokens[0]
continue
@@ -282,51 +322,39 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
if curve_type_desc_line != None:
type = curve_type_desc_line.split(":")[0].strip()
sql_batch.add(
f"insert into _curve (id, type) values ('{tokens[0]}', '{type}');"
f"insert into network.curves (id, curve_type) values ({sql_literal(tokens[0])}, {sql_literal(type)});"
)
current_curve = tokens[0]
curve_type_desc_line = None
elif s == REGION:
tokens = line.split()
region_list[tokens[0]] = tokens[1]
continue
elif s == BOUND:
tokens = line.split()
if tokens[0] != current_region and len(current_bound) > 0:
# insert the previous region after get all the vertex of the attatched geometry
current_bound.append(current_bound[0])
current_geometry = to_postgis_polygon(current_bound)
region_type = map_regiontype[region_list[tokens[0]]]
region_type = map_regiontype.get(
region_list[current_region],
region_list[current_region],
)
sql_batch.add(
f"insert into region(id, boundary,r_type) values ('{current_region}', '{current_geometry}','{region_type}');"
"insert into gis.regions(id, region_type, boundary) "
f"values ({sql_literal(current_region)}, {sql_literal(region_type)}, "
f"st_geomfromtext({sql_literal(current_geometry)}, 900914));"
)
# start the new region
current_bound.clear()
vertex_point = (float(tokens[1]), float(tokens[2]))
current_bound.append(vertex_point)
current_region = tokens[0]
elif s == REGION_NODES:
tokens = line.split()
if (
tokens[0] != current_region
and len(current_region_nodes) > 0
):
# insert the previous region after get all the vertex of the attatched geometry
sql_batch.add(
get_insert_into_region_sql(
current_region, current_region_nodes
)
)
# start the new region
current_region_nodes.clear()
current_region_nodes.append(tokens[1])
current_region = tokens[0]
if s == JUNCTIONS:
sql_batch.add(handler(line, demand_outside))
elif s == PATTERNS:
sql_batch.add(
handler(line, current_pattern not in variable_patterns)
)
elif s == BOUND or s == REGION_NODES:
elif s == BOUND:
continue
else:
sql_batch.add(handler(line))
@@ -342,42 +370,21 @@ def parse_file(project: str, inp: str, version: str = "3") -> None:
if len(current_bound) > 0:
current_bound.append(current_bound[0])
current_geometry = to_postgis_polygon(current_bound)
region_type = map_regiontype[region_list[current_region]]
sql_batch.add(
f"insert into region(id, boundary,r_type) values ('{current_region}', '{current_geometry}','{region_type}');"
region_type = map_regiontype.get(
region_list[current_region],
region_list[current_region],
)
# reset the current region to none for the [REGION_NODES] session reading
# current_region=None
# need to insert the last region_nodes into database
if len(current_region_nodes) > 0:
sql_batch.add(
get_insert_into_region_sql(current_region, current_region_nodes)
"insert into gis.regions(id, region_type, boundary) "
f"values ({sql_literal(current_region)}, {sql_literal(region_type)}, "
f"st_geomfromtext({sql_literal(current_geometry)}, 900914));"
)
# current_region=None
sql_batch.flush()
end = _print_time(f'End reading file "{inp}"')
print(f"Total (in second): {(end-start).seconds}(s)")
def get_insert_into_region_sql(region: str, nodes: list[str]) -> str:
str_sql = ""
str_nodes = str(nodes).replace("'", "''")
r_type = region[0 : region.index("_")]
if r_type == "DMA" or r_type == "SA" or r_type == "VD":
table = ""
if r_type == "DMA":
table = "region_dma"
elif r_type == "SA":
table = "region_sa"
source = region[region.index("_") + 1 :]
str_sql = f"insert into region_sa(id,time_index,source,nodes) values ('{region}', 0,'{source}','{str_nodes}');"
elif r_type == "VD":
table = "region_vd"
return str_sql
def read_inp(project: str, inp: str, version: str = "3") -> bool:
if version != "3" and version != "2":
version = "2"
@@ -391,7 +398,9 @@ def read_inp(project: str, inp: str, version: str = "3") -> bool:
create_project(project)
open_project(project)
parse_file(project, inp, version)
with project_transaction(project):
parse_file(project, inp, version)
refresh_materialized_views(project)
"""try:
parse_file(project, inp, version)
@@ -1,43 +1,3 @@
s1_title = 'title'
s2_junction = 'junction'
s3_reservoir = 'reservoir'
s4_tank = 'tank'
s5_pipe = 'pipe'
s6_pump = 'pump'
s7_valve = 'valve'
s8_tag = 'tag'
s9_demand = 'demand'
s10_status = 'status'
s11_pattern = 'pattern'
s12_curve = 'curve'
s13_control = 'control'
s14_rule = 'rule'
s15_energy = 'energy'
s15_pump_energy = 'pump_energy'
s16_emitter = 'emitter'
s17_quality = 'quality'
s18_source = 'source'
s19_reaction = 'reaction'
s19_pipe_reaction = 'pipe_reaction'
s19_tank_reaction = 'tank_reaction'
s20_mixing = 'mixing'
s21_time = 'time'
s22_report = 'report'
s23_option = 'option'
s23_option_v3 = 'option_v3'
s24_coordinate = 'coordinate'
s25_vertex = 'vertex'
s26_label = 'label'
s27_backdrop = 'backdrop'
s28_end = 'end'
s29_scada_device = 'scada_device'
s30_scada_device_data = 'scada_device_data'
s31_scada_element = 'scada_element'
s32_region = 'region'
s33_dma = 'district_metering_area'
s34_sa = 'service_area'
s35_vd = 'virtual_district'
TITLE = 'TITLE'
JUNCTIONS = 'JUNCTIONS'
RESERVOIRS = 'RESERVOIRS'
@@ -87,4 +47,4 @@ section_names_for_epanetv2 = [TITLE, JUNCTIONS, RESERVOIRS, TANKS, PIPE
PATTERNS, CURVES, CONTROLS, RULES, ENERGY,
EMITTERS, QUALITY, SOURCES, REACTIONS, MIXING,
TIMES, REPORT, OPTIONS, COORDINATES, VERTICES,
LABELS, BACKDROP, END]
LABELS, BACKDROP, END]
+1
View File
@@ -0,0 +1 @@
"""Water-network model persistence grouped by domain entity."""
@@ -1,4 +1,13 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
)
def get_control_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -6,28 +15,21 @@ def get_control_schema(name: str) -> dict[str, dict[str, Any]]:
def get_control(name: str) -> dict[str, Any]:
cs = read_all(name, f"select * from controls")
cs = read_all(name, "select line from network.controls order by sequence_no")
ds = []
for c in cs:
ds.append(c['line'])
return { 'controls': ds }
def _set_control(name: str, cs: ChangeSet) -> DbChangeSet:
old = get_control(name)
def _set_control(name: str, cs: ChangeSet) -> DatabaseCommand:
statement = 'delete from network.controls;'
for sequence_no, line in enumerate(cs.operations[0]['controls']):
statement += f"\ninsert into network.controls (sequence_no, line) values ({sequence_no}, {sql_literal(line)});"
redo_sql = 'delete from controls;'
for line in cs.operations[0]['controls']:
redo_sql += f"\ninsert into controls (line) values ('{line}');"
change = g_update_prefix | { 'type': 'control', 'controls': cs.operations[0]['controls'] }
undo_sql = 'delete from controls;'
for line in old['controls']:
undo_sql += f"\ninsert into controls (line) values ('{line}');"
redo_cs = g_update_prefix | { 'type': 'control', 'controls': cs.operations[0]['controls'] }
undo_cs = g_update_prefix | { 'type': 'control', 'controls': old['controls'] }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_control(name: str, cs: ChangeSet) -> ChangeSet:
@@ -45,7 +47,7 @@ def set_control(name: str, cs: ChangeSet) -> ChangeSet:
def inp_in_control(line: str) -> str:
return str(f"insert into controls (line) values ('{line}');")
return str(f"insert into network.controls (sequence_no, line) values ((select coalesce(max(sequence_no) + 1, 0) from network.controls), {sql_literal(line)});")
def inp_out_control(name: str) -> list[str]:
@@ -1,4 +1,16 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
CURVE_TYPE_PUMP = 'PUMP'
CURVE_TYPE_EFFICIENCY = 'EFFICIENCY'
@@ -16,26 +28,25 @@ def get_curve_schema(name: str) -> dict[str, dict[str, Any]]:
def get_curve(name: str, id: str) -> dict[str, Any]:
c_one = try_read(name, f"select * from _curve where id = '{id}'")
c_one = try_read(name, "select id, curve_type from network.curves where id = %s", (id,))
if c_one == None:
return {}
cus = read_all(name, f"select * from curves where id = '{id}' order by _order")
cus = read_all(name, "select x, y from network.curve_points where curve_id = %s order by sequence_no", (id,))
cs = []
for r in cus:
cs.append({ 'x': float(r['x']), 'y': float(r['y']) })
d = {}
d['id'] = id
d['c_type'] = c_one['type']
d['c_type'] = c_one['curve_type']
d['coords'] = cs
return d
def _set_curve(name: str, cs: ChangeSet) -> DbChangeSet:
def _set_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
old = get_curve(name, id)
old_f_type = f"'{old['c_type']}'"
new = { 'id': id }
if 'coords' in cs.operations[0]:
@@ -46,25 +57,17 @@ def _set_curve(name: str, cs: ChangeSet) -> DbChangeSet:
new['c_type'] = cs.operations[0]['c_type']
else:
new['c_type'] = old['c_type']
new_f_type = f"'{new['c_type']}'"
new_f_type = sql_literal(new['c_type'])
# TODO: transaction ?
redo_sql = f"delete from curves where id = {f_id};"
redo_sql += f"\nupdate _curve set type = {new_f_type} where id = {f_id};"
for xy in new['coords']:
f_x, f_y = xy['x'], xy['y']
redo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
statement = f"delete from network.curve_points where curve_id = {f_id};"
statement += f"\nupdate network.curves set curve_type = {new_f_type} where id = {f_id};"
for sequence_no, xy in enumerate(new['coords']):
f_x, f_y = sql_literal(xy['x']), sql_literal(xy['y'])
statement += f"\ninsert into network.curve_points (curve_id, sequence_no, x, y) values ({f_id}, {sequence_no}, {f_x}, {f_y});"
undo_sql = f"delete from curves where id = {f_id};"
undo_sql += f"\nupdate _curve set type = {old_f_type} where id = {f_id};"
for xy in old['coords']:
f_x, f_y = xy['x'], xy['y']
undo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
change = g_update_prefix | { 'type': 'curve' } | new
redo_cs = g_update_prefix | { 'type': 'curve' } | new
undo_cs = g_update_prefix | { 'type': 'curve' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_curve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -75,28 +78,23 @@ def set_curve(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_curve(name, cs))
def _add_curve(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
new = { 'id': id, 'c_type': cs.operations[0]['c_type'], 'coords': [] }
new_f_type = f"'{new['c_type']}'"
new_f_type = sql_literal(new['c_type'])
# TODO: transaction ?
redo_sql = f"insert into _curve (id, type) values ({f_id}, {new_f_type});"
for xy in cs.operations[0]['coords']:
statement = f"insert into network.curves (id, curve_type) values ({f_id}, {new_f_type});"
for sequence_no, xy in enumerate(cs.operations[0]['coords']):
x, y = float(xy['x']), float(xy['y'])
f_x, f_y = x, y
redo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
f_x, f_y = sql_literal(x), sql_literal(y)
statement += f"\ninsert into network.curve_points (curve_id, sequence_no, x, y) values ({f_id}, {sequence_no}, {f_x}, {f_y});"
new['coords'].append({ 'x': x, 'y': y })
undo_sql = f"delete from curves where id = {f_id};"
undo_sql += f"\ndelete from _curve where id = {f_id};"
change = g_add_prefix | { 'type': 'curve' } | new
redo_cs = g_add_prefix | { 'type': 'curve' } | new
undo_cs = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_curve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -107,26 +105,15 @@ def add_curve(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_curve(name, cs))
def _delete_curve(name: str, cs: ChangeSet) -> DbChangeSet:
def _delete_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
old = get_curve(name, id)
old_f_type = f"'{old['c_type']}'"
statement = f"delete from network.curves where id = {f_id};"
redo_sql = f"delete from curves where id = {f_id};"
redo_sql += f"\ndelete from _curve where id = {f_id};"
change = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
# TODO: transaction ?
undo_sql = f"insert into _curve (id, type) values ({f_id}, {old_f_type});"
for xy in old['coords']:
f_x, f_y = xy['x'], xy['y']
undo_sql += f"\ninsert into curves (id, x, y) values ({f_id}, {f_x}, {f_y});"
redo_cs = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
undo_cs = g_add_prefix | { 'type': 'curve' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_curve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -151,17 +138,18 @@ def delete_curve(name: str, cs: ChangeSet) -> ChangeSet:
def inp_in_curve(line: str) -> str:
tokens = line.split()
return str(f"insert into curves (id, x, y) values ('{tokens[0]}', {float(tokens[1])}, {float(tokens[2])});")
curve_id = sql_literal(tokens[0])
return str(f"insert into network.curve_points (curve_id, sequence_no, x, y) values ({curve_id}, (select coalesce(max(sequence_no) + 1, 0) from network.curve_points where curve_id = {curve_id}), {sql_literal(float(tokens[1]))}, {sql_literal(float(tokens[2]))});")
def inp_out_curve(name: str) -> list[str]:
lines = []
types = read_all(name, f"select * from _curve")
types = read_all(name, "select id, curve_type as type from network.curves order by id")
for type in types:
id = type['id']
# ;type: desc
lines.append(f";{type['type']}:")
objs = read_all(name, f"select * from curves where id = '{id}' order by _order")
objs = read_all(name, "select curve_id as id, x, y from network.curve_points where curve_id = %s order by sequence_no", (id,))
for obj in objs:
id = obj['id']
x = obj['x']
@@ -172,12 +160,12 @@ def inp_out_curve(name: str) -> list[str]:
def inp_out_curve_v3(name: str) -> list[str]:
lines = []
types = read_all(name, f"select * from _curve")
types = read_all(name, "select id, curve_type as type from network.curves order by id")
for type in types:
id = type['id']
# id type
lines.append(f"{id} {type['type']}")
objs = read_all(name, f"select * from curves where id = '{id}' order by _order")
objs = read_all(name, "select curve_id as id, x, y from network.curve_points where curve_id = %s order by sequence_no", (id,))
for obj in objs:
id = obj['id']
x = obj['x']
@@ -1,4 +1,12 @@
from .database import read_all, ChangeSet, DbChangeSet, g_update_prefix, execute_command, try_read
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
from typing import Any
def get_demand_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -10,7 +18,7 @@ def get_demand_schema(name: str) -> dict[str, dict[str, Any]]:
def get_demand(name: str, junction: str) -> dict[str, Any]:
des = read_all(name, f"select * from demands where junction = '{junction}' order by _order")
des = read_all(name, "select base_demand as demand, pattern_id as pattern, category from network.demands where junction_id = %s order by sequence_no", (junction,))
ds = []
for r in des:
d = {}
@@ -21,39 +29,26 @@ def get_demand(name: str, junction: str) -> dict[str, Any]:
return { 'junction': junction, 'demands': ds }
def _set_demand(name: str, cs: ChangeSet) -> DbChangeSet:
def _set_demand(name: str, cs: ChangeSet) -> DatabaseCommand:
junction = cs.operations[0]['junction']
old = get_demand(name, junction)
new = { 'junction': junction, 'demands': [] }
f_junction = f"'{junction}'"
f_junction = sql_literal(junction)
# TODO: transaction ?
redo_sql = f"delete from demands where junction = {f_junction};"
for r in cs.operations[0]['demands']:
statement = f"delete from network.demands where junction_id = {f_junction};"
for sequence_no, r in enumerate(cs.operations[0]['demands']):
demand = float(r['demand'])
pattern = str(r['pattern']) if 'pattern' in r and r['pattern'] != None else None
category = str(r['category']) if 'category' in r and r['category'] != None else None
f_demand = demand
f_pattern = f"'{pattern}'" if pattern is not None else 'null'
f_category = f"'{category}'" if category is not None else 'null'
redo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});"
f_demand = sql_literal(demand)
f_pattern = sql_literal(pattern)
f_category = sql_literal(category)
statement += f"\ninsert into network.demands (junction_id, sequence_no, base_demand, pattern_id, category) values ({f_junction}, {sequence_no}, {f_demand}, {f_pattern}, {f_category});"
new['demands'].append({ 'demand': demand, 'pattern': pattern, 'category': category })
undo_sql = f"delete from demands where junction = {f_junction};"
for r in old['demands']:
demand = float(r['demand'])
pattern = str(r['pattern']) if 'pattern' in r and r['pattern'] != None else None
category = str(r['category']) if 'category' in r and r['category'] != None else None
f_demand = demand
f_pattern = f"'{pattern}'" if pattern is not None else 'null'
f_category = f"'{category}'" if category is not None else 'null'
undo_sql += f"\ninsert into demands (junction, demand, pattern, category) values ({f_junction}, {f_demand}, {f_pattern}, {f_category});"
change = g_update_prefix | { 'type': 'demand' } | new
redo_cs = g_update_prefix | { 'type': 'demand' } | new
undo_cs = g_update_prefix | { 'type': 'demand' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_demand(name: str, cs: ChangeSet) -> ChangeSet:
@@ -76,16 +71,15 @@ def inp_in_demand(line: str) -> str:
junction = str(tokens[0])
demand = float(tokens[1])
pattern = str(tokens[2]) if num_without_desc >= 3 else None
pattern = f"'{pattern}'" if pattern is not None else 'null'
category = str(tokens[3]) if num_without_desc >= 4 else None
category = f"'{category}'" if category is not None else 'null'
return str(f"insert into demands (junction, demand, pattern, category) values ('{junction}', {demand}, {pattern}, {category});")
junction_sql = sql_literal(junction)
return str(f"insert into network.demands (junction_id, sequence_no, base_demand, pattern_id, category) values ({junction_sql}, (select coalesce(max(sequence_no) + 1, 0) from network.demands where junction_id = {junction_sql}), {sql_literal(demand)}, {sql_literal(pattern)}, {sql_literal(category)});")
def inp_out_demand(name: str) -> list[str]:
lines = []
objs = read_all(name, "select * from demands order by _order")
objs = read_all(name, "select junction_id as junction, base_demand as demand, pattern_id as pattern, category from network.demands order by junction_id, sequence_no")
for obj in objs:
junction = obj['junction']
demand = obj['demand']
@@ -96,7 +90,7 @@ def inp_out_demand(name: str) -> list[str]:
def delete_demand_by_junction(name: str, junction: str) -> ChangeSet:
row = try_read(name, f"select * from demands where junction = '{junction}'")
row = try_read(name, "select 1 from network.demands where junction_id = %s", (junction,))
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'demand', 'junction': junction, 'demands': []})
@@ -105,7 +99,7 @@ def delete_demand_by_junction(name: str, junction: str) -> ChangeSet:
def unset_demand_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select distinct junction from demands where pattern = '{pattern}'")
rows = read_all(name, "select distinct junction_id as junction from network.demands where pattern_id = %s", (pattern,))
for row in rows:
ds = get_demand(name, row['junction'])
for d in ds['demands']:
+282
View File
@@ -0,0 +1,282 @@
from typing import Any
from psycopg import sql
from psycopg.rows import Row, dict_row
from ..core.connection import project_connection
from ..core.database import read
_NODE = "network.nodes"
_LINK = "network.links"
_CURVE = "network.curves"
_PATTERN = "network.patterns"
_REGION = "gis.regions"
JUNCTION = "junction"
RESERVOIR = "reservoir"
TANK = "tank"
PIPE = "pipe"
PUMP = "pump"
VALVE = "valve"
PATTERN = "pattern"
CURVE = "curve"
REGION = "region"
ELEMENT_TYPES: dict[str, int] = {
RESERVOIR: 0,
TANK: 1,
JUNCTION: 2,
PIPE: 3,
PUMP: 4,
VALVE: 5,
}
def _table_identifier(table: str):
return sql.Identifier(*table.split("."))
def _get_from(name: str, element_id: str, table: str) -> Row | None:
query = sql.SQL("SELECT * FROM {} WHERE id = %s").format(
_table_identifier(table)
)
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
cur.execute(query, (element_id,))
return cur.fetchone()
def is_node(name: str, element_id: str) -> bool:
return _get_from(name, element_id, _NODE) is not None
def is_junction(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _NODE)
return row is not None and row["node_type"] == JUNCTION
def is_reservoir(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _NODE)
return row is not None and row["node_type"] == RESERVOIR
def is_tank(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _NODE)
return row is not None and row["node_type"] == TANK
def is_link(name: str, element_id: str) -> bool:
return _get_from(name, element_id, _LINK) is not None
def is_pipe(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _LINK)
return row is not None and row["link_type"] == PIPE
def is_pump(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _LINK)
return row is not None and row["link_type"] == PUMP
def is_valve(name: str, element_id: str) -> bool:
row = _get_from(name, element_id, _LINK)
return row is not None and row["link_type"] == VALVE
def get_node_type(name: str, node_id: str) -> str:
row = _get_from(name, node_id, _NODE)
if row is None:
raise LookupError(node_id)
return row["node_type"]
def get_link_type(name: str, link_id: str) -> str:
row = _get_from(name, link_id, _LINK)
if row is None:
raise LookupError(link_id)
return row["link_type"]
def get_element_type(name: str, element_id: str) -> str | None:
if is_node(name, element_id):
return get_node_type(name, element_id)
if is_link(name, element_id):
return get_link_type(name, element_id)
return None
def get_element_type_value(name: str, element_id: str) -> int:
element_type = get_element_type(name, element_id)
if element_type is None:
raise LookupError(element_id)
return ELEMENT_TYPES[element_type]
def is_curve(name: str, element_id: str) -> bool:
return _get_from(name, element_id, _CURVE) is not None
def is_pattern(name: str, element_id: str) -> bool:
return _get_from(name, element_id, _PATTERN) is not None
def is_region(name: str, element_id: str) -> bool:
return _get_from(name, element_id, _REGION) is not None
def _get_all(name: str, table: str) -> list[str]:
query = sql.SQL("SELECT id FROM {} ORDER BY id").format(_table_identifier(table))
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
cur.execute(query)
return [row["id"] for row in cur]
def _get_nodes_by_type(name: str, node_type: str) -> list[str]:
rows = read_all_typed(
name,
"SELECT id FROM network.nodes WHERE node_type = %s ORDER BY id",
(node_type,),
)
return [row["id"] for row in rows]
def _get_links_by_type(name: str, link_type: str) -> list[str]:
rows = read_all_typed(
name,
"SELECT id FROM network.links WHERE link_type = %s ORDER BY id",
(link_type,),
)
return [row["id"] for row in rows]
def read_all_typed(name: str, query: str, params: tuple[Any, ...]) -> list[Row]:
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
cur.execute(query, params)
return cur.fetchall()
def get_nodes(name: str) -> list[str]:
return _get_all(name, _NODE)
def get_nodes_id_and_type(name: str) -> dict[str, str]:
rows = read_all_typed(name, "SELECT id, node_type FROM network.nodes", ())
return {row["id"]: row["node_type"] for row in rows}
def get_major_nodes(name: str, diameter: int) -> list[str]:
rows = read_all_typed(
name,
"""
SELECT DISTINCT endpoint
FROM network.links AS l
JOIN network.pipes AS p ON p.link_id = l.id
CROSS JOIN LATERAL (VALUES (l.start_node_id), (l.end_node_id)) AS e(endpoint)
WHERE p.diameter > %s
""",
(diameter,),
)
return [row["endpoint"] for row in rows]
def get_junctions(name: str) -> list[str]:
return _get_nodes_by_type(name, JUNCTION)
def get_reservoirs(name: str) -> list[str]:
return _get_nodes_by_type(name, RESERVOIR)
def get_tanks(name: str) -> list[str]:
return _get_nodes_by_type(name, TANK)
def get_links(name: str) -> list[str]:
return _get_all(name, _LINK)
def get_links_id_and_type(name: str) -> dict[str, str]:
rows = read_all_typed(name, "SELECT id, link_type FROM network.links", ())
return {row["id"]: row["link_type"] for row in rows}
def get_major_pipes(name: str, diameter: int) -> list[str]:
rows = read_all_typed(
name,
"SELECT link_id FROM network.pipes WHERE diameter > %s ORDER BY link_id",
(diameter,),
)
return [row["link_id"] for row in rows]
def get_pipes(name: str) -> list[str]:
return _get_links_by_type(name, PIPE)
def get_pumps(name: str) -> list[str]:
return _get_links_by_type(name, PUMP)
def get_valves(name: str) -> list[str]:
return _get_links_by_type(name, VALVE)
def get_curves(name: str) -> list[str]:
return _get_all(name, _CURVE)
def get_patterns(name: str) -> list[str]:
return _get_all(name, _PATTERN)
def get_regions(name: str) -> list[str]:
return _get_all(name, _REGION)
def get_node_links(name: str, node_id: str) -> list[str]:
rows = read_all_typed(
name,
"""
SELECT id FROM network.links
WHERE start_node_id = %s OR end_node_id = %s
ORDER BY id
""",
(node_id, node_id),
)
return [row["id"] for row in rows]
def get_all_node_links(name: str) -> dict[str, list[str]]:
"""Build the node adjacency map with one scan of the link table."""
rows = read_all_typed(
name,
"SELECT id, start_node_id, end_node_id FROM network.links ORDER BY id",
(),
)
result: dict[str, list[str]] = {}
for row in rows:
link_id = str(row["id"])
result.setdefault(str(row["start_node_id"]), []).append(link_id)
result.setdefault(str(row["end_node_id"]), []).append(link_id)
return result
def get_link_nodes(name: str, link_id: str) -> list[str]:
row = read(
name,
"""
SELECT start_node_id, end_node_id
FROM network.links WHERE id = %s
""",
(link_id,),
)
return [str(row["start_node_id"]), str(row["end_node_id"])]
def get_region_type(name: str, region_id: str) -> str:
row = read(
name,
"SELECT region_type FROM gis.regions WHERE id = %s",
(region_id,),
)
return row["region_type"]
@@ -1,4 +1,14 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
def get_emitter_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -7,7 +17,7 @@ def get_emitter_schema(name: str) -> dict[str, dict[str, Any]]:
def get_emitter(name: str, junction: str) -> dict[str, Any]:
e = try_read(name, f"select * from emitters where junction = '{junction}'")
e = try_read(name, "select junction_id as junction, coefficient from network.emitters where junction_id = %s", (junction,))
if e == None:
return { 'junction': junction, 'coefficient': None }
d = {}
@@ -22,16 +32,15 @@ class Emitter(object):
self.junction = str(input['junction'])
self.coefficient = float(input['coefficient']) if 'coefficient' in input and input['coefficient'] != None else None
self.f_type = f"'{self.type}'"
self.f_junction = f"'{self.junction}'"
self.f_coefficient = self.coefficient if self.coefficient != None else 'null'
self.f_type = sql_literal(self.type)
self.f_junction = sql_literal(self.junction)
self.f_coefficient = sql_literal(self.coefficient)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'junction': self.junction, 'coefficient': self.coefficient }
def _set_emitter(name: str, cs: ChangeSet) -> DbChangeSet:
old = Emitter(get_emitter(name, cs.operations[0]['junction']))
def _set_emitter(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_emitter(name, cs.operations[0]['junction'])
new_dict = cs.operations[0]
@@ -41,18 +50,13 @@ def _set_emitter(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Emitter(raw_new)
redo_sql = f"delete from emitters where junction = {new.f_junction};"
statement = f"delete from network.emitters where junction_id = {new.f_junction};"
if new.coefficient != None:
redo_sql += f"\ninsert into emitters (junction, coefficient) values ({new.f_junction}, {new.f_coefficient});"
statement += f"\ninsert into network.emitters (junction_id, coefficient) values ({new.f_junction}, {new.f_coefficient});"
undo_sql = f"delete from emitters where junction = {old.f_junction};"
if old.coefficient != None:
undo_sql += f"\ninsert into emitters (junction, coefficient) values ({old.f_junction}, {old.f_coefficient});"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_emitter(name: str, cs: ChangeSet) -> ChangeSet:
@@ -78,12 +82,12 @@ def inp_in_emitter(line: str) -> str:
junction = str(tokens[0])
coefficient = float(tokens[1])
return str(f"insert into emitters (junction, coefficient) values ('{junction}', {coefficient});")
return str(f"insert into network.emitters (junction_id, coefficient) values ({sql_literal(junction)}, {sql_literal(coefficient)});")
def inp_out_emitter(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from emitters')
objs = read_all(name, 'select junction_id as junction, coefficient from network.emitters order by junction_id')
for obj in objs:
junction = obj['junction']
coefficient = obj['coefficient']
@@ -92,7 +96,7 @@ def inp_out_emitter(name: str) -> list[str]:
def delete_emitter_by_junction(name: str, junction: str) -> ChangeSet:
row = try_read(name, f"select * from emitters where junction = '{junction}'")
row = try_read(name, "select 1 from network.emitters where junction_id = %s", (junction,))
if row == None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type' : 'emitter', 'junction': junction, 'coefficient': None})
+220
View File
@@ -0,0 +1,220 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
def get_energy_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'GLOBAL PRICE' : element_schema,
'GLOBAL PATTERN' : element_schema,
'GLOBAL EFFIC' : element_schema,
'DEMAND CHARGE' : element_schema }
def get_energy(name: str) -> dict[str, Any]:
ts = read_all(name, "select key, value from network.energy_settings")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_energy(name: str, cs: ChangeSet) -> DatabaseCommand:
new = {}
new_dict = cs.operations[0]
schema = get_energy_schema(name)
for key in schema.keys():
if key in new_dict:
new[key] = str(new_dict[key])
change = g_update_prefix | { 'type' : 'energy' }
statement = ''
for key, value in new.items():
if statement != '':
statement += '\n'
statement += f"update network.energy_settings set value = {sql_literal(value)} where key = {sql_literal(key)};"
change |= { key: value }
return DatabaseCommand(statement, [change])
def set_energy(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_energy(name, cs))
def get_pump_energy_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'pump' : {'type': 'str' , 'optional': False , 'readonly': True },
'price' : {'type': 'float' , 'optional': True , 'readonly': False},
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False},
'effic' : {'type': 'str' , 'optional': True , 'readonly': False} }
def get_pump_energy(name: str, pump: str) -> dict[str, Any]:
d = {}
d['pump'] = pump
pe = try_read(name, "select price, pattern_id as pattern, efficiency_curve_id as effic from network.pump_energy_settings where pump_id = %s", (pump,))
d['price'] = float(pe['price']) if pe is not None and pe['price'] is not None else None
d['pattern'] = str(pe['pattern']) if pe is not None and pe['pattern'] is not None else None
d['effic'] = str(pe['effic']) if pe is not None and pe['effic'] is not None else None
return d
class PumpEnergy(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'pump_energy'
self.pump = str(input['pump'])
self.price = float(input['price']) if 'price' in input and input['price'] != None else None
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
self.effic = str(input['effic']) if 'effic' in input and input['effic'] != None else None
self.f_type = sql_literal(self.type)
self.f_pump = sql_literal(self.pump)
self.f_price = sql_literal(self.price)
self.f_pattern = sql_literal(self.pattern)
self.f_effic = sql_literal(self.effic)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'pump': self.pump, 'price': self.price, 'pattern': self.pattern, 'effic': self.effic }
def _set_pump_energy(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pump_energy(name, cs.operations[0]['pump'])
new_dict = cs.operations[0]
schema = get_pump_energy_schema(name)
for key, value in schema.items():
if key in new_dict and not value['readonly']:
raw_new[key] = new_dict[key]
new = PumpEnergy(raw_new)
statement = f"delete from network.pump_energy_settings where pump_id = {new.f_pump};"
if new.price is not None or new.pattern is not None or new.effic is not None:
statement += f"\ninsert into network.pump_energy_settings (pump_id, efficiency_curve_id, pattern_id, price) values ({new.f_pump}, {new.f_effic}, {new.f_pattern}, {new.f_price});"
change = g_update_prefix | new.as_dict()
return DatabaseCommand(statement, [change])
def set_pump_energy(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_pump_energy(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3][IN][OUT]
# GLOBAL {PRICE/PATTERN/EFFIC} value
# PUMP id {PRICE/PATTERN/EFFIC} value
# DEMAND CHARGE value
#--------------------------------------------------------------
def inp_in_energy(line: str) -> str:
tokens = line.split()
if tokens[0].upper() == 'PUMP':
pump = tokens[1]
key = tokens[2].lower()
value = tokens[3]
if key == 'price':
value = float(value)
if key == 'efficiency':
key = 'effic'
column = {'price': 'price', 'pattern': 'pattern_id', 'effic': 'efficiency_curve_id'}[key]
return str(f"insert into network.pump_energy_settings (pump_id, {column}) values ({sql_literal(pump)}, {sql_literal(value)}) on conflict (pump_id) do update set {column} = excluded.{column};")
else:
line = line.upper().strip()
for key in get_energy_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
# exception here
if line.startswith('GLOBAL EFFICIENCY'):
value = line.removeprefix('GLOBAL EFFICIENCY').strip()
return str(f"update network.energy_settings set value = {sql_literal(value)} where key = {sql_literal(key)};")
return str('')
def inp_out_energy(name: str) -> list[str]:
lines = []
objs = read_all(name, "select key, value from network.energy_settings order by key")
for obj in objs:
key = obj['key']
value = obj['value']
if value.strip() != '':
lines.append(f'{key} {value}')
objs = read_all(name, "select pump_id as pump, price, pattern_id as pattern, efficiency_curve_id as effic from network.pump_energy_settings order by pump_id")
for obj in objs:
pump = obj['pump']
if obj['price'] is not None:
lines.append(f"PUMP {pump} PRICE {obj['price']}")
if obj['pattern'] is not None:
lines.append(f"PUMP {pump} PATTERN {obj['pattern']}")
if obj['effic'] is not None:
lines.append(f"PUMP {pump} EFFIC {obj['effic']}")
return lines
def delete_pump_energy_by_pump(name: str, pump: str) -> ChangeSet:
row = try_read(
name,
"select pump_id from network.pump_energy_settings where pump_id = %s",
(pump,),
)
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': None, 'pattern': None, 'effic': None})
def unset_pump_energy_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(
name,
"select pump_id as pump, price, efficiency_curve_id as effic "
"from network.pump_energy_settings where pattern_id = %s",
(pattern,),
)
for row in rows:
pump = row['pump']
price = float(row['price']) if row['price'] is not None else None
effic = str(row['effic']) if row['effic'] is not None else None
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': None, 'effic': effic})
return cs
def unset_pump_energy_by_curve(name: str, curve: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(
name,
"select pump_id as pump, price, pattern_id as pattern "
"from network.pump_energy_settings where efficiency_curve_id = %s",
(curve,),
)
for row in rows:
pump = row['pump']
price = float(row['price']) if row['price'] is not None else None
pattern = str(row['pattern']) if row['pattern'] is not None else None
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': pattern, 'effic': None})
return cs
@@ -1,6 +1,20 @@
from .database import *
from .s0_base import *
from .s24_coordinates import *
from typing import Any
from psycopg import sql
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
from ..gis.coordinates import sql_delete_coord, sql_insert_coord, sql_update_coord
from .elements import get_all_node_links
def get_junction_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -12,34 +26,55 @@ def get_junction_schema(name: str) -> dict[str, dict[str, Any]]:
def get_junction(name: str, id: str) -> dict[str, Any]:
j = try_read(name, "select * from junctions where id = %s", (id,))
j = try_read(
name,
"""
SELECT n.id, j.elevation, ST_X(g.geom) AS x, ST_Y(g.geom) AS y,
COALESCE(array_agg(l.id ORDER BY l.id)
FILTER (WHERE l.id IS NOT NULL), '{}') AS links
FROM network.nodes AS n
JOIN network.junctions AS j ON j.node_id = n.id
LEFT JOIN gis.node_geometries AS g ON g.node_id = n.id
LEFT JOIN network.links AS l
ON l.start_node_id = n.id OR l.end_node_id = n.id
WHERE n.id = %s
GROUP BY n.id, j.elevation, g.geom
""",
(id,),
)
if j == None:
return {}
xy = get_node_coord(name, id)
d = {}
d['id'] = str(j['id'])
d['x'] = float(xy['x'])
d['y'] = float(xy['y'])
d['x'] = float(j['x'] or 0.0)
d['y'] = float(j['y'] or 0.0)
d['elevation'] = float(j['elevation'])
d['links'] = get_node_links(name, id)
d['links'] = list(j['links'])
return d
# DingZQ, 2025-03-29
def get_all_junctions(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from junctions")
rows = read_all(
name,
"""
SELECT id, elevation, x, y
FROM gis.junctions
ORDER BY id
""",
)
if rows == None:
return []
links_by_node = get_all_node_links(name)
result = []
for row in rows:
d = {}
id = str(row['id'])
xy = get_node_coord(name, id)
d['id'] = id
d['x'] = float(xy['x'])
d['y'] = float(xy['y'])
d['x'] = float(row['x'] or 0.0)
d['y'] = float(row['y'] or 0.0)
d['elevation'] = float(row['elevation'])
d['links'] = get_node_links(name, id)
d['links'] = links_by_node.get(id, [])
result.append(d)
return result
@@ -52,19 +87,14 @@ class Junction(object):
self.y = float(input['y'])
self.elevation = float(input['elevation'])
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_elevation = self.elevation
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_elevation = sql_literal(self.elevation)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_junction(name: str, cs: ChangeSet) -> DbChangeSet:
old = Junction(get_junction(name, cs.operations[0]['id']))
def _set_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_junction(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -74,16 +104,12 @@ def _set_junction(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Junction(raw_new)
redo_sql = f"update junctions set elevation = {new.f_elevation} where id = {new.f_id};"
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
statement = f"update network.junctions set elevation = {new.f_elevation} where node_id = {new.f_id};"
statement += f"\n{sql_update_coord(new.id, new.x, new.y)}"
undo_sql = sql_update_coord(old.id, old.x, old.y)
undo_sql += f"\nupdate junctions set elevation = {old.f_elevation} where id = {old.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_junction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -94,21 +120,16 @@ def set_junction(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_junction(name, cs))
def _add_junction(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Junction(cs.operations[0])
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into junctions (id, elevation) values ({new.f_id}, {new.f_elevation});"
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
statement = f"insert into network.nodes (id, node_type) values ({new.f_id}, {new.f_type});"
statement += f"\ninsert into network.junctions (node_id, elevation) values ({new.f_id}, {new.f_elevation});"
statement += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
undo_sql = sql_delete_coord(new.id)
undo_sql += f"\ndelete from junctions where id = {new.f_id};"
undo_sql += f"\ndelete from _node where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_junction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -119,21 +140,16 @@ def add_junction(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_junction(name, cs))
def _delete_junction(name: str, cs: ChangeSet) -> DbChangeSet:
old = Junction(get_junction(name, cs.operations[0]['id']))
def _delete_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = sql_delete_coord(old.id)
redo_sql += f"\ndelete from junctions where id = {old.f_id};"
redo_sql += f"\ndelete from _node where id = {old.f_id};"
statement = sql_delete_coord(element_id)
statement += f"\ndelete from network.nodes where id = {f_id};"
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into junctions (id, elevation) values ({old.f_id}, {old.f_elevation});"
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
change = g_delete_prefix | {'type': 'junction', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_junction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -170,19 +186,18 @@ def inp_in_junction(line: str, demand_outside: bool) -> str:
elevation = float(tokens[1])
demand = float(tokens[2]) if num_without_desc >= 3 and tokens[2] != '*' else None
pattern = str(tokens[3]) if num_without_desc >= 4 and tokens[3] != '*' else None
pattern = f"'{pattern}'" if pattern != None else 'null'
desc = str(tokens[-1]) if has_desc else None
sql = f"insert into _node (id, type) values ('{id}', 'junction');insert into junctions (id, elevation) values ('{id}', {elevation});"
sql = f"insert into network.nodes (id, node_type) values ({sql_literal(id)}, 'junction');insert into network.junctions (node_id, elevation) values ({sql_literal(id)}, {sql_literal(elevation)});"
if demand != None and demand_outside == False:
sql += f"insert into demands (junction, demand, pattern) values ('{id}', {demand}, {pattern});"
sql += f"insert into network.demands (junction_id, sequence_no, base_demand, pattern_id) values ({sql_literal(id)}, 0, {sql_literal(demand)}, {sql_literal(pattern)});"
return str(sql)
def inp_out_junction(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from junctions')
objs = read_all(name, 'select node_id as id, elevation from network.junctions order by node_id')
for obj in objs:
id = obj['id']
elev = obj['elevation']
@@ -1,5 +1,16 @@
from .database import *
from .s0_base import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
MIXING_MODEL_MIXED = 'MIXED'
MIXING_MODEL_2COMP = '2COMP'
@@ -13,7 +24,7 @@ def get_mixing_schema(name: str) -> dict[str, dict[str, Any]]:
def get_mixing(name: str, tank: str) -> dict[str, Any]:
m = try_read(name, f"select * from mixing where tank = '{tank}'")
m = try_read(name, "select tank_id as tank, model, value from network.tank_mixing where tank_id = %s", (tank,))
if m == None:
return {}
d = {}
@@ -30,20 +41,15 @@ class Mixing(object):
self.model = str(input['model'])
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
self.f_type = f"'{self.type}'"
self.f_tank = f"'{self.tank}'"
self.f_model = f"'{self.model}'"
self.f_value = self.value if self.value != None else 'null'
self.f_type = sql_literal(self.type)
self.f_tank = sql_literal(self.tank)
self.f_model = sql_literal(self.model)
self.f_value = sql_literal(self.value)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'tank': self.tank, 'model': self.model, 'value': self.value }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'tank': self.tank }
def _set_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
old = Mixing(get_mixing(name, cs.operations[0]['tank']))
def _set_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_mixing(name, cs.operations[0]['tank'])
new_dict = cs.operations[0]
@@ -53,13 +59,11 @@ def _set_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Mixing(raw_new)
redo_sql = f"update mixing set model = {new.f_model}, value = {new.f_value} where tank = {new.f_tank};"
undo_sql = f"update mixing set model = {old.f_model}, value = {old.f_value} where tank = {old.f_tank};"
statement = f"update network.tank_mixing set model = {new.f_model}, value = {new.f_value} where tank_id = {new.f_tank};"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
change = g_update_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_mixing(name: str, cs: ChangeSet) -> ChangeSet:
@@ -70,16 +74,14 @@ def set_mixing(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_mixing(name, cs))
def _add_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Mixing(cs.operations[0])
redo_sql = f"insert into mixing (tank, model, value) values ({new.f_tank}, {new.f_model}, {new.f_value});"
undo_sql = f"delete from mixing where tank = {new.f_tank};"
statement = f"insert into network.tank_mixing (tank_id, model, value) values ({new.f_tank}, {new.f_model}, {new.f_value});"
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
change = g_add_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_mixing(name: str, cs: ChangeSet) -> ChangeSet:
@@ -90,16 +92,15 @@ def add_mixing(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_mixing(name, cs))
def _delete_mixing(name: str, cs: ChangeSet) -> DbChangeSet:
old = Mixing(get_mixing(name, cs.operations[0]['tank']))
def _delete_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
tank = str(cs.operations[0]['tank'])
f_tank = sql_literal(tank)
redo_sql = f"delete from mixing where tank = {old.f_tank};"
undo_sql = f"insert into mixing (tank, model, value) values ({old.f_tank}, {old.f_model}, {old.f_value});"
statement = f"delete from network.tank_mixing where tank_id = {f_tank};"
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
change = g_delete_prefix | {'type': 'mixing', 'tank': tank}
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_mixing(name: str, cs: ChangeSet) -> ChangeSet:
@@ -127,14 +128,12 @@ def inp_in_mixing(line: str) -> str:
tank = str(tokens[0])
model = str(tokens[1].upper())
value = float(tokens[3]) if num_without_desc >= 4 else None
value = value if value != None else 'null'
return str(f"insert into mixing (tank, model, value) values ('{tank}', '{model}', {value});")
return str(f"insert into network.tank_mixing (tank_id, model, value) values ({sql_literal(tank)}, {sql_literal(model)}, {sql_literal(value)});")
def inp_out_mixing(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from mixing')
objs = read_all(name, 'select tank_id as tank, model, value from network.tank_mixing order by tank_id')
for obj in objs:
tank = obj['tank']
model = obj['model']
@@ -144,7 +143,7 @@ def inp_out_mixing(name: str) -> list[str]:
def delete_mixing_by_tank(name: str, tank: str) -> ChangeSet:
row = try_read(name, f"select * from mixing where tank = '{tank}'")
row = try_read(name, "select 1 from network.tank_mixing where tank_id = %s", (tank,))
if row == None:
return ChangeSet()
return ChangeSet(g_delete_prefix | {'type' : 'mixing', 'tank': tank})
@@ -1,4 +1,13 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
)
#--------------------------------------------------------------
@@ -99,45 +108,32 @@ def get_option_schema(name: str) -> dict[str, dict[str, Any]]:
def get_option(name: str) -> dict[str, Any]:
ts = read_all(name, f"select * from options")
ts = read_all(name, "select key, value from network.simulation_settings where engine_version = 'legacy'")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_option(name: str, cs: ChangeSet) -> DbChangeSet:
raw_old = get_option(name)
old = {}
def _set_option(name: str, cs: ChangeSet) -> DatabaseCommand:
new = {}
new_dict = cs.operations[0]
schema = get_option_schema(name)
for key in schema.keys():
if key in new_dict:
old[key] = str(raw_old[key])
new[key] = str(new_dict[key])
redo_cs = g_update_prefix | { 'type' : 'option' }
change = g_update_prefix | { 'type' : 'option' }
redo_sql = ''
statement = ''
for key, value in new.items():
if redo_sql != '':
redo_sql += '\n'
redo_sql += f"update options set value = '{value}' where key = '{key}';"
redo_cs |= { key: value }
if statement != '':
statement += '\n'
statement += f"update network.simulation_settings set value = {sql_literal(value)} where engine_version = 'legacy' and key = {sql_literal(key)};"
change |= { key: value }
undo_cs = g_update_prefix | { 'type' : 'option' }
undo_sql = ''
for key, value in old.items():
if undo_sql != '':
undo_sql += '\n'
undo_sql += f"update options set value = '{value}' where key = '{key}';"
undo_cs |= { key: value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_option(name: str, cs: ChangeSet) -> ChangeSet:
@@ -223,45 +219,32 @@ def get_option_v3_schema(name: str) -> dict[str, dict[str, Any]]:
def get_option_v3(name: str) -> dict[str, Any]:
ts = read_all(name, f"select * from options_v3")
ts = read_all(name, "select key, value from network.simulation_settings where engine_version = 'v3'")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_option_v3(name: str, cs: ChangeSet) -> DbChangeSet:
raw_old = get_option_v3(name)
old = {}
def _set_option_v3(name: str, cs: ChangeSet) -> DatabaseCommand:
new = {}
new_dict = cs.operations[0]
schema = get_option_v3_schema(name)
for key in schema.keys():
if key in new_dict:
old[key] = str(raw_old[key])
new[key] = str(new_dict[key])
redo_cs = g_update_prefix | { 'type' : 'option_v3' }
change = g_update_prefix | { 'type' : 'option_v3' }
redo_sql = ''
statement = ''
for key, value in new.items():
if redo_sql != '':
redo_sql += '\n'
redo_sql += f"update options_v3 set value = '{value}' where key = '{key}';"
redo_cs |= { key: value }
if statement != '':
statement += '\n'
statement += f"update network.simulation_settings set value = {sql_literal(value)} where engine_version = 'v3' and key = {sql_literal(key)};"
change |= { key: value }
undo_cs = g_update_prefix | { 'type' : 'option_v3' }
undo_sql = ''
for key, value in old.items():
if undo_sql != '':
undo_sql += '\n'
undo_sql += f"update options_v3 set value = '{value}' where key = '{key}';"
undo_cs |= { key: value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet:
@@ -398,4 +381,3 @@ def generate_v3(cs: ChangeSet) -> ChangeSet:
return ChangeSet(cs_v3)
return ChangeSet()
@@ -1,81 +1,83 @@
from .database import *
from .s23_options_util import get_option_schema, generate_v3
def _inp_in_option(section: list[str]) -> ChangeSet:
if len(section) <= 0:
return ChangeSet()
cs = g_update_prefix | { 'type' : 'option' }
for s in section:
if s.startswith(';'):
continue
tokens = s.strip().split()
if tokens[0].upper() == 'PATTERN': # can not upper id
value = tokens[1] if len(tokens) > 1 else ''
cs |= { 'PATTERN' : value }
elif tokens[0].upper() == 'QUALITY': # can not upper trace node
value = tokens[1] if len(tokens) > 1 else ''
if len(tokens) > 2:
value += f' {tokens[2]}'
cs |= { 'QUALITY' : value }
else:
line = s.upper().strip()
for key in get_option_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
cs |= { key : value }
result = ChangeSet(cs)
result.merge(generate_v3(result))
return result
def inp_in_option(section: list[str]) -> str:
sql = ''
result = _inp_in_option(section)
for op in result.operations:
for key in op.keys():
if key == 'operation' or key == 'type':
continue
if op['type'] == 'option':
sql += f"update options set value = '{op[key]}' where key = '{key}';"
else:
sql += f"update options_v3 set value = '{op[key]}' where key = '{key}';"
return sql
def inp_out_option(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from options")
is_dda = False
for obj in objs:
if obj['key'] == 'DEMAND MODEL':
is_dda = obj['value'] == 'DDA'
dda_ignore = [
'HEADERROR', # TODO: default is 0 which is conflict with PDA
'FLOWCHANGE', # TODO: default is 0 which is conflict with PDA
'MINIMUM PRESSURE',
'REQUIRED PRESSURE',
'PRESSURE EXPONENT'
]
for obj in objs:
key = obj['key']
# why write this ?
if key == 'PRESSURE':
continue
# release version does not support new keys and has error message
if key == 'HTOL' or key == 'QTOL' or key == 'RQTOL':
continue
# ignore some weird settings for DDA
if is_dda and key in dda_ignore:
continue
value = obj['value']
if str(value).strip() != '':
lines.append(f'{key} {value}')
return lines
from psycopg import sql
from ..core.database import ChangeSet, g_update_prefix, read_all, sql_literal
from .options import get_option_schema, generate_v3
def _inp_in_option(section: list[str]) -> ChangeSet:
if len(section) <= 0:
return ChangeSet()
cs = g_update_prefix | { 'type' : 'option' }
for s in section:
if s.startswith(';'):
continue
tokens = s.strip().split()
if tokens[0].upper() == 'PATTERN': # can not upper id
value = tokens[1] if len(tokens) > 1 else ''
cs |= { 'PATTERN' : value }
elif tokens[0].upper() == 'QUALITY': # can not upper trace node
value = tokens[1] if len(tokens) > 1 else ''
if len(tokens) > 2:
value += f' {tokens[2]}'
cs |= { 'QUALITY' : value }
else:
line = s.upper().strip()
for key in get_option_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
cs |= { key : value }
result = ChangeSet(cs)
result.merge(generate_v3(result))
return result
def inp_in_option(section: list[str]) -> str:
sql = ''
result = _inp_in_option(section)
for op in result.operations:
for key in op.keys():
if key == 'operation' or key == 'type':
continue
if op['type'] == 'option':
sql += f"update network.simulation_settings set value = {sql_literal(op[key])} where engine_version = 'legacy' and key = {sql_literal(key)};"
else:
sql += f"update network.simulation_settings set value = {sql_literal(op[key])} where engine_version = 'v3' and key = {sql_literal(key)};"
return sql
def inp_out_option(name: str) -> list[str]:
lines = []
objs = read_all(name, "select key, value from network.simulation_settings where engine_version = 'legacy' order by key")
is_dda = False
for obj in objs:
if obj['key'] == 'DEMAND MODEL':
is_dda = obj['value'] == 'DDA'
dda_ignore = [
'HEADERROR', # TODO: default is 0 which is conflict with PDA
'FLOWCHANGE', # TODO: default is 0 which is conflict with PDA
'MINIMUM PRESSURE',
'REQUIRED PRESSURE',
'PRESSURE EXPONENT'
]
for obj in objs:
key = obj['key']
# why write this ?
if key == 'PRESSURE':
continue
# release version does not support new keys and has error message
if key == 'HTOL' or key == 'QTOL' or key == 'RQTOL':
continue
# ignore some weird settings for DDA
if is_dda and key in dda_ignore:
continue
value = obj['value']
if str(value).strip() != '':
lines.append(f'{key} {value}')
return lines
@@ -1,5 +1,7 @@
from .database import *
from .s23_options_util import get_option_schema, get_option_v3_schema, generate_v2, generate_v3
from psycopg import sql
from ..core.database import ChangeSet, g_update_prefix, read_all, sql_literal
from .options import get_option_schema, get_option_v3_schema, generate_v2, generate_v3
def _parse_v2(v2_lines: list[str]) -> dict[str, str]:
@@ -62,15 +64,15 @@ def inp_in_option_v3(section: list[str]) -> str:
if key == 'operation' or key == 'type':
continue
if op['type'] == 'option_v3':
sql += f"update options_v3 set value = '{op[key]}' where key = '{key}';"
sql += f"update network.simulation_settings set value = {sql_literal(op[key])} where engine_version = 'v3' and key = {sql_literal(key)};"
else:
sql += f"update options set value = '{op[key]}' where key = '{key}';"
sql += f"update network.simulation_settings set value = {sql_literal(op[key])} where engine_version = 'legacy' and key = {sql_literal(key)};"
return sql
def inp_out_option_v3(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from options_v3")
objs = read_all(name, "select key, value from network.simulation_settings where engine_version = 'v3' order by key")
for obj in objs:
key = obj['key']
value = obj['value']
@@ -1,4 +1,18 @@
from .database import *
from typing import Any
from psycopg import sql
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
PATTERN_V3_TYPE_FIXED = 'FIXED'
PATTERN_V3_TYPE_VARIABLE = 'VARIABLE'
@@ -11,19 +25,19 @@ def get_pattern_schema(name: str) -> dict[str, dict[str, Any]]:
def get_pattern(name: str, id: str) -> dict[str, Any]:
p_one = try_read(name, f"select * from _pattern where id = '{id}'")
p_one = try_read(name, "select id from network.patterns where id = %s", (id,))
if p_one == None:
return {}
pas = read_all(name, f"select * from patterns where id = '{id}' order by _order")
pas = read_all(name, "select factor from network.pattern_values where pattern_id = %s order by sequence_no", (id,))
ps = []
for r in pas:
ps.append(float(r['factor']))
return { 'id': id, 'factors': ps }
def _set_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
def _set_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
old = get_pattern(name, id)
@@ -33,19 +47,14 @@ def _set_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
else:
new['factors'] = old['factors']
# TODO: transaction ?
redo_sql = f"delete from patterns where id = {f_id};"
for f_factor in new['factors']:
redo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
statement = f"delete from network.pattern_values where pattern_id = {f_id};"
for sequence_no, factor in enumerate(new['factors']):
f_factor = sql_literal(factor)
statement += f"\ninsert into network.pattern_values (pattern_id, sequence_no, factor) values ({f_id}, {sequence_no}, {f_factor});"
undo_sql = f"delete from patterns where id = {f_id};"
for f_factor in old['factors']:
undo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
change = g_update_prefix | { 'type': 'pattern' } | new
redo_cs = g_update_prefix | { 'type': 'pattern' } | new
undo_cs = g_update_prefix | { 'type': 'pattern' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_pattern(name: str, cs: ChangeSet) -> ChangeSet:
@@ -56,24 +65,20 @@ def set_pattern(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_pattern(name, cs))
def _add_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
new = { 'id': id, 'factors': cs.operations[0]['factors'] }
# TODO: transaction ?
redo_sql = f"insert into _pattern (id) values ({f_id});"
for f_factor in new['factors']:
redo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
statement = f"insert into network.patterns (id) values ({f_id});"
for sequence_no, factor in enumerate(new['factors']):
f_factor = sql_literal(factor)
statement += f"\ninsert into network.pattern_values (pattern_id, sequence_no, factor) values ({f_id}, {sequence_no}, {f_factor});"
undo_sql = f"delete from patterns where id = {f_id};"
undo_sql += f"\ndelete from _pattern where id = {f_id};"
change = g_add_prefix | { 'type': 'pattern' } | new
redo_cs = g_add_prefix | { 'type': 'pattern' } | new
undo_cs = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_pattern(name: str, cs: ChangeSet) -> ChangeSet:
@@ -84,24 +89,15 @@ def add_pattern(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_pattern(name, cs))
def _delete_pattern(name: str, cs: ChangeSet) -> DbChangeSet:
def _delete_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
id = cs.operations[0]['id']
f_id = f"'{id}'"
f_id = sql_literal(id)
old = get_pattern(name, id)
statement = f"delete from network.patterns where id = {f_id};"
redo_sql = f"delete from patterns where id = {f_id};"
redo_sql += f"\ndelete from _pattern where id = {f_id};"
change = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
# TODO: transaction ?
undo_sql = f"insert into _pattern (id) values ({f_id});"
for f_factor in old['factors']:
undo_sql += f"\ninsert into patterns (id, factor) values ({f_id}, {f_factor});"
redo_cs = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
undo_cs = g_add_prefix | { 'type': 'pattern' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_pattern(name: str, cs: ChangeSet) -> ChangeSet:
@@ -129,18 +125,21 @@ def delete_pattern(name: str, cs: ChangeSet) -> ChangeSet:
def inp_in_pattern(line: str, fixed: bool = True) -> str:
tokens = line.split()
sql = ''
pattern_id = sql_literal(tokens[0])
if fixed:
for token in tokens[1:]:
sql += f"insert into patterns (id, factor) values ('{tokens[0]}', {float(token)});"
factor = sql_literal(float(token))
sql += f"insert into network.pattern_values (pattern_id, sequence_no, factor) values ({pattern_id}, (select coalesce(max(sequence_no) + 1, 0) from network.pattern_values where pattern_id = {pattern_id}), {factor});"
else:
for token in tokens[1::2]:
sql += f"insert into patterns (id, factor) values ('{tokens[0]}', {float(token)});"
factor = sql_literal(float(token))
sql += f"insert into network.pattern_values (pattern_id, sequence_no, factor) values ({pattern_id}, (select coalesce(max(sequence_no) + 1, 0) from network.pattern_values where pattern_id = {pattern_id}), {factor});"
return sql
def inp_out_pattern(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from patterns order by _order")
objs = read_all(name, "select pattern_id as id, factor from network.pattern_values order by pattern_id, sequence_no")
for obj in objs:
id = obj['id']
factor = obj['factor']
@@ -150,7 +149,7 @@ def inp_out_pattern(name: str) -> list[str]:
def inp_out_pattern_v3(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from patterns order by _order")
objs = read_all(name, "select pattern_id as id, factor from network.pattern_values order by pattern_id, sequence_no")
ids = []
for obj in objs:
id = obj['id']
@@ -1,5 +1,16 @@
from .database import *
from .s0_base import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
PIPE_STATUS_OPEN = 'OPEN'
@@ -19,7 +30,7 @@ def get_pipe_schema(name: str) -> dict[str, dict[str, Any]]:
def get_pipe(name: str, id: str) -> dict[str, Any]:
p = try_read(name, f"select * from pipes where id = '{id}'")
p = try_read(name, "select l.id, l.start_node_id as node1, l.end_node_id as node2, p.length, p.diameter, p.roughness, p.minor_loss, p.status from network.links l join network.pipes p on p.link_id = l.id where l.id = %s", (id,))
if p == None:
return {}
d = {}
@@ -35,7 +46,11 @@ def get_pipe(name: str, id: str) -> dict[str, Any]:
# DingZQ, 2025-03-29
def get_all_pipes(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from pipes")
rows = read_all(
name,
"SELECT id, start_node_id AS node1, end_node_id AS node2, length, "
"diameter, roughness, minor_loss, status FROM gis.pipes ORDER BY id",
)
if rows == None:
return []
@@ -72,7 +87,11 @@ def get_pipes_by_property(
'status',
]
rows = read_all(name, "select * from pipes")
rows = read_all(
name,
"SELECT id, start_node_id AS node1, end_node_id AS node2, length, "
"diameter, roughness, minor_loss, status FROM gis.pipes ORDER BY id",
)
if rows == None:
return []
@@ -112,25 +131,20 @@ class Pipe(object):
self.minor_loss = float(input['minor_loss'])
self.status = str(input['status'])
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_node1 = f"'{self.node1}'"
self.f_node2 = f"'{self.node2}'"
self.f_length = self.length
self.f_diameter = self.diameter
self.f_roughness = self.roughness
self.f_minor_loss = self.minor_loss
self.f_status = f"'{self.status}'"
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_node1 = sql_literal(self.node1)
self.f_node2 = sql_literal(self.node2)
self.f_length = sql_literal(self.length)
self.f_diameter = sql_literal(self.diameter)
self.f_roughness = sql_literal(self.roughness)
self.f_minor_loss = sql_literal(self.minor_loss)
self.f_status = sql_literal(self.status)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'length': self.length, 'diameter': self.diameter, 'roughness': self.roughness, 'minor_loss': self.minor_loss, 'status': self.status }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
old = Pipe(get_pipe(name, cs.operations[0]['id']))
def _set_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pipe(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -140,13 +154,11 @@ def _set_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Pipe(raw_new)
redo_sql = f"update pipes set node1 = {new.f_node1}, node2 = {new.f_node2}, length = {new.f_length}, diameter = {new.f_diameter}, roughness = {new.f_roughness}, minor_loss = {new.f_minor_loss}, status = {new.f_status} where id = {new.f_id};"
undo_sql = f"update pipes set node1 = {old.f_node1}, node2 = {old.f_node2}, length = {old.f_length}, diameter = {old.f_diameter}, roughness = {old.f_roughness}, minor_loss = {old.f_minor_loss}, status = {old.f_status} where id = {old.f_id};"
statement = f"update network.links set start_node_id = {new.f_node1}, end_node_id = {new.f_node2} where id = {new.f_id};"
statement += f"\nupdate network.pipes set length = {new.f_length}, diameter = {new.f_diameter}, roughness = {new.f_roughness}, minor_loss = {new.f_minor_loss}, status = {new.f_status} where link_id = {new.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_pipe(name: str, cs: ChangeSet) -> ChangeSet:
@@ -157,19 +169,15 @@ def set_pipe(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_pipe(name, cs))
def _add_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Pipe(cs.operations[0])
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_length}, {new.f_diameter}, {new.f_roughness}, {new.f_minor_loss}, {new.f_status});"
statement = f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({new.f_id}, {new.f_type}, {new.f_node1}, {new.f_node2});"
statement += f"\ninsert into network.pipes (link_id, length, diameter, roughness, minor_loss, status) values ({new.f_id}, {new.f_length}, {new.f_diameter}, {new.f_roughness}, {new.f_minor_loss}, {new.f_status});"
undo_sql = f"delete from pipes where id = {new.f_id};"
undo_sql += f"\ndelete from _link where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_pipe(name: str, cs: ChangeSet) -> ChangeSet:
@@ -180,19 +188,15 @@ def add_pipe(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_pipe(name, cs))
def _delete_pipe(name: str, cs: ChangeSet) -> DbChangeSet:
old = Pipe(get_pipe(name, cs.operations[0]['id']))
def _delete_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = f"delete from pipes where id = {old.f_id};"
redo_sql += f"\ndelete from _link where id = {old.f_id};"
statement = f"delete from network.links where id = {f_id};"
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_length}, {old.f_diameter}, {old.f_roughness}, {old.f_minor_loss}, {old.f_status});"
change = g_delete_prefix | {'type': 'pipe', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_pipe(name: str, cs: ChangeSet) -> ChangeSet:
@@ -230,12 +234,12 @@ def inp_in_pipe(line: str) -> str:
status = str(tokens[7].upper()) if num_without_desc >= 8 else PIPE_STATUS_OPEN
desc = str(tokens[-1]) if has_desc else None
return str(f"insert into _link (id, type) values ('{id}', 'pipe');insert into pipes (id, node1, node2, length, diameter, roughness, minor_loss, status) values ('{id}', '{node1}', '{node2}', {length}, {diameter}, {roughness}, {minor_loss}, '{status}');")
return str(f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({sql_literal(id)}, 'pipe', {sql_literal(node1)}, {sql_literal(node2)});insert into network.pipes (link_id, length, diameter, roughness, minor_loss, status) values ({sql_literal(id)}, {sql_literal(length)}, {sql_literal(diameter)}, {sql_literal(roughness)}, {sql_literal(minor_loss)}, {sql_literal(status)});")
def inp_out_pipe(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from pipes')
objs = read_all(name, 'select l.id, l.start_node_id as node1, l.end_node_id as node2, p.length, p.diameter, p.roughness, p.minor_loss, p.status from network.links l join network.pipes p on p.link_id = l.id order by l.id')
for obj in objs:
id = obj['id']
node1 = obj['node1']
@@ -248,13 +252,3 @@ def inp_out_pipe(name: str) -> list[str]:
desc = ';'
lines.append(f'{id} {node1} {node2} {length} {diameter} {roughness} {minor_loss} {status} {desc}')
return lines
'''def delete_pipe_by_node(name: str, node: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from pipes where node1 = '{node}' or node2 = '{node}'")
for row in rows:
cs.append(g_delete_prefix | {'type': 'pipe', 'id': row['id']})
return cs'''
@@ -1,5 +1,16 @@
from .database import *
from .s0_base import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
def get_pump_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -13,7 +24,7 @@ def get_pump_schema(name: str) -> dict[str, dict[str, Any]]:
def get_pump(name: str, id: str) -> dict[str, Any]:
p = try_read(name, f"select * from pumps where id = '{id}'")
p = try_read(name, "select l.id, l.start_node_id as node1, l.end_node_id as node2, p.power, p.head_curve_id as head, p.speed, p.pattern_id as pattern from network.links l join network.pumps p on p.link_id = l.id where l.id = %s", (id,))
if p == None:
return {}
d = {}
@@ -28,7 +39,12 @@ def get_pump(name: str, id: str) -> dict[str, Any]:
# DingZQ, 2025-03-29
def get_all_pumps(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from pumps")
rows = read_all(
name,
"SELECT id, start_node_id AS node1, end_node_id AS node2, power, "
"head_curve_id AS head, speed, pattern_id AS pattern "
"FROM gis.pumps ORDER BY id",
)
if rows == None:
return []
@@ -59,24 +75,19 @@ class Pump(object):
self.speed = float(input['speed']) if 'speed' in input and input['speed'] != None else None
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_node1 = f"'{self.node1}'"
self.f_node2 = f"'{self.node2}'"
self.f_power = self.power if self.power != None else 'null'
self.f_head = f"'{self.head}'" if self.head != None else 'null'
self.f_speed = self.speed if self.speed != None else 'null'
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_node1 = sql_literal(self.node1)
self.f_node2 = sql_literal(self.node2)
self.f_power = sql_literal(self.power)
self.f_head = sql_literal(self.head)
self.f_speed = sql_literal(self.speed)
self.f_pattern = sql_literal(self.pattern)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'power': self.power, 'head': self.head, 'speed': self.speed, 'pattern': self.pattern }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_pump(name: str, cs: ChangeSet) -> DbChangeSet:
old = Pump(get_pump(name, cs.operations[0]['id']))
def _set_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pump(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -86,13 +97,11 @@ def _set_pump(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Pump(raw_new)
redo_sql = f"update pumps set node1 = {new.f_node1}, node2 = {new.f_node2}, power = {new.f_power}, head = {new.f_head}, speed = {new.f_speed}, pattern = {new.f_pattern} where id = {new.f_id};"
undo_sql = f"update pumps set node1 = {old.f_node1}, node2 = {old.f_node2}, power = {old.f_power}, head = {old.f_head}, speed = {old.f_speed}, pattern = {old.f_pattern} where id = {old.f_id};"
statement = f"update network.links set start_node_id = {new.f_node1}, end_node_id = {new.f_node2} where id = {new.f_id};"
statement += f"\nupdate network.pumps set power = {new.f_power}, head_curve_id = {new.f_head}, speed = {new.f_speed}, pattern_id = {new.f_pattern} where link_id = {new.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_pump(name: str, cs: ChangeSet) -> ChangeSet:
@@ -103,19 +112,15 @@ def set_pump(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_pump(name, cs))
def _add_pump(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Pump(cs.operations[0])
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_power}, {new.f_head}, {new.f_speed}, {new.f_pattern});"
statement = f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({new.f_id}, {new.f_type}, {new.f_node1}, {new.f_node2});"
statement += f"\ninsert into network.pumps (link_id, power, head_curve_id, speed, pattern_id) values ({new.f_id}, {new.f_power}, {new.f_head}, {new.f_speed}, {new.f_pattern});"
undo_sql = f"delete from pumps where id = {new.f_id};"
undo_sql += f"\ndelete from _link where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_pump(name: str, cs: ChangeSet) -> ChangeSet:
@@ -126,19 +131,15 @@ def add_pump(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_pump(name, cs))
def _delete_pump(name: str, cs: ChangeSet) -> DbChangeSet:
old = Pump(get_pump(name, cs.operations[0]['id']))
def _delete_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = f"delete from pumps where id = {old.f_id};"
redo_sql += f"\ndelete from _link where id = {old.f_id};"
statement = f"delete from network.links where id = {f_id};"
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into pumps (id, node1, node2, power, head, speed, pattern) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_power}, {old.f_head}, {old.f_speed}, {old.f_pattern});"
change = g_delete_prefix | {'type': 'pump', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_pump(name: str, cs: ChangeSet) -> ChangeSet:
@@ -170,21 +171,17 @@ def inp_in_pump(line: str) -> str:
for i in range(3, num_without_desc, 2):
props |= { tokens[i].lower(): tokens[i + 1] }
power = float(props['power']) if 'power' in props else None
power = power if power != None else 'null'
head = str(props['head']) if 'head' in props else None
head = f"'{head}'" if head != None else 'null'
speed = float(props['speed']) if 'speed' in props else None
speed = speed if speed != None else 'null'
pattern = str(props['pattern']) if 'pattern' in props else None
pattern = f"'{pattern}'" if pattern != None else 'null'
desc = str(tokens[-1]) if has_desc else None
return str(f"insert into _link (id, type) values ('{id}', 'pump');insert into pumps (id, node1, node2, power, head, speed, pattern) values ('{id}', '{node1}', '{node2}', {power}, {head}, {speed}, {pattern});")
return str(f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({sql_literal(id)}, 'pump', {sql_literal(node1)}, {sql_literal(node2)});insert into network.pumps (link_id, power, head_curve_id, speed, pattern_id) values ({sql_literal(id)}, {sql_literal(power)}, {sql_literal(head)}, {sql_literal(speed)}, {sql_literal(pattern)});")
def inp_out_pump(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from pumps')
objs = read_all(name, 'select l.id, l.start_node_id as node1, l.end_node_id as node2, p.power, p.head_curve_id as head, p.speed, p.pattern_id as pattern from network.links l join network.pumps p on p.link_id = l.id order by l.id')
for obj in objs:
id = obj['id']
node1 = obj['node1']
@@ -198,20 +195,10 @@ def inp_out_pump(name: str) -> list[str]:
return lines
'''def delete_pump_by_node(name: str, node: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from pumps where node1 = '{node}' or node2 = '{node}'")
for row in rows:
cs.append(g_delete_prefix | {'type': 'pump', 'id': row['id']})
return cs'''
def unset_pump_by_curve(name: str, curve: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select * from pumps where head = '{curve}'")
rows = read_all(name, "select link_id as id, power from network.pumps where head_curve_id = %s", (curve,))
for row in rows:
if row['power'] != None:
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'head': None})
@@ -224,7 +211,7 @@ def unset_pump_by_curve(name: str, curve: str) -> ChangeSet:
def unset_pump_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from pumps where pattern = '{pattern}'")
rows = read_all(name, "select link_id as id from network.pumps where pattern_id = %s", (pattern,))
for row in rows:
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'pattern': None})
@@ -1,4 +1,14 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
def get_quality_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -7,7 +17,7 @@ def get_quality_schema(name: str) -> dict[str, dict[str, Any]]:
def get_quality(name: str, node: str) -> dict[str, Any]:
e = try_read(name, f"select * from quality where node = '{node}'")
e = try_read(name, "select node_id as node, value as quality from network.initial_quality where node_id = %s", (node,))
if e == None:
return { 'node': node, 'quality': None }
d = {}
@@ -22,16 +32,15 @@ class Quality(object):
self.node = str(input['node'])
self.quality = float(input['quality']) if 'quality' in input and input['quality'] != None else None
self.f_type = f"'{self.type}'"
self.f_node = f"'{self.node}'"
self.f_quality = self.quality if self.quality != None else 'null'
self.f_type = sql_literal(self.type)
self.f_node = sql_literal(self.node)
self.f_quality = sql_literal(self.quality)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'node': self.node, 'quality': self.quality }
def _set_quality(name: str, cs: ChangeSet) -> DbChangeSet:
old = Quality(get_quality(name, cs.operations[0]['node']))
def _set_quality(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_quality(name, cs.operations[0]['node'])
new_dict = cs.operations[0]
@@ -41,18 +50,13 @@ def _set_quality(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Quality(raw_new)
redo_sql = f"delete from quality where node = {new.f_node};"
statement = f"delete from network.initial_quality where node_id = {new.f_node};"
if new.quality != None:
redo_sql += f"\ninsert into quality (node, quality) values ({new.f_node}, {new.f_quality});"
statement += f"\ninsert into network.initial_quality (node_id, value) values ({new.f_node}, {new.f_quality});"
undo_sql = f"delete from quality where node = {old.f_node};"
if old.quality != None:
undo_sql += f"\ninsert into quality (node, quality) values ({old.f_node}, {old.f_quality});"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_quality(name: str, cs: ChangeSet) -> ChangeSet:
@@ -75,12 +79,12 @@ def inp_in_quality(line: str) -> str:
node = str(tokens[0])
quality = float(tokens[1])
return str(f"insert into quality (node, quality) values ('{node}', {quality});")
return str(f"insert into network.initial_quality (node_id, value) values ({sql_literal(node)}, {sql_literal(quality)});")
def inp_out_quality(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from quality')
objs = read_all(name, 'select node_id as node, value as quality from network.initial_quality order by node_id')
for obj in objs:
node = obj['node']
quality = obj['quality']
@@ -89,7 +93,7 @@ def inp_out_quality(name: str) -> list[str]:
def delete_quality_by_node(name: str, node: str) -> ChangeSet:
row = try_read(name, f"select * from quality where node = '{node}'")
row = try_read(name, "select 1 from network.initial_quality where node_id = %s", (node,))
if row == None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type' : 'quality', 'node': node, 'quality': None})
@@ -1,4 +1,14 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
@@ -15,45 +25,32 @@ def get_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
def get_reaction(name: str) -> dict[str, Any]:
ts = read_all(name, f"select * from reactions")
ts = read_all(name, "select key, value from network.reaction_settings")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
raw_old = get_reaction(name)
old = {}
def _set_reaction(name: str, cs: ChangeSet) -> DatabaseCommand:
new = {}
new_dict = cs.operations[0]
schema = get_reaction_schema(name)
for key in schema.keys():
if key in new_dict:
old[key] = str(raw_old[key])
new[key] = str(new_dict[key])
redo_cs = g_update_prefix | { 'type' : 'reaction' }
change = g_update_prefix | { 'type' : 'reaction' }
redo_sql = ''
statement = ''
for key, value in new.items():
if redo_sql != '':
redo_sql += '\n'
redo_sql += f"update reactions set value = '{value}' where key = '{key}';"
redo_cs |= { key: value }
if statement != '':
statement += '\n'
statement += f"update network.reaction_settings set value = {sql_literal(value)} where key = {sql_literal(key)};"
change |= { key: value }
undo_cs = g_update_prefix | { 'type' : 'reaction' }
undo_sql = ''
for key, value in old.items():
if undo_sql != '':
undo_sql += '\n'
undo_sql += f"update reactions set value = '{value}' where key = '{key}';"
undo_cs |= { key: value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_reaction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -69,10 +66,9 @@ def get_pipe_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
def get_pipe_reaction(name: str, pipe: str) -> dict[str, Any]:
d = {}
d['pipe'] = pipe
pr = try_read(name, f"select * from reactions_pipe_bulk where pipe = '{pipe}'")
d['bulk'] = float(pr['value']) if pr != None else None
pr = try_read(name, f"select * from reactions_pipe_wall where pipe = '{pipe}'")
d['wall'] = float(pr['value']) if pr != None else None
pr = try_read(name, "select bulk_coefficient as bulk, wall_coefficient as wall from network.pipe_reaction_coefficients where pipe_id = %s", (pipe,))
d['bulk'] = float(pr['bulk']) if pr is not None and pr['bulk'] is not None else None
d['wall'] = float(pr['wall']) if pr is not None and pr['wall'] is not None else None
return d
@@ -83,17 +79,16 @@ class PipeReaction(object):
self.bulk = float(input['bulk']) if 'bulk' in input and input['bulk'] != None else None
self.wall = float(input['wall']) if 'wall' in input and input['wall'] != None else None
self.f_type = f"'{self.type}'"
self.f_pipe = f"'{self.pipe}'"
self.f_bulk = self.bulk if self.bulk != None else 'null'
self.f_wall = self.wall if self.wall != None else 'null'
self.f_type = sql_literal(self.type)
self.f_pipe = sql_literal(self.pipe)
self.f_bulk = sql_literal(self.bulk)
self.f_wall = sql_literal(self.wall)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'pipe': self.pipe, 'bulk': self.bulk, 'wall': self.wall }
def _set_pipe_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
old = PipeReaction(get_pipe_reaction(name, cs.operations[0]['pipe']))
def _set_pipe_reaction(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_pipe_reaction(name, cs.operations[0]['pipe'])
new_dict = cs.operations[0]
@@ -103,22 +98,13 @@ def _set_pipe_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = PipeReaction(raw_new)
redo_sql = f"delete from reactions_pipe_bulk where pipe = {new.f_pipe};\ndelete from reactions_pipe_wall where pipe = {new.f_pipe};"
if new.bulk != None:
redo_sql += f"\ninsert into reactions_pipe_bulk (pipe, value) values ({new.f_pipe}, {new.f_bulk});"
if new.wall != None:
redo_sql += f"\ninsert into reactions_pipe_wall (pipe, value) values ({new.f_pipe}, {new.f_wall});"
statement = f"delete from network.pipe_reaction_coefficients where pipe_id = {new.f_pipe};"
if new.bulk is not None or new.wall is not None:
statement += f"\ninsert into network.pipe_reaction_coefficients (pipe_id, bulk_coefficient, wall_coefficient) values ({new.f_pipe}, {new.f_bulk}, {new.f_wall});"
undo_sql = f"delete from reactions_pipe_bulk where pipe = {old.f_pipe};\ndelete from reactions_pipe_wall where pipe = {old.f_pipe};"
if old.bulk != None:
undo_sql += f"\ninsert into reactions_pipe_bulk (pipe, value) values ({old.f_pipe}, {old.f_bulk});"
if old.wall != None:
undo_sql += f"\ninsert into reactions_pipe_wall (pipe, value) values ({old.f_pipe}, {old.f_wall});"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_pipe_reaction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -133,8 +119,8 @@ def get_tank_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
def get_tank_reaction(name: str, tank: str) -> dict[str, Any]:
d = {}
d['tank'] = tank
pr = try_read(name, f"select * from reactions_tank where tank = '{tank}'")
d['value'] = float(pr['value']) if pr != None else None
pr = try_read(name, "select coefficient as value from network.tank_reaction_coefficients where tank_id = %s", (tank,))
d['value'] = float(pr['value']) if pr is not None else None
return d
@@ -144,16 +130,15 @@ class TankReaction(object):
self.tank = str(input['tank'])
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
self.f_type = f"'{self.type}'"
self.f_tank = f"'{self.tank}'"
self.f_value = self.value if self.value != None else 'null'
self.f_type = sql_literal(self.type)
self.f_tank = sql_literal(self.tank)
self.f_value = sql_literal(self.value)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'tank': self.tank, 'value': self.value }
def _set_tank_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
old = TankReaction(get_tank_reaction(name, cs.operations[0]['tank']))
def _set_tank_reaction(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_tank_reaction(name, cs.operations[0]['tank'])
new_dict = cs.operations[0]
@@ -163,18 +148,13 @@ def _set_tank_reaction(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = TankReaction(raw_new)
redo_sql = f"delete from reactions_tank where tank = {new.f_tank};"
statement = f"delete from network.tank_reaction_coefficients where tank_id = {new.f_tank};"
if new.value != None:
redo_sql += f"\ninsert into reactions_tank (tank, value) values ({new.f_tank}, {new.f_value});"
statement += f"\ninsert into network.tank_reaction_coefficients (tank_id, coefficient) values ({new.f_tank}, {new.f_value});"
undo_sql = f"delete from reactions_tank where tank = {old.f_tank};"
if old.value != None:
undo_sql += f"\ninsert into reactions_tank (tank, value) values ({old.f_tank}, {old.f_value});"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_tank_reaction(name: str, cs: ChangeSet) -> ChangeSet:
@@ -200,20 +180,21 @@ def inp_in_reaction(line: str) -> str:
if token0 == 'BULK' or token0 == 'WALL':
pipe = tokens[1]
key = token0.lower()
value = tokens[2]
return str(f"insert into reactions_pipe_{key} (pipe, value) values ('{pipe}', {value});")
value = float(tokens[2])
column = 'bulk_coefficient' if key == 'bulk' else 'wall_coefficient'
return str(f"insert into network.pipe_reaction_coefficients (pipe_id, {column}) values ({sql_literal(pipe)}, {sql_literal(value)}) on conflict (pipe_id) do update set {column} = excluded.{column};")
elif token0 == 'TANK':
tank = tokens[1]
value = tokens[2]
return str(f"insert into reactions_tank (tank, value) values ('{tank}', {value});")
value = float(tokens[2])
return str(f"insert into network.tank_reaction_coefficients (tank_id, coefficient) values ({sql_literal(tank)}, {sql_literal(value)});")
else:
line = line.upper().strip()
for key in get_reaction_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
return str(f"update reactions set value = '{value}' where key = '{key}';")
return str(f"update network.reaction_settings set value = {sql_literal(value)} where key = {sql_literal(key)};")
return str('')
@@ -221,25 +202,25 @@ def inp_in_reaction(line: str) -> str:
def inp_out_reaction(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from reactions")
objs = read_all(name, "select key, value from network.reaction_settings order by key")
for obj in objs:
key = obj['key']
value = obj['value']
lines.append(f'{key} {value}')
objs = read_all(name, f"select * from reactions_pipe_bulk")
objs = read_all(name, "select pipe_id as pipe, bulk_coefficient as value from network.pipe_reaction_coefficients where bulk_coefficient is not null order by pipe_id")
for obj in objs:
pipe = obj['pipe']
value = obj['value']
lines.append(f'BULK {pipe} {value}')
objs = read_all(name, f"select * from reactions_pipe_wall")
objs = read_all(name, "select pipe_id as pipe, wall_coefficient as value from network.pipe_reaction_coefficients where wall_coefficient is not null order by pipe_id")
for obj in objs:
pipe = obj['pipe']
value = obj['value']
lines.append(f'WALL {pipe} {value}')
objs = read_all(name, f"select * from reactions_tank")
objs = read_all(name, "select tank_id as tank, coefficient as value from network.tank_reaction_coefficients order by tank_id")
for obj in objs:
tank = obj['tank']
value = obj['value']
@@ -249,15 +230,14 @@ def inp_out_reaction(name: str) -> list[str]:
def delete_pipe_reaction_by_pipe(name: str, pipe: str) -> ChangeSet:
row1 = try_read(name, f"select * from reactions_pipe_bulk where pipe = '{pipe}'")
row2 = try_read(name, f"select * from reactions_pipe_wall where pipe = '{pipe}'")
if row1 == None and row2 == None:
row = try_read(name, "select 1 from network.pipe_reaction_coefficients where pipe_id = %s", (pipe,))
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'pipe_reaction', 'pipe': pipe, 'bulk': None, 'wall': None})
def delete_tank_reaction_by_tank(name: str, tank: str) -> ChangeSet:
row = try_read(name, f"select * from reactions_tank where tank = '{tank}'")
row = try_read(name, "select 1 from network.tank_reaction_coefficients where tank_id = %s", (tank,))
if row == None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'tank_reaction', 'tank': tank, 'value': None})
@@ -1,4 +1,4 @@
from .database import *
from ..core.database import read_all
#--------------------------------------------------------------
@@ -26,7 +26,7 @@ def inp_in_report(section: list[str]) -> str:
def inp_out_report(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from report")
objs = read_all(name, "select key, value from network.report_settings order by key")
for obj in objs:
key = obj['key']
value = obj['value']
@@ -1,6 +1,23 @@
from .database import *
from .s0_base import *
from .s24_coordinates import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
from ..gis.coordinates import (
get_node_coord,
sql_delete_coord,
sql_insert_coord,
sql_update_coord,
)
from .elements import get_all_node_links, get_node_links
def get_reservoir_schema(name: str) -> dict[str, dict[str, Any]]:
@@ -13,7 +30,7 @@ def get_reservoir_schema(name: str) -> dict[str, dict[str, Any]]:
def get_reservoir(name: str, id: str) -> dict[str, Any]:
r = try_read(name, f"select * from reservoirs where id = '{id}'")
r = try_read(name, "select node_id as id, head, pattern_id as pattern from network.reservoirs where node_id = %s", (id,))
if r == None:
return {}
xy = get_node_coord(name, id)
@@ -28,21 +45,25 @@ def get_reservoir(name: str, id: str) -> dict[str, Any]:
# DingZQ, 2025-03-29
def get_all_reservoirs(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from reservoirs")
rows = read_all(
name,
"SELECT id, head, pattern_id AS pattern, x, y "
"FROM gis.reservoirs ORDER BY id",
)
if rows == None:
return []
links_by_node = get_all_node_links(name)
result = []
for row in rows:
d = {}
id = str(row['id'])
xy = get_node_coord(name, id)
d['id'] = id
d['x'] = float(xy['x'])
d['y'] = float(xy['y'])
d['x'] = float(row['x'] or 0.0)
d['y'] = float(row['y'] or 0.0)
d['head'] = float(row['head']) if row['head'] != None else None
d['pattern'] = str(row['pattern']) if row['pattern'] != None else None
d['links'] = get_node_links(name, id)
d['links'] = links_by_node.get(id, [])
result.append(d)
return result
@@ -56,20 +77,15 @@ class Reservoir(object):
self.head = float(input['head'])
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_head = self.head
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_head = sql_literal(self.head)
self.f_pattern = sql_literal(self.pattern)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'head': self.head, 'pattern': self.pattern }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
old = Reservoir(get_reservoir(name, cs.operations[0]['id']))
def _set_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_reservoir(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -79,16 +95,12 @@ def _set_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Reservoir(raw_new)
redo_sql = f"update reservoirs set head = {new.f_head}, pattern = {new.f_pattern} where id = {new.f_id};"
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
statement = f"update network.reservoirs set head = {new.f_head}, pattern_id = {new.f_pattern} where node_id = {new.f_id};"
statement += f"\n{sql_update_coord(new.id, new.x, new.y)}"
undo_sql = sql_update_coord(old.id, old.x, old.y)
undo_sql += f"\nupdate reservoirs set head = {old.f_head}, pattern = {old.f_pattern} where id = {old.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
@@ -99,21 +111,16 @@ def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_reservoir(name, cs))
def _add_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Reservoir(cs.operations[0])
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({new.f_id}, {new.f_head}, {new.f_pattern});"
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
statement = f"insert into network.nodes (id, node_type) values ({new.f_id}, {new.f_type});"
statement += f"\ninsert into network.reservoirs (node_id, head, pattern_id) values ({new.f_id}, {new.f_head}, {new.f_pattern});"
statement += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
undo_sql = sql_delete_coord(new.id)
undo_sql += f"\ndelete from reservoirs where id = {new.f_id};"
undo_sql += f"\ndelete from _node where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
@@ -124,21 +131,16 @@ def add_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_reservoir(name, cs))
def _delete_reservoir(name: str, cs: ChangeSet) -> DbChangeSet:
old = Reservoir(get_reservoir(name, cs.operations[0]['id']))
def _delete_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = sql_delete_coord(old.id)
redo_sql += f"\ndelete from reservoirs where id = {old.f_id};"
redo_sql += f"\ndelete from _node where id = {old.f_id};"
statement = sql_delete_coord(element_id)
statement += f"\ndelete from network.nodes where id = {f_id};"
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({old.f_id}, {old.f_head}, {old.f_pattern});"
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
change = g_delete_prefix | {'type': 'reservoir', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
@@ -165,15 +167,14 @@ def inp_in_reservoir(line: str) -> str:
id = str(tokens[0])
head = float(tokens[1])
pattern = str(tokens[2]) if num_without_desc >= 3 else None
pattern = f"'{pattern}'" if pattern != None else 'null'
desc = str(tokens[-1]) if has_desc else None
return str(f"insert into _node (id, type) values ('{id}', 'reservoir');insert into reservoirs (id, head, pattern) values ('{id}', {head}, {pattern});")
return str(f"insert into network.nodes (id, node_type) values ({sql_literal(id)}, 'reservoir');insert into network.reservoirs (node_id, head, pattern_id) values ({sql_literal(id)}, {sql_literal(head)}, {sql_literal(pattern)});")
def inp_out_reservoir(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from reservoirs')
objs = read_all(name, 'select node_id as id, head, pattern_id as pattern from network.reservoirs order by node_id')
for obj in objs:
id = obj['id']
head = obj['head']
@@ -186,7 +187,7 @@ def inp_out_reservoir(name: str) -> list[str]:
def unset_reservoir_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from reservoirs where pattern = '{pattern}'")
rows = read_all(name, "select node_id as id from network.reservoirs where pattern_id = %s", (pattern,))
for row in rows:
cs.append(g_update_prefix | {'type': 'reservoir', 'id': row['id'], 'pattern': None})
+50
View File
@@ -0,0 +1,50 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
)
def get_rule_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'rules' : {'type': 'str_list' , 'optional': False , 'readonly': False} }
def get_rule(name: str) -> dict[str, Any]:
cs = read_all(name, "select line from network.rules order by sequence_no")
ds = []
for c in cs:
ds.append(c['line'])
return { 'rules': ds }
def _set_rule(name: str, cs: ChangeSet) -> DatabaseCommand:
statement = 'delete from network.rules;'
for sequence_no, line in enumerate(cs.operations[0]['rules']):
statement += f"\ninsert into network.rules (sequence_no, line) values ({sequence_no}, {sql_literal(line)});"
change = g_update_prefix | { 'type': 'rule', 'rules': cs.operations[0]['rules'] }
return DatabaseCommand(statement, [change])
def set_rule(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_rule(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3]
# TODO...
#--------------------------------------------------------------
def inp_in_rule(line: str) -> str:
return str(f"insert into network.rules (sequence_no, line) values ((select coalesce(max(sequence_no) + 1, 0) from network.rules), {sql_literal(line)});")
def inp_out_rule(name: str) -> list[str]:
return get_rule(name)['rules']
@@ -1,5 +1,16 @@
from .database import *
from .s0_base import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
SOURCE_TYPE_CONCEN = 'CONCEN'
SOURCE_TYPE_MASS = 'MASS'
@@ -14,7 +25,7 @@ def get_source_schema(name: str) -> dict[str, dict[str, Any]]:
def get_source(name: str, node: str) -> dict[str, Any]:
s = try_read(name, f"select * from sources where node = '{node}'")
s = try_read(name, "select node_id as node, source_type as s_type, strength, pattern_id as pattern from network.sources where node_id = %s", (node,))
if s == None:
return {}
d = {}
@@ -33,21 +44,16 @@ class Source(object):
self.strength = float(input['strength'])
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
self.f_type = f"'{self.type}'"
self.f_node = f"'{self.node}'"
self.f_s_type = f"'{self.s_type}'"
self.f_strength = self.strength
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
self.f_type = sql_literal(self.type)
self.f_node = sql_literal(self.node)
self.f_s_type = sql_literal(self.s_type)
self.f_strength = sql_literal(self.strength)
self.f_pattern = sql_literal(self.pattern)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'node': self.node, 's_type': self.s_type, 'strength': self.strength, 'pattern': self.pattern }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'node': self.node }
def _set_source(name: str, cs: ChangeSet) -> DbChangeSet:
old = Source(get_source(name, cs.operations[0]['node']))
def _set_source(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_source(name, cs.operations[0]['node'])
new_dict = cs.operations[0]
@@ -57,45 +63,40 @@ def _set_source(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Source(raw_new)
redo_sql = f"update sources set s_type = {new.f_s_type}, strength = {new.f_strength}, pattern = {new.f_pattern} where node = {new.f_node};"
undo_sql = f"update sources set s_type = {old.f_s_type}, strength = {old.f_strength}, pattern = {old.f_pattern} where node = {old.f_node};"
statement = f"update network.sources set source_type = {new.f_s_type}, strength = {new.f_strength}, pattern_id = {new.f_pattern} where node_id = {new.f_node};"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
change = g_update_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_source(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_source(name, cs))
def _add_source(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_source(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Source(cs.operations[0])
redo_sql = f"insert into sources (node, s_type, strength, pattern) values ({new.f_node}, {new.f_s_type}, {new.f_strength}, {new.f_pattern});"
undo_sql = f"delete from sources where node = {new.f_node};"
statement = f"insert into network.sources (node_id, source_type, strength, pattern_id) values ({new.f_node}, {new.f_s_type}, {new.f_strength}, {new.f_pattern});"
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
change = g_add_prefix | new.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_source(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_source(name, cs))
def _delete_source(name: str, cs: ChangeSet) -> DbChangeSet:
old = Source(get_source(name, cs.operations[0]['node']))
def _delete_source(name: str, cs: ChangeSet) -> DatabaseCommand:
node = str(cs.operations[0]['node'])
f_node = sql_literal(node)
redo_sql = f"delete from sources where node = {old.f_node};"
undo_sql = f"insert into sources (node, s_type, strength, pattern) values ({old.f_node}, {old.f_s_type}, {old.f_strength}, {old.f_pattern});"
statement = f"delete from network.sources where node_id = {f_node};"
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
change = g_delete_prefix | {'type': 'source', 'node': node}
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_source(name: str, cs: ChangeSet) -> ChangeSet:
@@ -119,14 +120,12 @@ def inp_in_source(line: str) -> str:
s_type = str(tokens[1].upper())
strength = float(tokens[2])
pattern = str(tokens[3]) if num_without_desc >= 4 else None
pattern = f"'{pattern}'" if pattern != None else 'null'
return str(f"insert into sources (node, s_type, strength, pattern) values ('{node}', '{s_type}', {strength}, {pattern});")
return str(f"insert into network.sources (node_id, source_type, strength, pattern_id) values ({sql_literal(node)}, {sql_literal(s_type)}, {sql_literal(strength)}, {sql_literal(pattern)});")
def inp_out_source(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from sources')
objs = read_all(name, 'select node_id as node, source_type as s_type, strength, pattern_id as pattern from network.sources order by node_id')
for obj in objs:
node = obj['node']
s_type = obj['s_type']
@@ -137,7 +136,7 @@ def inp_out_source(name: str) -> list[str]:
def delete_source_by_node(name: str, node: str) -> ChangeSet:
row = try_read(name, f"select * from sources where node = '{node}'")
row = try_read(name, "select 1 from network.sources where node_id = %s", (node,))
if row == None:
return ChangeSet()
return ChangeSet(g_delete_prefix | {'type' : 'source', 'node': node})
@@ -146,7 +145,7 @@ def delete_source_by_node(name: str, node: str) -> ChangeSet:
def unset_source_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select node from sources where pattern = '{pattern}'")
rows = read_all(name, "select node_id as node from network.sources where pattern_id = %s", (pattern,))
for row in rows:
cs.append(g_update_prefix | {'type': 'source', 'node': row['node'], 'pattern': None})
@@ -1,4 +1,14 @@
from .database import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
LINK_STATUS_OPEN = 'OPEN'
@@ -13,7 +23,7 @@ def get_status_schema(name: str) -> dict[str, dict[str, Any]]:
def get_status(name: str, link: str) -> dict[str, Any]:
s = try_read(name, f"select * from status where link = '{link}'")
s = try_read(name, "select link_id as link, status, setting from network.link_initial_settings where link_id = %s", (link,))
if s == None:
return { 'link': link, 'status': None, 'setting': None }
d = {}
@@ -30,17 +40,16 @@ class Status(object):
self.status = str(input['status']) if 'status' in input and input['status'] != None else None
self.setting = float(input['setting']) if 'setting' in input and input['setting'] != None else None
self.f_type = f"'{self.type}'"
self.f_link = f"'{self.link}'"
self.f_status = f"'{self.status}'" if self.status != None else 'null'
self.f_setting = self.setting if self.setting != None else 'null'
self.f_type = sql_literal(self.type)
self.f_link = sql_literal(self.link)
self.f_status = sql_literal(self.status)
self.f_setting = sql_literal(self.setting)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'link': self.link, 'status': self.status, 'setting': self.setting }
def _set_status(name: str, cs: ChangeSet) -> DbChangeSet:
old = Status(get_status(name, cs.operations[0]['link']))
def _set_status(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_status(name, cs.operations[0]['link'])
new_dict = cs.operations[0]
@@ -50,18 +59,13 @@ def _set_status(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Status(raw_new)
redo_sql = f"delete from status where link = {new.f_link};"
statement = f"delete from network.link_initial_settings where link_id = {new.f_link};"
if new.status != None or new.setting != None:
redo_sql += f"\ninsert into status (link, status, setting) values ({new.f_link}, {new.f_status}, {new.f_setting});"
statement += f"\ninsert into network.link_initial_settings (link_id, status, setting) values ({new.f_link}, {new.f_status}, {new.f_setting});"
undo_sql = f"delete from status where link = {old.f_link};"
if old.status != None or old.setting != None:
undo_sql += f"\ninsert into status (link, status, setting) values ({old.f_link}, {old.f_status}, {old.f_setting});"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_status(name: str, cs: ChangeSet) -> ChangeSet:
@@ -84,14 +88,14 @@ def inp_in_status(line: str) -> str:
link = str(tokens[0])
value = tokens[1].upper()
if value == LINK_STATUS_OPEN or value == LINK_STATUS_CLOSED or value == LINK_STATUS_ACTIVE:
return str(f"insert into status (link, status, setting) values ('{link}', '{value}', null);")
return str(f"insert into network.link_initial_settings (link_id, status, setting) values ({sql_literal(link)}, {sql_literal(value)}, null);")
else:
return str(f"insert into status (link, status, setting) values ('{link}', null, {float(value)});")
return str(f"insert into network.link_initial_settings (link_id, status, setting) values ({sql_literal(link)}, null, {sql_literal(float(value))});")
def inp_out_status(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from status')
objs = read_all(name, 'select link_id as link, status, setting from network.link_initial_settings order by link_id')
for obj in objs:
link = obj['link']
status = obj['status'] if obj['status'] != None else ''
@@ -104,7 +108,7 @@ def inp_out_status(name: str) -> list[str]:
def delete_status_by_link(name: str, link: str) -> ChangeSet:
row = try_read(name, f"select * from status where link = '{link}'")
row = try_read(name, "select 1 from network.link_initial_settings where link_id = %s", (link,))
if row == None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'status', 'link': link, 'status': None, 'setting': None})
+123
View File
@@ -0,0 +1,123 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
TAG_TYPE_NODE = "NODE"
TAG_TYPE_LINK = "LINK"
def get_tag_schema(name: str) -> dict[str, dict[str, Any]]:
return {
"t_type": {"type": "str", "optional": False, "readonly": False},
"id": {"type": "str", "optional": False, "readonly": False},
"tag": {"type": "str", "optional": True, "readonly": False},
}
def get_tags(name: str) -> list[dict[str, Any]]:
rows = read_all(
name,
"""
select 'NODE' as t_type, node_id as id, tag from network.node_tags
union all
select 'LINK' as t_type, link_id as id, tag from network.link_tags
order by t_type, id
""",
)
return [
{
"t_type": str(row["t_type"]),
"id": str(row["id"]),
"tag": str(row["tag"]) if row["tag"] is not None else None,
}
for row in rows
]
def _tag_table(t_type: str) -> tuple[str, str]:
if t_type == TAG_TYPE_NODE:
return "network.node_tags", "node_id"
if t_type == TAG_TYPE_LINK:
return "network.link_tags", "link_id"
raise ValueError("Only NODE and LINK tags are supported")
def get_tag(name: str, t_type: str, id: str) -> dict[str, Any]:
table, id_column = _tag_table(t_type)
row = try_read(
name,
f"select {id_column} as id, tag from {table} where {id_column} = %s",
(id,),
)
return {
"t_type": t_type,
"id": id,
"tag": str(row["tag"]) if row and row["tag"] is not None else None,
}
def _replace_tag_sql(t_type: str, element_id: str, tag: str | None) -> str:
table, id_column = _tag_table(t_type)
element_sql = sql_literal(element_id)
statements = [f"delete from {table} where {id_column} = {element_sql};"]
if tag is not None:
statements.append(
f"insert into {table} ({id_column}, tag) "
f"values ({element_sql}, {sql_literal(tag)});"
)
return "\n".join(statements)
def _set_tag(name: str, cs: ChangeSet) -> DatabaseCommand:
operation = cs.operations[0]
t_type = str(operation["t_type"]).upper()
element_id = str(operation["id"])
new = {"t_type": t_type, "id": element_id, "tag": operation.get("tag")}
return DatabaseCommand(
_replace_tag_sql(t_type, element_id, new["tag"]),
[g_update_prefix | {"type": "tag"} | new],
)
def set_tag(name: str, cs: ChangeSet) -> ChangeSet:
if not {"t_type", "id", "tag"} <= cs.operations[0].keys():
return ChangeSet()
return execute_command(name, _set_tag(name, cs))
def inp_in_tag(line: str) -> str:
tokens = line.split()
if len(tokens) < 3:
return ""
return _replace_tag_sql(tokens[0].upper(), tokens[1], tokens[2])
def inp_out_tag(name: str) -> list[str]:
return [f"{row['t_type']} {row['id']} {row['tag']}" for row in get_tags(name)]
def delete_tag_by_node(name: str, node: str) -> ChangeSet:
row = get_tag(name, TAG_TYPE_NODE, node)
return (
ChangeSet(g_update_prefix | {"type": "tag"} | row | {"tag": None})
if row["tag"] is not None
else ChangeSet()
)
def delete_tag_by_link(name: str, link: str) -> ChangeSet:
row = get_tag(name, TAG_TYPE_LINK, link)
return (
ChangeSet(g_update_prefix | {"type": "tag"} | row | {"tag": None})
if row["tag"] is not None
else ChangeSet()
)
@@ -1,6 +1,23 @@
from .database import *
from .s0_base import *
from .s24_coordinates import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
from ..gis.coordinates import (
get_node_coord,
sql_delete_coord,
sql_insert_coord,
sql_update_coord,
)
from .elements import get_all_node_links, get_node_links
OVERFLOW_YES = 'YES'
@@ -23,7 +40,7 @@ def get_tank_schema(name: str) -> dict[str, dict[str, Any]]:
def get_tank(name: str, id: str) -> dict[str, Any]:
t = try_read(name, f"select * from tanks where id = '{id}'")
t = try_read(name, "select node_id as id, elevation, initial_level as init_level, minimum_level as min_level, maximum_level as max_level, diameter, minimum_volume as min_vol, volume_curve_id as vol_curve, overflow from network.tanks where node_id = %s", (id,))
if t == None:
return {}
xy = get_node_coord(name, id)
@@ -44,18 +61,24 @@ def get_tank(name: str, id: str) -> dict[str, Any]:
# DingZQ, 2025-03-29
def get_all_tanks(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from tanks")
rows = read_all(
name,
"SELECT id, elevation, initial_level AS init_level, "
"minimum_level AS min_level, maximum_level AS max_level, diameter, "
"minimum_volume AS min_vol, volume_curve_id AS vol_curve, overflow, "
"x, y FROM gis.tanks ORDER BY id",
)
if rows == None:
return []
links_by_node = get_all_node_links(name)
result = []
for row in rows:
d = {}
id = str(row['id'])
xy = get_node_coord(name, id)
d['id'] = id
d['x'] = float(xy['x'])
d['y'] = float(xy['y'])
d['x'] = float(row['x'] or 0.0)
d['y'] = float(row['y'] or 0.0)
d['elevation'] = float(row['elevation'])
d['init_level'] = float(row['init_level'])
d['min_level'] = float(row['min_level'])
@@ -64,7 +87,7 @@ def get_all_tanks(name: str) -> list[dict[str, Any]]:
d['min_vol'] = float(row['min_vol'])
d['vol_curve'] = str(row['vol_curve']) if row['vol_curve'] != None else None
d['overflow'] = str(row['overflow']) if row['overflow'] != None else None
d['links'] = get_node_links(name, id)
d['links'] = links_by_node.get(id, [])
result.append(d)
return result
@@ -84,26 +107,21 @@ class Tank(object):
self.vol_curve = str(input['vol_curve']) if 'vol_curve' in input and input['vol_curve'] != None else None
self.overflow = str(input['overflow']) if 'overflow' in input and input['overflow'] != None else None
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_elevation = self.elevation
self.f_init_level = self.init_level
self.f_min_level = self.min_level
self.f_max_level = self.max_level
self.f_diameter = self.diameter
self.f_min_vol = self.min_vol
self.f_vol_curve = f"'{self.vol_curve}'" if self.vol_curve != None else 'null'
self.f_overflow = f"'{self.overflow}'" if self.overflow != None else 'null'
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_elevation = sql_literal(self.elevation)
self.f_init_level = sql_literal(self.init_level)
self.f_min_level = sql_literal(self.min_level)
self.f_max_level = sql_literal(self.max_level)
self.f_diameter = sql_literal(self.diameter)
self.f_min_vol = sql_literal(self.min_vol)
self.f_vol_curve = sql_literal(self.vol_curve)
self.f_overflow = sql_literal(self.overflow)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation, 'init_level': self.init_level, 'min_level': self.min_level, 'max_level': self.max_level, 'diameter': self.diameter, 'min_vol': self.min_vol, 'vol_curve': self.vol_curve, 'overflow': self.overflow }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_tank(name: str, cs: ChangeSet) -> DbChangeSet:
old = Tank(get_tank(name, cs.operations[0]['id']))
def _set_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_tank(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -113,16 +131,12 @@ def _set_tank(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Tank(raw_new)
redo_sql = f"update tanks set elevation = {new.f_elevation}, init_level = {new.f_init_level}, min_level = {new.f_min_level}, max_level = {new.f_max_level}, diameter = {new.f_diameter}, min_vol = {new.f_min_vol}, vol_curve = {new.f_vol_curve}, overflow = {new.f_overflow} where id = {new.f_id};"
redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}"
statement = f"update network.tanks set elevation = {new.f_elevation}, initial_level = {new.f_init_level}, minimum_level = {new.f_min_level}, maximum_level = {new.f_max_level}, diameter = {new.f_diameter}, minimum_volume = {new.f_min_vol}, volume_curve_id = {new.f_vol_curve}, overflow = {new.f_overflow} where node_id = {new.f_id};"
statement += f"\n{sql_update_coord(new.id, new.x, new.y)}"
undo_sql = sql_update_coord(old.id, old.x, old.y)
undo_sql += f"\nupdate tanks set elevation = {old.f_elevation}, init_level = {old.f_init_level}, min_level = {old.f_min_level}, max_level = {old.f_max_level}, diameter = {old.f_diameter}, min_vol = {old.f_min_vol}, vol_curve = {old.f_vol_curve}, overflow = {old.f_overflow} where id = {old.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_tank(name: str, cs: ChangeSet) -> ChangeSet:
@@ -133,21 +147,16 @@ def set_tank(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_tank(name, cs))
def _add_tank(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Tank(cs.operations[0])
redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({new.f_id}, {new.f_elevation}, {new.f_init_level}, {new.f_min_level}, {new.f_max_level}, {new.f_diameter}, {new.f_min_vol}, {new.f_vol_curve}, {new.f_overflow});"
redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
statement = f"insert into network.nodes (id, node_type) values ({new.f_id}, {new.f_type});"
statement += f"\ninsert into network.tanks (node_id, elevation, initial_level, minimum_level, maximum_level, diameter, minimum_volume, volume_curve_id, overflow) values ({new.f_id}, {new.f_elevation}, {new.f_init_level}, {new.f_min_level}, {new.f_max_level}, {new.f_diameter}, {new.f_min_vol}, {new.f_vol_curve}, {new.f_overflow});"
statement += f"\n{sql_insert_coord(new.id, new.x, new.y)}"
undo_sql = sql_delete_coord(new.id)
undo_sql += f"\ndelete from tanks where id = {new.f_id};"
undo_sql += f"\ndelete from _node where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_tank(name: str, cs: ChangeSet) -> ChangeSet:
@@ -158,21 +167,16 @@ def add_tank(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_tank(name, cs))
def _delete_tank(name: str, cs: ChangeSet) -> DbChangeSet:
old = Tank(get_tank(name, cs.operations[0]['id']))
def _delete_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = sql_delete_coord(old.id)
redo_sql += f"\ndelete from tanks where id = {old.f_id};"
redo_sql += f"\ndelete from _node where id = {old.f_id};"
statement = sql_delete_coord(element_id)
statement += f"\ndelete from network.nodes where id = {f_id};"
undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({old.f_id}, {old.f_elevation}, {old.f_init_level}, {old.f_min_level}, {old.f_max_level}, {old.f_diameter}, {old.f_min_vol}, {old.f_vol_curve}, {old.f_overflow});"
undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}"
change = g_delete_prefix | {'type': 'tank', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_tank(name: str, cs: ChangeSet) -> ChangeSet:
@@ -212,17 +216,15 @@ def inp_in_tank(line: str) -> str:
diameter = float(tokens[5])
min_vol = float(tokens[6]) if num_without_desc >= 7 else 0.0
vol_curve = str(tokens[7]) if num_without_desc >= 8 and tokens[7] != '*' else None
vol_curve = f"'{vol_curve}'" if vol_curve != None else 'null'
overflow = str(tokens[8].upper()) if num_without_desc >= 9 else None
overflow = f"'{overflow}'" if overflow != None else 'null'
desc = str(tokens[-1]) if has_desc else None
return str(f"insert into _node (id, type) values ('{id}', 'tank');insert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ('{id}', {elevation}, {init_level}, {min_level}, {max_level}, {diameter}, {min_vol}, {vol_curve}, {overflow});")
return str(f"insert into network.nodes (id, node_type) values ({sql_literal(id)}, 'tank');insert into network.tanks (node_id, elevation, initial_level, minimum_level, maximum_level, diameter, minimum_volume, volume_curve_id, overflow) values ({sql_literal(id)}, {sql_literal(elevation)}, {sql_literal(init_level)}, {sql_literal(min_level)}, {sql_literal(max_level)}, {sql_literal(diameter)}, {sql_literal(min_vol)}, {sql_literal(vol_curve)}, {sql_literal(overflow)});")
def inp_out_tank(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from tanks')
objs = read_all(name, 'select node_id as id, elevation, initial_level as init_level, minimum_level as min_level, maximum_level as max_level, diameter, minimum_volume as min_vol, volume_curve_id as vol_curve, overflow from network.tanks order by node_id')
for obj in objs:
id = obj['id']
elevation = obj['elevation']
@@ -243,7 +245,7 @@ def inp_out_tank(name: str) -> list[str]:
def unset_tank_by_curve(name: str, curve: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from tanks where vol_curve = '{curve}'")
rows = read_all(name, "select node_id as id from network.tanks where volume_curve_id = %s", (curve,))
for row in rows:
cs.append(g_update_prefix | {'type': 'tank', 'id': row['id'], 'vol_curve': None})
@@ -1,112 +1,110 @@
from .database import *
TIME_STATISTIC_NONE = 'NONE'
TIME_STATISTIC_AVERAGED = 'AVERAGED'
TIME_STATISTIC_MINIMUM = 'MINIMUM'
TIME_STATISTIC_MAXIMUM = 'MAXIMUM'
TIME_STATISTIC_RANGE = 'RANGE'
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
def get_time_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'DURATION' : element_schema,
'HYDRAULIC TIMESTEP' : element_schema,
'QUALITY TIMESTEP' : element_schema,
'RULE TIMESTEP' : element_schema,
'PATTERN TIMESTEP' : element_schema,
'PATTERN START' : element_schema,
'REPORT TIMESTEP' : element_schema,
'REPORT START' : element_schema,
'START CLOCKTIME' : element_schema,
'STATISTIC' : element_schema}
def get_time(name: str) -> dict[str, Any]:
ts = read_all(name, f"select * from times")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_time(name: str, cs: ChangeSet) -> DbChangeSet:
raw_old = get_time(name)
old = {}
new = {}
new_dict = cs.operations[0]
schema = get_time_schema(name)
for key in schema.keys():
if key in new_dict:
old[key] = str(raw_old[key])
new[key] = str(new_dict[key])
redo_cs = g_update_prefix | { 'type' : 'time' }
redo_sql = ''
for key, value in new.items():
if redo_sql != '':
redo_sql += '\n'
redo_sql += f"update times set value = '{value}' where key = '{key}';"
redo_cs |= { key: value }
undo_cs = g_update_prefix | { 'type' : 'time' }
undo_sql = ''
for key, value in old.items():
if undo_sql != '':
undo_sql += '\n'
undo_sql += f"update times set value = '{value}' where key = '{key}';"
undo_cs |= { key: value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_time(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_time(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3]
# STATISTIC {NONE/AVERAGE/MIN/MAX/RANGE}
# DURATION value (units)
# HYDRAULIC TIMESTEP value (units)
# QUALITY TIMESTEP value (units)
# RULE TIMESTEP value (units)
# PATTERN TIMESTEP value (units)
# PATTERN START value (units)
# REPORT TIMESTEP value (units)
# REPORT START value (units)
# START CLOCKTIME value (AM PM)
# [EPA3] supports [EPA2] keyword
#--------------------------------------------------------------
def inp_in_time(section: list[str]) -> str:
sql = ''
for s in section:
if s.startswith(';'):
continue
line = s.upper().strip()
# TOTAL DURATION => DURATION
if line.startswith('TOTAL DURATION'):
line = line.replace('TOTAL DURATION', 'DURATION')
for key in get_time_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
sql += f"update times set value = '{value}' where key = '{key}';"
return sql
def inp_out_time(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from times")
for obj in objs:
key = obj['key']
value = obj['value']
lines.append(f'{key} {value}')
return lines
from typing import Any
from psycopg import sql
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
)
TIME_STATISTIC_NONE = 'NONE'
TIME_STATISTIC_AVERAGED = 'AVERAGED'
TIME_STATISTIC_MINIMUM = 'MINIMUM'
TIME_STATISTIC_MAXIMUM = 'MAXIMUM'
TIME_STATISTIC_RANGE = 'RANGE'
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
def get_time_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'DURATION' : element_schema,
'HYDRAULIC TIMESTEP' : element_schema,
'QUALITY TIMESTEP' : element_schema,
'RULE TIMESTEP' : element_schema,
'PATTERN TIMESTEP' : element_schema,
'PATTERN START' : element_schema,
'REPORT TIMESTEP' : element_schema,
'REPORT START' : element_schema,
'START CLOCKTIME' : element_schema,
'STATISTIC' : element_schema}
def get_time(name: str) -> dict[str, Any]:
ts = read_all(name, "select key, value from network.time_settings")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_time(name: str, cs: ChangeSet) -> DatabaseCommand:
new = {}
new_dict = cs.operations[0]
schema = get_time_schema(name)
for key in schema.keys():
if key in new_dict:
new[key] = str(new_dict[key])
change = g_update_prefix | { 'type' : 'time' }
statement = ''
for key, value in new.items():
if statement != '':
statement += '\n'
statement += f"update network.time_settings set value = {sql_literal(value)} where key = {sql_literal(key)};"
change |= { key: value }
return DatabaseCommand(statement, [change])
def set_time(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_time(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3]
# STATISTIC {NONE/AVERAGE/MIN/MAX/RANGE}
# DURATION value (units)
# HYDRAULIC TIMESTEP value (units)
# QUALITY TIMESTEP value (units)
# RULE TIMESTEP value (units)
# PATTERN TIMESTEP value (units)
# PATTERN START value (units)
# REPORT TIMESTEP value (units)
# REPORT START value (units)
# START CLOCKTIME value (AM PM)
# [EPA3] supports [EPA2] keyword
#--------------------------------------------------------------
def inp_in_time(section: list[str]) -> str:
sql = ''
for s in section:
if s.startswith(';'):
continue
line = s.upper().strip()
# TOTAL DURATION => DURATION
if line.startswith('TOTAL DURATION'):
line = line.replace('TOTAL DURATION', 'DURATION')
for key in get_time_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
sql += f"update network.time_settings set value = {sql_literal(value)} where key = {sql_literal(key)};"
return sql
def inp_out_time(name: str) -> list[str]:
lines = []
objs = read_all(name, "select key, value from network.time_settings order by key")
for obj in objs:
key = obj['key']
value = obj['value']
lines.append(f'{key} {value}')
return lines
+52
View File
@@ -0,0 +1,52 @@
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_update_prefix,
read_all,
sql_literal,
)
def get_title_schema(name: str) -> dict[str, dict[str, Any]]:
return {"value": {"type": "str", "optional": False, "readonly": False}}
def get_title(name: str) -> dict[str, Any]:
rows = read_all(
name,
"select value from network.model_titles order by sequence_no",
)
return {"value": "\n".join(str(row["value"]) for row in rows)}
def _replace_title_sql(value: str) -> str:
statements = ["delete from network.model_titles;"]
for sequence_no, line in enumerate(value.split("\n")):
statements.append(
"insert into network.model_titles (sequence_no, value) "
f"values ({sequence_no}, {sql_literal(line)});"
)
return "\n".join(statements)
def _set_title(name: str, cs: ChangeSet) -> DatabaseCommand:
new = str(cs.operations[0]["value"])
return DatabaseCommand(
_replace_title_sql(new),
[g_update_prefix | {"type": "title", "value": new}],
)
def set_title(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_title(name, cs))
def inp_in_title(section: list[str]) -> str:
return _replace_title_sql("\n".join(section))
def inp_out_title(name: str) -> list[str]:
return str(get_title(name)["value"]).split("\n")
@@ -1,5 +1,16 @@
from .database import *
from .s0_base import *
from typing import Any
from ..core.database import (
ChangeSet,
DatabaseCommand,
execute_command,
g_add_prefix,
g_delete_prefix,
g_update_prefix,
read_all,
sql_literal,
try_read,
)
VALVES_TYPE_PRV = 'PRV'
@@ -21,7 +32,7 @@ def get_valve_schema(name: str) -> dict[str, dict[str, Any]]:
def get_valve(name: str, id: str) -> dict[str, Any]:
p = try_read(name, f"select * from valves where id = '{id}'")
p = try_read(name, "select l.id, l.start_node_id as node1, l.end_node_id as node2, v.diameter, v.valve_type as v_type, v.setting, v.minor_loss from network.links l join network.valves v on v.link_id = l.id where l.id = %s", (id,))
if p == None:
return {}
d = {}
@@ -35,7 +46,11 @@ def get_valve(name: str, id: str) -> dict[str, Any]:
return d
def get_all_valves(name: str) -> list[dict[str, Any]]:
rows = read_all(name, f"select * from valves")
rows = read_all(
name,
"SELECT id, start_node_id AS node1, end_node_id AS node2, diameter, "
"valve_type AS v_type, setting, minor_loss FROM gis.valves ORDER BY id",
)
if rows == None:
return []
@@ -67,24 +82,19 @@ class Valve(object):
self.setting = str(input['setting'])
self.minor_loss = float(input['minor_loss'])
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_node1 = f"'{self.node1}'"
self.f_node2 = f"'{self.node2}'"
self.f_diameter = self.diameter
self.f_v_type = f"'{self.v_type}'"
self.f_setting = f"'{self.setting}'"
self.f_minor_loss = self.minor_loss
self.f_type = sql_literal(self.type)
self.f_id = sql_literal(self.id)
self.f_node1 = sql_literal(self.node1)
self.f_node2 = sql_literal(self.node2)
self.f_diameter = sql_literal(self.diameter)
self.f_v_type = sql_literal(self.v_type)
self.f_setting = sql_literal(self.setting)
self.f_minor_loss = sql_literal(self.minor_loss)
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'node1': self.node1, 'node2': self.node2, 'diameter': self.diameter, 'v_type': self.v_type, 'setting': self.setting, 'minor_loss': self.minor_loss }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_valve(name: str, cs: ChangeSet) -> DbChangeSet:
old = Valve(get_valve(name, cs.operations[0]['id']))
def _set_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
raw_new = get_valve(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
@@ -94,13 +104,11 @@ def _set_valve(name: str, cs: ChangeSet) -> DbChangeSet:
raw_new[key] = new_dict[key]
new = Valve(raw_new)
redo_sql = f"update valves set node1 = {new.f_node1}, node2 = {new.f_node2}, diameter = {new.f_diameter}, v_type = {new.f_v_type}, setting = {new.f_setting}, minor_loss = {new.f_minor_loss} where id = {new.f_id};"
undo_sql = f"update valves set node1 = {old.f_node1}, node2 = {old.f_node2}, diameter = {old.f_diameter}, v_type = {old.f_v_type}, setting = {old.f_setting}, minor_loss = {old.f_minor_loss} where id = {old.f_id};"
statement = f"update network.links set start_node_id = {new.f_node1}, end_node_id = {new.f_node2} where id = {new.f_id};"
statement += f"\nupdate network.valves set diameter = {new.f_diameter}, valve_type = {new.f_v_type}, setting = {new.f_setting}, minor_loss = {new.f_minor_loss} where link_id = {new.f_id};"
change = g_update_prefix | new.as_dict()
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def set_valve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -111,19 +119,15 @@ def set_valve(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_valve(name, cs))
def _add_valve(name: str, cs: ChangeSet) -> DbChangeSet:
def _add_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
new = Valve(cs.operations[0])
redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});"
redo_sql += f"\ninsert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ({new.f_id}, {new.f_node1}, {new.f_node2}, {new.f_diameter}, {new.f_v_type}, {new.f_setting}, {new.f_minor_loss});"
statement = f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({new.f_id}, {new.f_type}, {new.f_node1}, {new.f_node2});"
statement += f"\ninsert into network.valves (link_id, diameter, valve_type, setting, minor_loss) values ({new.f_id}, {new.f_diameter}, {new.f_v_type}, {new.f_setting}, {new.f_minor_loss});"
undo_sql = f"delete from valves where id = {new.f_id};"
undo_sql += f"\ndelete from _link where id = {new.f_id};"
change = g_add_prefix | new.as_dict()
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def add_valve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -134,19 +138,15 @@ def add_valve(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _add_valve(name, cs))
def _delete_valve(name: str, cs: ChangeSet) -> DbChangeSet:
old = Valve(get_valve(name, cs.operations[0]['id']))
def _delete_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
element_id = str(cs.operations[0]['id'])
f_id = sql_literal(element_id)
redo_sql = f"delete from valves where id = {old.f_id};"
redo_sql += f"\ndelete from _link where id = {old.f_id};"
statement = f"delete from network.links where id = {f_id};"
undo_sql = f"insert into _link (id, type) values ({old.f_id}, {old.f_type});"
undo_sql += f"\ninsert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ({old.f_id}, {old.f_node1}, {old.f_node2}, {old.f_diameter}, {old.f_v_type}, {old.f_setting}, {old.f_minor_loss});"
change = g_delete_prefix | {'type': 'valve', 'id': element_id}
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
return DatabaseCommand(statement, [change])
def delete_valve(name: str, cs: ChangeSet) -> ChangeSet:
@@ -181,12 +181,12 @@ def inp_in_valve(line: str) -> str:
minor_loss = float(tokens[6]) if len(tokens) >= 7 else 0.0
desc = str(tokens[-1]) if has_desc else None
return str(f"insert into _link (id, type) values ('{id}', 'valve');insert into valves (id, node1, node2, diameter, v_type, setting, minor_loss) values ('{id}', '{node1}', '{node2}', {diameter}, '{v_type}', '{setting}', {minor_loss});")
return str(f"insert into network.links (id, link_type, start_node_id, end_node_id) values ({sql_literal(id)}, 'valve', {sql_literal(node1)}, {sql_literal(node2)});insert into network.valves (link_id, diameter, valve_type, setting, minor_loss) values ({sql_literal(id)}, {sql_literal(diameter)}, {sql_literal(v_type)}, {sql_literal(setting)}, {sql_literal(minor_loss)});")
def inp_out_valve(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from valves')
objs = read_all(name, 'select l.id, l.start_node_id as node1, l.end_node_id as node2, v.diameter, v.valve_type as v_type, v.setting, v.minor_loss from network.links l join network.valves v on v.link_id = l.id order by l.id')
for obj in objs:
id = obj['id']
node1 = obj['node1']
@@ -198,13 +198,3 @@ def inp_out_valve(name: str) -> list[str]:
desc = ';'
lines.append(f'{id} {node1} {node2} {diameter} {v_type} {setting} {minor_loss} {desc}')
return lines
'''def delete_valve_by_node(name: str, node: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select id from valves where node1 = '{node}' or node2 = '{node}'")
for row in rows:
cs.append(g_delete_prefix | {'type': 'valve', 'id': row['id']})
return cs'''
-192
View File
@@ -1,192 +0,0 @@
import os
import psycopg as pg
from psycopg import sql
from psycopg.rows import dict_row
from .connection import (
close_connection,
is_connection_open,
open_connection,
)
from app.core.config import get_pg_config, get_pg_password
from app.infra.db.project_routing import get_project_pgconn_string
# no undo/redo
_server_databases = ["template0", "template1", "postgres", "project"]
def list_project() -> list[str]:
ps = []
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
with conn.cursor(row_factory=dict_row) as cur:
for p in cur.execute(
f"select datname from pg_database where datname <> 'postgres' and datname <> 'template0' and datname <> 'template1' and datname <> 'project'"
):
ps.append(p["datname"])
return ps
def have_project(name: str) -> bool:
with pg.connect(
conninfo=get_project_pgconn_string(db_name="postgres"), autocommit=True
) as conn:
with conn.cursor() as cur:
cur.execute("select 1 from pg_database where datname = %s", (name,))
return cur.fetchone() is not None
def copy_project(source: str, new: str) -> None:
close_connection(source)
with pg.connect(
conninfo=get_project_pgconn_string(db_name="postgres"), autocommit=True
) as admin_conn:
with admin_conn.cursor() as cur:
cur.execute(
"update pg_database set datallowconn = false where datname = %s",
(source,),
)
try:
cur.execute(
"select pg_terminate_backend(pid) from pg_stat_activity where datname = %s and pid <> pg_backend_pid()",
(source,),
)
cur.execute(
sql.SQL("create database {} with template = {}").format(
sql.Identifier(new), sql.Identifier(source)
)
)
finally:
cur.execute(
"update pg_database set datallowconn = true where datname = %s",
(source,),
)
# 2025-02-07, WMH
# copyproject会把pg中operation这个表的全部内容也加进去,我们实际项目运行一周后operation这个表会变得特别大,导致CopyProject花费的时间很长,CopyProjectEx把operation的在复制时没有一块复制过去,节省时间
class CopyProjectEx:
@staticmethod
def create_database(connection, new_db):
with connection.cursor() as cursor:
cursor.execute(f'create database "{new_db}"')
connection.commit()
@staticmethod
def execute_pg_dump(source_db, exclude_table_list):
os.environ["PGPASSWORD"] = get_pg_password() # 设置密码环境变量
pg_config = get_pg_config()
host = pg_config["host"]
port = pg_config["port"]
user = pg_config["user"]
dump_command_structure = f"pg_dump -h {host} -p {port} -U {user} -F c -s -f source_db_structure.dump {source_db}"
os.system(dump_command_structure)
if exclude_table_list is not None:
exclude_table = " ".join(["-T {}".format(i) for i in exclude_table_list])
dump_command_db = f"pg_dump -h {host} -p {port} -U {user} -F c -a {exclude_table} -f source_db.dump {source_db}"
else:
dump_command_db = f"pg_dump -h {host} -p {port} -U {user} -F c -a -f source_db.dump {source_db}"
os.system(dump_command_db)
@staticmethod
def execute_pg_restore(new_db):
os.environ["PGPASSWORD"] = get_pg_password() # 设置密码环境变量
pg_config = get_pg_config()
host = pg_config["host"]
port = pg_config["port"]
user = pg_config["user"]
restore_command_structure = f"pg_restore -h {host} -p {port} -U {user} -d {new_db} source_db_structure.dump"
os.system(restore_command_structure)
restore_command_db = (
f"pg_restore -h {host} -p {port} -U {user} -d {new_db} source_db.dump"
)
os.system(restore_command_db)
@staticmethod
def init_operation_table(connection, excluded_table):
with connection.cursor() as cursor:
if "operation" in excluded_table:
insert_query = "insert into operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
cursor.execute(insert_query)
if "current_operation" in excluded_table:
insert_query = "insert into current_operation (id) values (0)"
cursor.execute(insert_query)
if "restore_operation" in excluded_table:
insert_query = "insert into restore_operation (id) values (0)"
cursor.execute(insert_query)
if "batch_operation" in excluded_table:
insert_query = "insert into batch_operation (id, redo, undo, redo_cs, undo_cs) values (0, '', '', '', '')"
cursor.execute(insert_query)
if "operation_table" in excluded_table:
insert_query = (
"insert into operation_table (option) values ('operation')"
)
cursor.execute(insert_query)
connection.commit()
def __call__(self, source: str, new_db: str, excluded_tables: [str] = None) -> None:
source_connection = pg.connect(
conninfo=get_project_pgconn_string(), autocommit=True
)
self.create_database(source_connection, new_db)
self.execute_pg_dump(source, excluded_tables)
self.execute_pg_restore(new_db)
source_connection.close()
new_db_connection = pg.connect(
conninfo=get_project_pgconn_string(db_name=new_db), autocommit=True
)
self.init_operation_table(new_db_connection, excluded_tables)
new_db_connection.close()
def create_project(name: str) -> None:
return copy_project("project", name)
def delete_project(name: str) -> None:
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
with conn.cursor() as cur:
cur.execute(
f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{name}'"
)
cur.execute(f'drop database "{name}"')
def clean_project(excluded: list[str] = []) -> None:
projects = list_project()
with pg.connect(conninfo=get_project_pgconn_string(), autocommit=True) as conn:
with conn.cursor(row_factory=dict_row) as cur:
row = cur.execute(f"select current_database()").fetchone()
if row != None:
current_db = row["current_database"]
if current_db in projects:
projects.remove(current_db)
for project in projects:
if project in _server_databases or project in excluded:
continue
cur.execute(
f"select pg_terminate_backend(pid) from pg_stat_activity where datname = '{project}'"
)
cur.execute(f'drop database "{project}"')
def open_project(name: str) -> None:
open_connection(name)
def is_project_open(name: str) -> bool:
return is_connection_open(name)
def close_project(name: str) -> None:
close_connection(name)
-281
View File
@@ -1,281 +0,0 @@
from psycopg import sql
from psycopg.rows import dict_row, Row
from .connection import project_connection
from .database import read
from typing import Any
_NODE = '_node'
_LINK = '_link'
_CURVE = '_curve'
_PATTERN = '_pattern'
_REGION = '_region'
JUNCTION = 'junction'
RESERVOIR = 'reservoir'
TANK = 'tank'
PIPE = 'pipe'
PUMP = 'pump'
VALVE = 'valve'
PATTERN = 'pattern'
CURVE = 'curve'
REGION = 'region'
# DingZQ, 2025-02-05
'''
C++ 代码里已经定义了这些 enum 值
{
kNothing = -1,
//Node
kReservoir = 0,
kTank,
kJunction,
//Link
kPipe,
kPump,
kValve,
'''
ELEMENT_TYPES : dict[str, int] = {
RESERVOIR : 0,
TANK : 1,
JUNCTION : 2,
PIPE : 3,
PUMP : 4,
VALVE : 5,
}
def _get_from(name: str, id: str, base_type: str) -> Row | None:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(
sql.SQL("select * from {} where id = %s").format(
sql.Identifier(base_type)
),
(id,),
)
return cur.fetchone()
def is_node(name: str, id: str) -> bool:
return _get_from(name, id, _NODE) != None
def is_junction(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == JUNCTION
def is_reservoir(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == RESERVOIR
def is_tank(name: str, id: str) -> bool:
row = _get_from(name, id, _NODE)
return row != None and row['type'] == TANK
def is_link(name: str, id: str) -> bool:
return _get_from(name, id, _LINK) != None
def is_pipe(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == PIPE
def is_pump(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == PUMP
def is_valve(name: str, id: str) -> bool:
row = _get_from(name, id, _LINK)
return row != None and row['type'] == VALVE
# DingZQ, 2025-02-05
def get_node_type(name: str, node_id: str) -> str:
row = _get_from(name, node_id, _NODE)
return row['type']
def get_link_type(name: str, link_id: str) -> str:
row = _get_from(name, link_id, _LINK)
return row['type']
def get_element_type(name: str, element_id: str) -> str:
if is_node(name, element_id):
return get_node_type(name, element_id)
elif is_link(name, element_id):
return get_link_type(name, element_id)
else:
return None
def get_element_type_value(name: str, element_id: str) -> int:
return ELEMENT_TYPES[get_element_type(name, element_id)]
def is_curve(name: str, id: str) -> bool:
return _get_from(name, id, _CURVE) != None
def is_pattern(name: str, id: str) -> bool:
return _get_from(name, id, _PATTERN) != None
def is_region(name: str, id: str) -> bool:
return _get_from(name, id, _REGION) != None
def _get_all(name: str, base_type: str) -> list[str]:
ids : list[str] = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from {base_type} order by id")
for record in cur:
ids.append(record['id'])
return ids
def get_nodes(name: str) -> list[str]:
return _get_all(name, _NODE)
# DingZQ
def _get_nodes_by_type(name: str, type: str) -> list[str]:
ids : list[str] = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from {_NODE} where type = '{type}' order by id")
for record in cur:
ids.append(record['id'])
return ids
# DingZQ
def get_nodes_id_and_type(name: str) -> dict[str, str]:
nodes_id_and_type: dict[str, str] = {}
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id, type from {_NODE} order by id")
for record in cur:
nodes_id_and_type[record['id']] = record['type']
return nodes_id_and_type
# DingZQ 2024-12-31
def get_major_nodes(name: str, diameter: int) -> list[str]:
major_nodes_set = set()
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select node1, node2 from pipes where diameter > {diameter}")
for record in cur:
major_nodes_set.add(record['node1'])
major_nodes_set.add(record['node2'])
return list(major_nodes_set)
# DingZQs
def get_junctions(name: str) -> list[str]:
return _get_nodes_by_type(name, JUNCTION)
# DingZQ
def get_reservoirs(name: str) -> list[str]:
return _get_nodes_by_type(name, RESERVOIR)
# DingZQ
def get_tanks(name: str) -> list[str]:
return _get_nodes_by_type(name, TANK)
# DingZQ
def get_links(name: str) -> list[str]:
return _get_all(name, _LINK)
# DingZQ
def _get_links_by_type(name: str, type: str) -> list[str]:
ids : list[str] = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from {_LINK} where type = '{type}' order by id")
for record in cur:
ids.append(record['id'])
return ids
# DingZQ
def get_links_id_and_type(name: str) -> dict[str, str]:
links_id_and_type: dict[str, str] = {}
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id, type from {_LINK} order by id")
for record in cur:
links_id_and_type[record['id']] = record['type']
return links_id_and_type
# DingZQ 2024-12-31
# 获取直径大于800的管道
def get_major_pipes(name: str, diameter: int) -> list[str]:
major_pipe_ids: list[str] = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select id from pipes where diameter > {diameter} order by id")
for record in cur:
major_pipe_ids.append(record['id'])
return major_pipe_ids
# DingZQ
def get_pipes(name: str) -> list[str]:
return _get_links_by_type(name, PIPE)
# DingZQ
def get_pumps(name: str) -> list[str]:
return _get_links_by_type(name, PUMP)
# DingZQ
def get_valves(name: str) -> list[str]:
return _get_links_by_type(name, VALVE)
def get_curves(name: str) -> list[str]:
return _get_all(name, _CURVE)
def get_patterns(name: str) -> list[str]:
return _get_all(name, _PATTERN)
def get_regions(name: str) -> list[str]:
return _get_all(name, _REGION)
def get_node_links(name: str, id: str) -> list[str]:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
links: list[str] = []
for p in cur.execute(
"select id from pipes where node1 = %s or node2 = %s", (id, id)
).fetchall():
links.append(p['id'])
for p in cur.execute(
"select id from pumps where node1 = %s or node2 = %s", (id, id)
).fetchall():
links.append(p['id'])
for p in cur.execute(
"select id from valves where node1 = %s or node2 = %s", (id, id)
).fetchall():
links.append(p['id'])
return links
def get_link_nodes(name: str, id: str) -> list[str]:
row = {}
if is_pipe(name, id):
row = read(name, "select node1, node2 from pipes where id = %s", (id,))
elif is_pump(name, id):
row = read(name, "select node1, node2 from pumps where id = %s", (id,))
elif is_valve(name, id):
row = read(name, "select node1, node2 from valves where id = %s", (id,))
return [str(row['node1']), str(row['node2'])]
def get_region_type(name: str, id: str)->str:
if(is_region(name,id)):
type = read(name, "select type from _region where id = %s", (id,))
return type
-48
View File
@@ -1,48 +0,0 @@
from .database import *
def get_rule_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'rules' : {'type': 'str_list' , 'optional': False , 'readonly': False} }
def get_rule(name: str) -> dict[str, Any]:
cs = read_all(name, f"select * from rules")
ds = []
for c in cs:
ds.append(c['line'])
return { 'rules': ds }
def _set_rule(name: str, cs: ChangeSet) -> DbChangeSet:
old = get_rule(name)
redo_sql = 'delete from rules;'
for line in cs.operations[0]['rules']:
redo_sql += f"\ninsert into rules (line) values ('{line}');"
undo_sql = 'delete from rules;'
for line in old['rules']:
undo_sql += f"\ninsert into rules (line) values ('{line}');"
redo_cs = g_update_prefix | { 'type': 'rule', 'rules': cs.operations[0]['rules'] }
undo_cs = g_update_prefix | { 'type': 'rule', 'rules': old['rules'] }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_rule(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_rule(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3]
# TODO...
#--------------------------------------------------------------
def inp_in_rule(line: str) -> str:
return str(f"insert into rules (line) values ('{line}');")
def inp_out_rule(name: str) -> list[str]:
return get_rule(name)['rules']
-240
View File
@@ -1,240 +0,0 @@
from .database import *
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
def get_energy_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'GLOBAL PRICE' : element_schema,
'GLOBAL PATTERN' : element_schema,
'GLOBAL EFFIC' : element_schema,
'DEMAND CHARGE' : element_schema }
def get_energy(name: str) -> dict[str, Any]:
ts = read_all(name, f"select * from energy")
d = {}
for e in ts:
d[e['key']] = str(e['value'])
return d
def _set_energy(name: str, cs: ChangeSet) -> DbChangeSet:
raw_old = get_energy(name)
old = {}
new = {}
new_dict = cs.operations[0]
schema = get_energy_schema(name)
for key in schema.keys():
if key in new_dict:
old[key] = str(raw_old[key])
new[key] = str(new_dict[key])
redo_cs = g_update_prefix | { 'type' : 'energy' }
redo_sql = ''
for key, value in new.items():
if redo_sql != '':
redo_sql += '\n'
redo_sql += f"update energy set value = '{value}' where key = '{key}';"
redo_cs |= { key: value }
undo_cs = g_update_prefix | { 'type' : 'energy' }
undo_sql = ''
for key, value in old.items():
if undo_sql != '':
undo_sql += '\n'
undo_sql += f"update energy set value = '{value}' where key = '{key}';"
undo_cs |= { key: value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_energy(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_energy(name, cs))
def get_pump_energy_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'pump' : {'type': 'str' , 'optional': False , 'readonly': True },
'price' : {'type': 'float' , 'optional': True , 'readonly': False},
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False},
'effic' : {'type': 'str' , 'optional': True , 'readonly': False} }
def get_pump_energy(name: str, pump: str) -> dict[str, Any]:
d = {}
d['pump'] = pump
pe = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
d['price'] = float(pe['price']) if pe != None else None
pe = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
d['pattern'] = str(pe['pattern']) if pe != None else None
pe = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
d['effic'] = str(pe['effic']) if pe != None else None
return d
class PumpEnergy(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'pump_energy'
self.pump = str(input['pump'])
self.price = float(input['price']) if 'price' in input and input['price'] != None else None
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
self.effic = str(input['effic']) if 'effic' in input and input['effic'] != None else None
self.f_type = f"'{self.type}'"
self.f_pump = f"'{self.pump}'"
self.f_price = self.price if self.price != None else 'null'
self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null'
self.f_effic = f"'{self.effic}'" if self.effic != None else 'null'
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'pump': self.pump, 'price': self.price, 'pattern': self.pattern, 'effic': self.effic }
def _set_pump_energy(name: str, cs: ChangeSet) -> DbChangeSet:
old = PumpEnergy(get_pump_energy(name, cs.operations[0]['pump']))
raw_new = get_pump_energy(name, cs.operations[0]['pump'])
new_dict = cs.operations[0]
schema = get_pump_energy_schema(name)
for key, value in schema.items():
if key in new_dict and not value['readonly']:
raw_new[key] = new_dict[key]
new = PumpEnergy(raw_new)
redo_sql = f"delete from energy_pump_price where pump = {new.f_pump};\ndelete from energy_pump_pattern where pump = {new.f_pump};\ndelete from energy_pump_effic where pump = {new.f_pump};"
if new.price != None:
redo_sql += f"\ninsert into energy_pump_price (pump, price) values ({new.f_pump}, {new.f_price});"
if new.pattern != None:
redo_sql += f"\ninsert into energy_pump_pattern (pump, pattern) values ({new.f_pump}, {new.f_pattern});"
if new.effic != None:
redo_sql += f"\ninsert into energy_pump_effic (pump, effic) values ({new.f_pump}, {new.f_effic});"
undo_sql = f"delete from energy_pump_price where pump = {old.f_pump};\ndelete from energy_pump_pattern where pump = {old.f_pump};\ndelete from energy_pump_effic where pump = {old.f_pump};"
if old.price != None:
undo_sql += f"\ninsert into energy_pump_price (pump, price) values ({old.f_pump}, {old.f_price});"
if old.pattern != None:
undo_sql += f"\ninsert into energy_pump_pattern (pump, pattern) values ({old.f_pump}, {old.f_pattern});"
if old.effic != None:
undo_sql += f"\ninsert into energy_pump_effic (pump, effic) values ({old.f_pump}, {old.f_effic});"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_pump_energy(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_pump_energy(name, cs))
#--------------------------------------------------------------
# [EPA2][EPA3][IN][OUT]
# GLOBAL {PRICE/PATTERN/EFFIC} value
# PUMP id {PRICE/PATTERN/EFFIC} value
# DEMAND CHARGE value
#--------------------------------------------------------------
def inp_in_energy(line: str) -> str:
tokens = line.split()
if tokens[0].upper() == 'PUMP':
pump = tokens[1]
key = tokens[2].lower()
value = tokens[3]
if key == 'price':
value = float(value)
else:
value = f"'{value}'"
if key == 'efficiency':
key = 'effic'
return str(f"insert into energy_pump_{key} (pump, {key}) values ('{pump}', {value});")
else:
line = line.upper().strip()
for key in get_energy_schema('').keys():
if line.startswith(key):
value = line.removeprefix(key).strip()
# exception here
if line.startswith('GLOBAL EFFICIENCY'):
value = line.removeprefix('GLOBAL EFFICIENCY').strip()
return str(f"update energy set value = '{value}' where key = '{key}';")
return str('')
def inp_out_energy(name: str) -> list[str]:
lines = []
objs = read_all(name, f"select * from energy")
for obj in objs:
key = obj['key']
value = obj['value']
if value.strip() != '':
lines.append(f'{key} {value}')
objs = read_all(name, f"select * from energy_pump_price")
for obj in objs:
pump = obj['pump']
value = obj['price']
lines.append(f'PUMP {pump} PRICE {value}')
objs = read_all(name, f"select * from energy_pump_pattern")
for obj in objs:
pump = obj['pump']
value = obj['pattern']
lines.append(f'PUMP {pump} PATTERN {value}')
objs = read_all(name, f"select * from energy_pump_effic")
for obj in objs:
pump = obj['pump']
value = obj['effic']
lines.append(f'PUMP {pump} EFFIC {value}')
return lines
def delete_pump_energy_by_pump(name: str, pump: str) -> ChangeSet:
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
row2 = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
row3 = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
if row1 == None and row2 == None and row3 == None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': None, 'pattern': None, 'effic': None})
def unset_pump_energy_by_pattern(name: str, pattern: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select * from energy_pump_pattern where pattern = '{pattern}'")
for row in rows:
pump = row['pump']
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
price = float(row1['price']) if row1 != None else None
row2 = try_read(name, f"select * from energy_pump_effic where pump = '{pump}'")
effic = str(row2['effic']) if row2 != None else None
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': None, 'effic': effic})
return cs
def unset_pump_energy_by_curve(name: str, curve: str) -> ChangeSet:
cs = ChangeSet()
rows = read_all(name, f"select * from energy_pump_effic where effic = '{curve}'")
for row in rows:
pump = row['pump']
row1 = try_read(name, f"select * from energy_pump_price where pump = '{pump}'")
price = float(row1['price']) if row1 != None else None
row2 = try_read(name, f"select * from energy_pump_pattern where pump = '{pump}'")
pattern = str(row2['pattern']) if row2 != None else None
cs.append(g_update_prefix | {'type': 'pump_energy', 'pump' : pump, 'price': price, 'pattern': pattern, 'effic': None})
return cs
-40
View File
@@ -1,40 +0,0 @@
from .database import *
def get_title_schema(name: str) -> dict[str, dict[str, Any]]:
return {'value': {'type': 'float', 'optional': False, 'readonly': False}}
def get_title(name: str) -> dict[str, Any]:
title = read(name, 'select * from title')
return { 'value': title['value'] }
def _set_title(name: str, cs: ChangeSet) -> DbChangeSet:
new = cs.operations[0]['value']
old = get_title(name)['value']
redo_sql = f"update title set value = '{new}';"
undo_sql = f"update title set value = '{old}';"
redo_cs = g_update_prefix | { 'type': 'title', 'value': new }
undo_cs = g_update_prefix | { 'type': 'title', 'value': old }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_title(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_title(name ,cs))
def inp_in_title(section: list[str]) -> str:
if section == []:
return str('')
title = '\n'.join(section)
return str(f"update title set value = '{title}';")
def inp_out_title(name: str) -> list[str]:
obj = str(get_title(name)['value'])
return obj.split('\n')
-39
View File
@@ -1,39 +0,0 @@
from .database import *
def get_backdrop_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'content' : {'type': 'str' , 'optional': False , 'readonly': False} }
def get_backdrop(name: str) -> dict[str, Any]:
e = read(name, f"select * from backdrop")
return { 'content': e['content'] }
def _set_backdrop(name: str, cs: ChangeSet) -> DbChangeSet:
old = get_backdrop(name)
redo_sql = f"update backdrop set content = '{cs.operations[0]['content']}' where content = '{old['content']}';"
undo_sql = f"update backdrop set content = '{old['content']}' where content = '{cs.operations[0]['content']}';"
redo_cs = g_update_prefix | { 'type': 'backdrop', 'content': cs.operations[0]['content'] }
undo_cs = g_update_prefix | { 'type': 'backdrop', 'content': old['content'] }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_backdrop(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_backdrop(name, cs))
def inp_in_backdrop(section: list[str]) -> str:
if section == []:
return str('')
content = '\n'.join(section)
return str(f"update backdrop set content = '{content}';")
def inp_out_backdrop(name: str) -> list[str]:
obj = str(get_backdrop(name)['content'])
return obj.split('\n')
View File
-123
View File
@@ -1,123 +0,0 @@
from .database import *
SCADA_DEVICE_TYPE_PRESSURE = 'PRESSURE'
SCADA_DEVICE_TYPE_DEMAND = 'DEMAND'
SCADA_DEVICE_TYPE_QUALITY = 'QUALITY'
SCADA_DEVICE_TYPE_LEVEL = 'LEVEL'
SCADA_DEVICE_TYPE_FLOW = 'FLOW'
SCADA_DEVICE_TYPE_UNKNOWN = 'UNKNOWN'
def get_scada_device_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str', 'optional': False, 'readonly': True },
'name' : {'type': 'str', 'optional': True , 'readonly': False},
'address': {'type': 'str', 'optional': True , 'readonly': False},
'sd_type': {'type': 'str', 'optional': True , 'readonly': False}}
def get_scada_device(name: str, id: str) -> dict[str, Any]:
sm = try_read(name, f"select * from scada_device where id = '{id}'")
if sm == None:
return {}
d = {}
d['id'] = str(sm['id'])
d['name'] = str(sm['name']) if sm['name'] != None else None
d['address'] = str(sm['address']) if sm['address'] != None else None
d['sd_type'] = str(sm['sd_type']) if sm['sd_type'] != None else None
return d
class ScadaDevice(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'scada_device'
self.id = str(input['id'])
self.name = str(input['name']) if 'name' in input and input['name'] != None else None
self.address = str(input['address']) if 'address' in input and input['address'] != None else None
self.sd_type = str(input['sd_type']) if 'sd_type' in input and input['sd_type'] != None else None
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_name = f"'{self.name}'" if self.name != None else 'null'
self.f_address = f"'{self.address}'" if self.address != None else 'null'
self.f_sd_type = f"'{self.sd_type}'" if self.sd_type != None else 'null'
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'name': self.name, 'address': self.address, 'sd_type': self.sd_type }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
old = ScadaDevice(get_scada_device(name, cs.operations[0]['id']))
raw_new = get_scada_device(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_scada_device_schema(name)
for key, value in schema.items():
if key in new_dict and not value['readonly']:
raw_new[key] = new_dict[key]
new = ScadaDevice(raw_new)
redo_sql = f"update scada_device set name = {new.f_name}, address = {new.f_address}, sd_type = {new.f_sd_type} where id = {new.f_id};"
undo_sql = f"update scada_device set name = {old.f_name}, address = {old.f_address}, sd_type = {old.f_sd_type} where id = {old.f_id};"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_device(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_scada_device(name, cs))
def _add_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
new = ScadaDevice(cs.operations[0])
redo_sql = f"insert into scada_device (id, name, address, sd_type) values ({new.f_id}, {new.f_name}, {new.f_address}, {new.f_sd_type});"
undo_sql = f"delete from scada_device where id = {new.f_id};"
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_device(name, cs.operations[0]['id']) != {}:
return ChangeSet()
return execute_command(name, _add_scada_device(name, cs))
def _delete_scada_device(name: str, cs: ChangeSet) -> DbChangeSet:
old = ScadaDevice(get_scada_device(name, cs.operations[0]['id']))
redo_sql = f"delete from scada_device where id = {old.f_id};"
undo_sql = f"insert into scada_device (id, name, address, sd_type) values ({old.f_id}, {old.f_name}, {old.f_address}, {old.f_sd_type});"
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_scada_device(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_device(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _delete_scada_device(name, cs))
def get_all_scada_device_ids(name: str) -> list[str]:
result : list[str] = []
rows = read_all(name, 'select id from scada_device order by id')
for row in rows:
result.append(str(row['id']))
return result
def get_all_scada_devices(name: str) -> list[dict[str, Any]]:
return read_all(name, 'select * from scada_device order by id')
-90
View File
@@ -1,90 +0,0 @@
from .database import *
def get_scada_device_data_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'device_id' : {'type': 'str' , 'optional': False , 'readonly': True },
'data' : {'type': 'list' , 'optional': False , 'readonly': False,
'element': { 'time' : {'type': 'str' , 'optional': False , 'readonly': False },
'value' : {'type': 'float' , 'optional': False , 'readonly': False } }}}
def get_scada_device_data(name: str, device_id: str) -> dict[str, Any]:
sds = read_all(name, f"select * from scada_device_data where device_id = '{device_id}' order by time")
ds = []
for r in sds:
ds.append({ 'time': str(r['time']), 'value': float(r['value']) })
return { 'device_id': device_id, 'data': ds }
def _set_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
device_id = cs.operations[0]['device_id']
old = get_scada_device_data(name, device_id)
new = { 'device_id': device_id, 'data': [] }
f_device_id = f"'{device_id}'"
# TODO: transaction ?
redo_sql = f"delete from scada_device_data where device_id = {f_device_id};"
for tv in cs.operations[0]['data']:
time, value = str(tv['time']), float(tv['value'])
f_time, f_value = f"'{time}'", value
redo_sql += f"\ninsert into scada_device_data (device_id, time, value) values ({f_device_id}, {f_time}, {f_value});"
new['data'].append({ 'time': time, 'value': value })
undo_sql = f"delete from scada_device_data where device_id = {f_device_id};"
for tv in old['data']:
time, value = str(tv['time']), float(tv['value'])
f_time, f_value = f"'{time}'", value
undo_sql += f"\ninsert into scada_device_data (device_id, time, value) values ({f_device_id}, {f_time}, {f_value});"
redo_cs = g_update_prefix | { 'type': 'scada_device_data' } | new
undo_cs = g_update_prefix | { 'type': 'scada_device_data' } | old
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
return execute_command(name, _set_scada_device_data(name, cs))
def _add_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
values = cs.operations[0]
device_id = values['device_id']
time = values['time']
value = float(values['value'])
redo_sql = f"insert into scada_device_data (device_id, time, value) values ('{device_id}', '{time}', {value});"
undo_sql = f"delete from scada_device_data where device_id = '{device_id}' and time = '{time}';"
redo_cs = g_add_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time, 'value': value }
undo_cs = g_delete_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'")
if row != None:
return ChangeSet()
return execute_command(name, _add_scada_device_data(name, cs))
def _delete_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet:
values = cs.operations[0]
device_id = values['device_id']
time = values['time']
value = float(read(name, f"select * from scada_device_data where device_id = '{device_id}' and time = '{time}'")['value'])
redo_sql = f"delete from scada_device_data where device_id = '{device_id}' and time = '{time}';"
undo_sql = f"insert into scada_device_data (device_id, time, value) values ('{device_id}', '{time}', {value});"
redo_cs = g_delete_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time }
undo_cs = g_add_prefix | { 'type': 'scada_device_data', 'device_id': device_id, 'time': time, 'value': value }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet:
row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'")
if row == None:
return ChangeSet()
return execute_command(name, _delete_scada_device_data(name, cs))
-197
View File
@@ -1,197 +0,0 @@
from .database import *
from .s0_base import *
SCADA_TYPE_PRESSURE = 'PRESSURE'
SCADA_TYPE_DEMAND = 'DEMAND'
SCADA_TYPE_QUALITY = 'QUALITY'
SCADA_TYPE_LEVEL = 'LEVEL'
SCADA_TYPE_FLOW = 'FLOW'
SCADA_MODEL_TYPE_JUNCTION = 'JUNCTION'
SCADA_MODEL_TYPE_RESERVOIR = 'RESERVOIR'
SCADA_MODEL_TYPE_TANK = 'TANK'
SCADA_MODEL_TYPE_PIPE = 'PIPE'
SCADA_MODEL_TYPE_PUMP = 'PUMP'
SCADA_MODEL_TYPE_VALVE = 'VALVE'
SCADA_ELEMENT_STATUS_OFFLINE = 'OFF'
SCADA_ELEMENT_STATUS_ONLINE = 'ON'
_scada_model_types = [SCADA_MODEL_TYPE_JUNCTION, SCADA_MODEL_TYPE_RESERVOIR, SCADA_MODEL_TYPE_TANK, SCADA_MODEL_TYPE_PIPE, SCADA_MODEL_TYPE_PUMP, SCADA_MODEL_TYPE_VALVE]
def _check_model(name: str, cs: ChangeSet) -> bool:
has_model_id = 'model_id' in cs.operations[0]
has_model_type = 'model_type' in cs.operations[0]
if has_model_id and has_model_type:
pass
elif has_model_id and not has_model_type:
return False
elif not has_model_id and has_model_type:
return False
elif not has_model_id and not has_model_type:
return True
_model_id = cs.operations[0]['model_id']
_model_type = cs.operations[0]['model_type']
if _model_type == SCADA_MODEL_TYPE_JUNCTION:
return is_junction(name, _model_id)
elif _model_type == SCADA_MODEL_TYPE_RESERVOIR:
return is_reservoir(name, _model_id)
elif _model_type == SCADA_MODEL_TYPE_TANK:
return is_tank(name, _model_id)
elif _model_type == SCADA_MODEL_TYPE_PIPE:
return is_pipe(name, _model_id)
elif _model_type == SCADA_MODEL_TYPE_PUMP:
return is_pump(name, _model_id)
elif _model_type == SCADA_MODEL_TYPE_VALVE:
return is_valve(name, _model_id)
return False
def get_scada_element_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
'device_id' : {'type': 'str' , 'optional': True , 'readonly': False},
'model_id' : {'type': 'str' , 'optional': True , 'readonly': False},
'model_type' : {'type': 'str' , 'optional': True , 'readonly': False},
'status' : {'type': 'str' , 'optional': True , 'readonly': False} }
def get_scada_element(name: str, id: str) -> dict[str, Any]:
sm = try_read(name, f"select * from scada_element where id = '{id}'")
if sm == None:
return {}
d = {}
d['id'] = str(sm['id'])
d['x'] = float(sm['x'])
d['y'] = float(sm['y'])
d['device_id'] = str(sm['device_id']) if sm['device_id'] != None else None
d['model_id'] = str(sm['model_id']) if sm['model_id'] != None else None
d['model_type'] = str(sm['model_type']) if sm['model_type'] != None else None
d['status'] = str(sm['status'])
return d
class ScadaModel(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'scada_element'
self.id = str(input['id'])
self.x = float(input['x'])
self.y = float(input['y'])
self.device_id = str(input['device_id']) if 'device_id' in input and input['device_id'] != None else None
self.model_id = str(input['model_id']) if 'model_id' in input and input['model_id'] != None else None
self.model_type = str(input['model_type']) if 'model_type' in input and input['model_type'] != None else None
self.status = str(input['status']) if 'status' in input and input['status'] != None else SCADA_ELEMENT_STATUS_OFFLINE
self.f_type = f"'{self.type}'"
self.f_id = f"'{self.id}'"
self.f_x = self.x
self.f_y = self.y
self.f_device_id = f"'{self.device_id}'" if self.device_id != None else 'null'
self.f_model_id = f"'{self.model_id}'" if self.model_id != None else 'null'
self.f_model_type = f"'{self.model_type}'" if self.model_type != None else 'null'
self.f_status = f"'{self.status}'"
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'device_id': self.device_id, 'model_id': self.model_id, 'model_type': self.model_type, 'status': self.status }
def as_id_dict(self) -> dict[str, Any]:
return { 'type': self.type, 'id': self.id }
def _set_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
old = ScadaModel(get_scada_element(name, cs.operations[0]['id']))
raw_new = get_scada_element(name, cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_scada_element_schema(name)
for key, value in schema.items():
if key in new_dict and not value['readonly']:
raw_new[key] = new_dict[key]
new = ScadaModel(raw_new)
redo_sql = f"update scada_element set x = {new.f_x}, y = {new.f_y}, device_id = {new.f_device_id}, model_id = {new.f_model_id}, model_type = {new.f_model_type}, status = {new.f_status} where id = {new.f_id};"
undo_sql = f"update scada_element set x = {old.f_x}, y = {old.f_y}, device_id = {old.f_device_id}, model_id = {old.f_model_id}, model_type = {old.f_model_type}, status = {old.f_status} where id = {old.f_id};"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_element(name, cs.operations[0]['id']) == {}:
return ChangeSet()
if _check_model(name, cs) == False:
return ChangeSet()
return execute_command(name, _set_scada_element(name, cs))
def _add_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
new = ScadaModel(cs.operations[0])
redo_sql = f"insert into scada_element (id, x, y, device_id, model_id, model_type, status) values ({new.f_id}, {new.f_x}, {new.f_y}, {new.f_device_id}, {new.f_model_id}, {new.f_model_type}, {new.f_status});"
undo_sql = f"delete from scada_element where id = {new.f_id};"
redo_cs = g_add_prefix | new.as_dict()
undo_cs = g_delete_prefix | new.as_id_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_element(name, cs.operations[0]['id']) != {}:
return ChangeSet()
if _check_model(name, cs) == False:
return ChangeSet()
return execute_command(name, _add_scada_element(name, cs))
def _delete_scada_element(name: str, cs: ChangeSet) -> DbChangeSet:
old = ScadaModel(get_scada_element(name, cs.operations[0]['id']))
redo_sql = f"delete from scada_element where id = {old.f_id};"
undo_sql = f"insert into scada_element (id, x, y, device_id, model_id, model_type, status) values ({old.f_id}, {old.f_x}, {old.f_y}, {old.f_device_id}, {old.f_model_id}, {old.f_model_type}, {old.f_status});"
redo_cs = g_delete_prefix | old.as_id_dict()
undo_cs = g_add_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_scada_element(name: str, cs: ChangeSet) -> ChangeSet:
if get_scada_element(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _delete_scada_element(name, cs))
def get_all_scada_element_ids(name: str) -> list[str]:
result : list[str] = []
rows = read_all(name, 'select id from scada_element order by id')
for row in rows:
result.append(str(row['id']))
return result
#
# create table scada_element
# (
# id text primary key
# , x float8 not null
# , y float8 not null
# , device_id text references scada_device(id)
# , model_id varchar(32) -- add constraint in API
# , model_type scada_model_type
# , status scada_element_status not null default 'OFF'
# );
#
# 返回listlist里每个item是dict,内容是 'id':'abc' 这样
# scada_model type 是类似pressureflow之类的,是由Device 决定 的
def get_all_scada_elements(name: str) -> list[dict[str, Any]]:
return read_all(name, 'select * from scada_element order by id')
-95
View File
@@ -1,95 +0,0 @@
from .database import *
from .s32_region_util import from_postgis_polygon, to_postgis_polygon
def get_region_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False} }
def get_region(name: str, id: str) -> dict[str, Any]:
r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{id}'")
if r == None:
return {}
d = {}
d['id'] = str(r['id'])
d['boundary'] = from_postgis_polygon(str(r['boundary_geom']))
return d
def _set_region(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
new = cs.operations[0]['boundary']
old = get_region(name, id)['boundary']
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new)}') where id = '{id}';"
undo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old)}') where id = '{id}';"
redo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': new }
undo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': old }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_region(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]:
return ChangeSet()
b = cs.operations[0]['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if get_region(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _set_region(name, cs))
def _add_region(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
new = cs.operations[0]['boundary']
redo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(new)}');"
undo_sql = f"delete from region where id = '{id}';"
redo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': new }
undo_cs = g_delete_prefix | { 'type': 'region', 'id': id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_region(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]:
return ChangeSet()
b = cs.operations[0]['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if get_region(name, cs.operations[0]['id']) != {}:
return ChangeSet()
return execute_command(name, _add_region(name, cs))
def _delete_region(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
old = get_region(name, id)['boundary']
redo_sql = f"delete from region where id = '{id}';"
undo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(old)}');"
redo_cs = g_delete_prefix | { 'type': 'region', 'id': id }
undo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': old }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_region(name: str, cs: ChangeSet) -> ChangeSet:
if 'id' not in cs.operations[0]:
return ChangeSet()
if get_region(name, cs.operations[0]['id']) == {}:
return ChangeSet()
return execute_command(name, _delete_region(name, cs))
def inp_in_region(line: str) -> str:
tokens = line.split()
return str(f"insert into _region (id, type) values ('{tokens[0]}', '{tokens[1]}');")
def inp_in_bound(line: str) -> str:
tokens = line.split()
return tokens[0]
def inp_in_regionnodes(line: str)->str:
tokens = line.split()
return tokens[0]
-230
View File
@@ -1,230 +0,0 @@
from .database import *
from .s0_base import is_node
from .s32_region_util import to_postgis_polygon
from .s32_region import get_region
def get_district_metering_area_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
'parent' : {'type': 'str' , 'optional': True , 'readonly': False },
'level' : {'type': 'int' , 'optional': False , 'readonly': True } }
def get_district_metering_area(name: str, id: str) -> dict[str, Any]:
dma = get_region(name, id)
if dma == {}:
return {}
r = try_read(name, f"select * from region_dma where id = '{id}'")
if r == None:
return {}
dma['parent'] = r['parent']
dma['nodes'] = list(eval(r['nodes']))
dma['level'] = 1
if dma['parent'] != None:
parent = dma['parent']
while parent != None:
parent = read(name, f"select parent from region_dma where id = '{parent}'")['parent']
dma['level'] += 1
return dma
def _set_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
new_boundary = cs.operations[0]['boundary']
old_boundary = get_region(name, id)['boundary']
new_parent = cs.operations[0]['parent']
f_new_parent = f"'{new_parent}'" if new_parent != None else 'null'
new_nodes = cs.operations[0]['nodes']
str_new_nodes = str(new_nodes).replace("'", "''")
old = get_district_metering_area(name, id)
old_parent = old['parent']
f_old_parent = f"'{old_parent}'" if old_parent != None else 'null'
old_nodes = old['nodes']
str_old_nodes = str(old_nodes).replace("'", "''")
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
redo_sql += f"update region_dma set parent = {f_new_parent}, nodes = '{str_new_nodes}' where id = '{id}';"
undo_sql = f"update region_dma set parent = {f_old_parent}, nodes = '{str_old_nodes}' where id = '{id}';"
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
redo_cs = g_update_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': new_boundary, 'parent': new_parent, 'nodes': new_nodes }
undo_cs = g_update_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': old_boundary, 'parent': old_parent, 'nodes': old_nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
dma = get_district_metering_area(name, op['id'])
if dma == {}:
return ChangeSet()
if 'boundary' not in op:
op['boundary'] = dma['boundary']
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'parent' not in op:
op['parent'] = dma['parent']
if op['parent'] != None and get_district_metering_area(name, op['parent']) == {}:
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = dma['nodes']
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _set_district_metering_area(name, cs))
def _add_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
boundary = cs.operations[0]['boundary']
parent = cs.operations[0]['parent']
f_parent = f"'{parent}'" if parent != None else 'null'
nodes = cs.operations[0]['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'DMA');"
redo_sql += f"insert into region_dma (id, parent, nodes) values ('{id}', {f_parent}, '{str_nodes}');"
undo_sql = f"delete from region_dma where id = '{id}';"
undo_sql += f"delete from region where id = '{id}';"
redo_cs = g_add_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': boundary, 'parent': parent, 'nodes': nodes }
undo_cs = g_delete_prefix | { 'type': 'district_metering_area', 'id': id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
dma = get_district_metering_area(name, op['id'])
if dma != {}:
return ChangeSet()
if 'boundary' not in op:
return ChangeSet()
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'parent' not in op:
op['parent'] = None
if op['parent'] != None and get_district_metering_area(name, op['parent']) == {}:
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = []
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _add_district_metering_area(name, cs))
def _delete_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
dma = get_district_metering_area(name, id)
boundary = dma['boundary']
parent = dma['parent']
f_parent = f"'{parent}'" if parent != None else 'null'
nodes = dma['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"delete from region_dma where id = '{id}';"
redo_sql += f"delete from region where id = '{id}';"
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'DMA');"
undo_sql += f"insert into region_dma (id, parent, nodes) values ('{id}', {f_parent}, '{str_nodes}');"
redo_cs = g_delete_prefix | { 'type': 'district_metering_area', 'id': id }
undo_cs = g_add_prefix | { 'type': 'district_metering_area', 'id': id, 'boundary': boundary, 'parent': parent, 'nodes': nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def _has_child(name: str, parent: str) -> bool:
return try_read(name, f"select * from region_dma where parent = '{parent}'") != None
def is_descendant_of(name: str, descendant: str, ancestor: str) -> bool:
parent = descendant
while parent != None:
parent = read(name, f"select parent from region_dma where id = '{parent}'")['parent']
if parent == ancestor:
return True
return False
def delete_district_metering_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
dma = get_district_metering_area(name, op['id'])
if dma == {}:
return ChangeSet()
#TODO: cascade ?
if _has_child(name, dma['id']):
return ChangeSet()
return execute_command(name, _delete_district_metering_area(name, cs))
def get_all_district_metering_area_ids(name: str) -> list[str]:
ids = []
for row in read_all(name, f"select id from region_dma"):
ids.append(row['id'])
return ids
def get_all_district_metering_areas(name: str) -> list[dict[str, Any]]:
result = []
for id in get_all_district_metering_area_ids(name):
result.append(get_district_metering_area(name, id))
return result
-168
View File
@@ -1,168 +0,0 @@
import ctypes
import os
import numpy as np
import pymetis
from .database import *
from .s0_base import get_nodes
from .s32_region_util import get_nodes_in_region
from .s32_region_util import Topology
PARTITION_TYPE_RB = 0
PARTITION_TYPE_KWAY = 1
'''
adjacency_list = [np.array([4, 2, 1]),
np.array([0, 2, 3]),
np.array([4, 3, 1, 0]),
np.array([1, 2, 5, 6]),
np.array([0, 2, 5]),
np.array([4, 3, 6]),
np.array([5, 3])]
n_cuts, membership = pymetis.part_graph(2, adjacency=adjacency_list)
# n_cuts = 3
# membership = [1, 1, 1, 0, 1, 0, 0]
nodes_part_0 = np.argwhere(np.array(membership) == 0).ravel() # [3, 5, 6]
nodes_part_1 = np.argwhere(np.array(membership) == 1).ravel() # [0, 1, 2, 4]
print(nodes_part_0)
print(nodes_part_1)
'''
def calculate_district_metering_area_for_nodes(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
if part_type != PARTITION_TYPE_RB and part_type != PARTITION_TYPE_KWAY:
return []
if part_count <= 0:
return []
elif part_count == 1:
return [nodes]
topology = Topology(name, nodes)
t_nodes = topology.nodes()
t_links = topology.links()
t_node_list = topology.node_list()
adjacency_list = []
for node in t_node_list:
links: list[str] = t_nodes[node]['links']
a_nodes: list[int] = []
for link in links:
if t_links[link]['node1'] == node:
i = t_node_list.index(t_links[link]['node2'])
a_nodes.append(i)
elif t_links[link]['node2'] == node:
i = t_node_list.index(t_links[link]['node1'])
a_nodes.append(i)
adjacency_list.append(np.array(a_nodes))
recursive = part_type == PARTITION_TYPE_RB
options = pymetis.Options()
options.set_defaults()
options._set(pymetis.OptionKey.CONTIG, 1)
options._set(pymetis.OptionKey.SEED, 0)
n_cuts, membership = pymetis.part_graph(
nparts=part_count,
adjacency=adjacency_list,
recursive=recursive,
options=options,
)
result: list[list[str]] = []
for i in range(0, part_count):
indices: list[int] = list(np.argwhere(np.array(membership) == i).ravel())
index_strs: list[str] = []
for index in indices:
index_strs.append(t_node_list[index])
result.append(index_strs)
return result
def _calculate_district_metering_area_for_nodes(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
if part_type != PARTITION_TYPE_RB and part_type != PARTITION_TYPE_KWAY:
return []
if part_count <= 0:
return []
elif part_count == 1:
return [nodes]
lib = ctypes.CDLL(os.path.join(os.getcwd(), 'api', 'CMetis.dll'))
METIS_NOPTIONS = 40
c_options = (ctypes.c_int64 * METIS_NOPTIONS)()
METIS_OK = 1
result = lib.set_default_options(c_options)
if result != METIS_OK:
return []
METIS_OPTION_PTYPE , METIS_OPTION_CONTIG = 0, 13
c_options[METIS_OPTION_PTYPE] = part_type
c_options[METIS_OPTION_CONTIG] = 1
topology = Topology(name, nodes)
t_nodes = topology.nodes()
t_links = topology.links()
t_node_list = topology.node_list()
t_link_list = topology.link_list()
nedges = len(t_link_list) * 2
c_nvtxs = ctypes.c_int64(len(t_node_list))
c_ncon = ctypes.c_int64(1)
c_xadj = (ctypes.c_int64 * (c_nvtxs.value + 1))()
c_adjncy = (ctypes.c_int64 * nedges)()
c_vwgt = (ctypes.c_int64 * (c_ncon.value * c_nvtxs.value))()
c_adjwgt = (ctypes.c_int64 * nedges)()
c_vsize = (ctypes.c_int64 * c_nvtxs.value)()
c_xadj[0] = 0
l, n = 0, 0
c_xadj_i = 1
for node in t_node_list:
links = t_nodes[node]['links']
for link in links:
node1 = t_links[link]['node1']
node2 = t_links[link]['node2']
c_adjncy[l] = t_node_list.index(node2) if node2 != node else t_node_list.index(node1)
c_adjwgt[l] = 1
l += 1
if len(links) > 0:
c_xadj[c_xadj_i] = l # adjncy.size()
c_xadj_i += 1
c_vwgt[n] = 1
c_vsize[n] = 1
n += 1
part_func = lib.part_graph_recursive if part_type == PARTITION_TYPE_RB else lib.part_graph_kway
c_nparts = ctypes.c_int64(part_count)
c_tpwgts = ctypes.POINTER(ctypes.c_double)()
c_ubvec = ctypes.POINTER(ctypes.c_double)()
c_out_edgecut = ctypes.c_int64(0)
c_out_part = (ctypes.c_int64 * c_nvtxs.value)()
result = part_func(ctypes.byref(c_nvtxs), ctypes.byref(c_ncon), c_xadj, c_adjncy, c_vwgt, c_vsize, c_adjwgt, ctypes.byref(c_nparts), c_tpwgts, c_ubvec, c_options, ctypes.byref(c_out_edgecut), c_out_part)
if result != METIS_OK:
return []
dmas : list[list[str]]= []
for i in range(part_count):
dmas.append([])
for i in range(c_nvtxs.value):
dmas[c_out_part[i]].append(t_node_list[i])
return dmas
def calculate_district_metering_area_for_region(name: str, region: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
nodes = get_nodes_in_region(name, region)
return calculate_district_metering_area_for_nodes(name, nodes, part_count, part_type)
def calculate_district_metering_area_for_network(name: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]:
nodes = get_nodes(name)
return calculate_district_metering_area_for_nodes(name, nodes, part_count, part_type)
-47
View File
@@ -1,47 +0,0 @@
from .s32_region_util import calculate_boundary, inflate_boundary
from .s33_dma_cal import *
from .s33_dma import get_all_district_metering_area_ids, get_all_district_metering_areas, get_district_metering_area, is_descendant_of
from .batch_exe import execute_batch_command
def generate_district_metering_area(name: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB, inflate_delta: float = 0.5) -> ChangeSet:
cs = ChangeSet()
dmas = get_all_district_metering_areas(name)
max_level = 0
for dma in dmas:
if dma['level'] > max_level:
max_level = dma['level']
while max_level > 0:
for dma in dmas:
if dma['level'] == max_level:
cs.delete({ 'type': 'district_metering_area', 'id': dma['id'] })
max_level -= 1
i = 1
for nodes in calculate_district_metering_area_for_network(name, part_count, part_type):
boundary = calculate_boundary(name, nodes)
boundary = inflate_boundary(name, boundary, inflate_delta)
cs.add({ 'type': 'district_metering_area', 'id': f"DMA_1_{i}", 'boundary': boundary, 'parent': None, 'nodes': nodes })
i += 1
return execute_batch_command(name, cs)
def generate_sub_district_metering_area(name: str, dma: str, part_count: int = 1, part_type: int = PARTITION_TYPE_RB, inflate_delta: float = 0.5) -> ChangeSet:
cs = ChangeSet()
for id in get_all_district_metering_area_ids(name):
if is_descendant_of(name, id, dma):
cs.delete({ 'type': 'district_metering_area', 'id': id })
level = get_district_metering_area(name, dma)['level'] + 1
i = 1
for nodes in calculate_district_metering_area_for_region(name, dma, part_count, part_type):
boundary = calculate_boundary(name, nodes)
boundary = inflate_boundary(name, boundary, inflate_delta)
cs.add({ 'type': 'district_metering_area', 'id': f"DMA_[{dma}]_{level}_{i}", 'boundary': boundary, 'parent': dma, 'nodes': nodes })
i += 1
return execute_batch_command(name, cs)
-217
View File
@@ -1,217 +0,0 @@
from .database import *
from .s0_base import is_node
from .s32_region_util import to_postgis_polygon
from .s32_region import get_region
def get_service_area_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
'source' : {'type': 'str' , 'optional': False , 'readonly': False },
'time_index' : {'type': 'int' , 'optional': False , 'readonly': False } }
def get_service_area(name: str, id: str) -> dict[str, Any]:
sa = get_region(name, id)
if sa == {}:
return {}
r = try_read(name, f"select * from region_sa where id = '{id}'")
if r == None:
return {}
sa['source'] = r['source']
sa['nodes'] = list(eval(r['nodes']))
sa['time_index'] = r['time_index']
return sa
def _set_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
new_boundary = cs.operations[0]['boundary']
old_boundary = get_region(name, id)['boundary']
new_source = cs.operations[0]['source']
f_new_source = f"'{new_source}'"
new_nodes = cs.operations[0]['nodes']
str_new_nodes = str(new_nodes).replace("'", "''")
new_time_index = cs.operations[0]['time_index']
old = get_service_area(name, id)
old_source = old['source']
f_old_source = f"'{old_source}'"
old_nodes = old['nodes']
str_old_nodes = str(old_nodes).replace("'", "''")
old_time_index = old['time_index']
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
redo_sql += f"update region_sa set time_index = {new_time_index}, source = {f_new_source}, nodes = '{str_new_nodes}' where id = '{id}';"
undo_sql = f"update region_sa set time_index = {old_time_index}, source = {f_old_source}, nodes = '{str_old_nodes}' where id = '{id}';"
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
redo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': new_boundary, 'time_index': new_time_index, 'source': new_source, 'nodes': new_nodes }
undo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': old_boundary, 'time_index': old_time_index, 'source': old_source, 'nodes': old_nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_service_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
sa = get_service_area(name, op['id'])
if sa == {}:
return ChangeSet()
if 'boundary' not in op:
op['boundary'] = sa['boundary']
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'time_index' not in op:
op['time_index'] = sa['time_index']
if 'source' not in op:
op['source'] = sa['source']
if not is_node(name, op['source']):
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = sa['nodes']
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _set_service_area(name, cs))
def _add_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
boundary = cs.operations[0]['boundary']
time_index = cs.operations[0]['time_index']
source = cs.operations[0]['source']
f_source = f"'{source}'"
nodes = cs.operations[0]['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');"
redo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');"
undo_sql = f"delete from region_sa where id = '{id}';"
undo_sql += f"delete from region where id = '{id}';"
redo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes }
undo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_service_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
sa = get_service_area(name, op['id'])
if sa != {}:
return ChangeSet()
if 'boundary' not in op:
return ChangeSet()
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'time_index' not in op:
return ChangeSet()
if 'source' not in op:
return ChangeSet()
if not is_node(name, op['source']):
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = []
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _add_service_area(name, cs))
def _delete_service_area(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
sa = get_service_area(name, id)
boundary = sa['boundary']
time_index = sa['time_index']
source = sa['source']
f_source = f"'{source}'"
nodes = sa['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"delete from region_sa where id = '{id}';"
redo_sql += f"delete from region where id = '{id}';"
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');"
undo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');"
redo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id }
undo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_service_area(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
sa = get_service_area(name, op['id'])
if sa == {}:
return ChangeSet()
return execute_command(name, _delete_service_area(name, cs))
def get_all_service_area_ids(name: str) -> list[str]:
ids = []
for row in read_all(name, f"select id from region_sa"):
ids.append(row['id'])
return ids
def get_all_service_areas(name: str) -> list[dict[str, Any]]:
result = []
for id in get_all_service_area_ids(name):
result.append(get_service_area(name, id))
return result
-210
View File
@@ -1,210 +0,0 @@
import os
import platform
import subprocess
import uuid
from queue import Queue
from typing import Any
from app.infra.epanet.epanet import Output
from .inp_out import dump_inp
from .project import have_project
from .s0_base import get_link_nodes, get_node_links
from .s23_options_util import get_option_v3
def _update_section(lines: list[str], section: str, transform) -> list[str]:
result: list[str] = []
i = 0
while i < len(lines):
line = lines[i]
if line.strip() == f'[{section}]':
result.append(line)
i += 1
section_lines: list[str] = []
while i < len(lines) and not lines[i].startswith('['):
section_lines.append(lines[i])
i += 1
result.extend(transform(section_lines))
continue
result.append(line)
i += 1
return result
def _build_service_area_input(name: str, inp_path: str) -> None:
dump_inp(name, inp_path, '2')
with open(inp_path, encoding='utf-8') as file:
lines = file.read().splitlines()
unbalanced = get_option_v3(name).get('IF_UNBALANCED', '').strip()
if unbalanced != '':
lines = _update_section(
lines,
'OPTIONS',
lambda option_lines: [
f'UNBALANCED {unbalanced}' if line.startswith('UNBALANCED ') else line
for line in option_lines
],
)
with open(inp_path, mode='w', encoding='utf-8') as file:
file.write('\n'.join(lines) + '\n')
def _run_epanet_output(inp_path: str, rpt_path: str, out_path: str) -> dict[str, Any]:
epanet_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'infra', 'epanet'))
if platform.system() == 'Windows':
exe = os.path.join(epanet_dir, 'windows', 'runepanet.exe')
else:
exe = os.path.join(epanet_dir, 'linux', 'runepanet')
if not os.access(exe, os.X_OK):
os.chmod(exe, 0o755)
env = os.environ.copy()
if platform.system() == 'Linux':
lib_dir = os.path.dirname(exe)
env['LD_LIBRARY_PATH'] = f"{lib_dir}:{env.get('LD_LIBRARY_PATH', '')}"
process = subprocess.run([exe, inp_path, rpt_path, out_path], env=env, capture_output=True, text=True)
if process.returncode != 0:
raise RuntimeError(
f'EPANET failed for [{inp_path}] with code {process.returncode}: '
f'stdout={process.stdout} stderr={process.stderr}'
)
return Output(out_path).dump()
def _calculate_service_area(name: str, output: dict[str, Any], time_index: int) -> dict[str, list[str]]:
sources: dict[str, list[str]] = {}
for node_result in output['node_results']:
result = node_result['result'][time_index]
if result['demand'] < 0:
sources[node_result['node']] = []
link_flows: dict[str, float] = {}
for link_result in output['link_results']:
result = link_result['result'][time_index]
link_flows[link_result['link']] = float(result['flow'])
for source in sources:
queue: Queue[str] = Queue()
queue.put(source)
while not queue.empty():
cursor = queue.get()
if cursor not in sources[source]:
sources[source].append(cursor)
links = get_node_links(name, cursor)
for link in links:
node1, node2 = get_link_nodes(name, link)
if node1 == cursor and link_flows[link] > 0:
queue.put(node2)
elif node2 == cursor and link_flows[link] < 0:
queue.put(node1)
concentration_map: dict[str, dict[str, float]] = {}
node_wip: list[str] = []
for source, nodes in sources.items():
for node in nodes:
if node not in concentration_map:
concentration_map[node] = {}
concentration_map[node][source] = 0.0
if node not in node_wip:
node_wip.append(node)
for node, concentrations in concentration_map.items():
if len(concentrations) == 1:
node_wip.remove(node)
for source in concentrations.keys():
concentration_map[node][source] = 1.0
node_upstream: dict[str, list[tuple[str, str]]] = {}
for node in node_wip:
node_upstream[node] = []
links = get_node_links(name, node)
for link in links:
node1, node2 = get_link_nodes(name, link)
if node2 == node and link_flows[link] > 0:
node_upstream[node].append((link, node1))
elif node1 == node and link_flows[link] < 0:
node_upstream[node].append((link, node2))
while len(node_wip) != 0:
done: list[str] = []
for node in node_wip:
up_link_nodes = node_upstream[node]
ready = True
for link_node in up_link_nodes:
if link_node[1] in node_wip:
ready = False
break
if not ready:
continue
for link, upstream_node in up_link_nodes:
if upstream_node not in concentration_map:
continue
for source, concentration in concentration_map[upstream_node].items():
concentration_map[node][source] += concentration * abs(link_flows[link])
total_concentration = sum(concentration_map[node].values())
if total_concentration == 0:
raise RuntimeError(f'Failed to normalize service area concentration for node [{node}] at time [{time_index}]')
for source in concentration_map[node].keys():
concentration_map[node][source] /= total_concentration
done.append(node)
if len(done) == 0:
raise RuntimeError(f'Failed to resolve service area graph for time [{time_index}]')
for node in done:
node_wip.remove(node)
source_to_main_node: dict[str, list[str]] = {}
for node, concentrations in concentration_map.items():
max_source = ''
max_concentration = 0.0
for source, concentration in concentrations.items():
if concentration > max_concentration:
max_concentration = concentration
max_source = source
if max_source not in source_to_main_node:
source_to_main_node[max_source] = []
source_to_main_node[max_source].append(node)
return source_to_main_node
def calculate_service_area(name: str) -> list[dict[str, list[str]]]:
if not have_project(name):
raise Exception(f'Not found project [{name}]')
root = os.path.abspath(os.getcwd())
token = f'{os.getpid()}_{uuid.uuid4().hex}'
inp_path = os.path.join(root, 'db_inp', f'{name}.service_area.{token}.inp')
rpt_path = os.path.join(root, 'temp', f'{name}.service_area.{token}.rpt')
out_path = os.path.join(root, 'temp', f'{name}.service_area.{token}.opt')
os.makedirs(os.path.dirname(inp_path), exist_ok=True)
os.makedirs(os.path.dirname(rpt_path), exist_ok=True)
try:
_build_service_area_input(name, inp_path)
output = _run_epanet_output(inp_path, rpt_path, out_path)
results: list[dict[str, list[str]]] = []
time_count = len(output['node_results'][0]['result'])
for time_index in range(time_count):
results.append(_calculate_service_area(name, output, time_index))
return results
finally:
for path in (inp_path, rpt_path, out_path):
if os.path.exists(path):
os.remove(path)
-23
View File
@@ -1,23 +0,0 @@
from .s32_region_util import calculate_boundary, inflate_boundary
from .s34_sa_cal import *
from .s34_sa import get_all_service_area_ids
from .batch_exe import execute_batch_command
from .database import ChangeSet
def generate_service_area(name: str, inflate_delta: float = 0.5) -> ChangeSet:
cs = ChangeSet()
for id in get_all_service_area_ids(name):
cs.delete({'type': 'service_area', 'id': id})
sass = calculate_service_area(name)
time_index = 0
for sas in sass:
for source, nodes in sas.items():
boundary = calculate_boundary(name, nodes)
boundary = inflate_boundary(name, boundary, inflate_delta)
cs.add({ 'type': 'service_area', 'id': f"SA_{source}_{time_index}", 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes })
time_index += 1
return execute_batch_command(name, cs)
-202
View File
@@ -1,202 +0,0 @@
from .database import *
from .s0_base import is_node
from .s32_region_util import to_postgis_polygon
from .s32_region import get_region
def get_virtual_district_schema(name: str) -> dict[str, dict[str, Any]]:
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False },
'center' : {'type': 'str' , 'optional': False , 'readonly': False } }
def get_virtual_district(name: str, id: str) -> dict[str, Any]:
vd = get_region(name, id)
if vd == {}:
return {}
r = try_read(name, f"select * from region_vd where id = '{id}'")
if r == None:
return {}
vd['center'] = r['center']
vd['nodes'] = list(eval(r['nodes']))
return vd
def _set_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
new_boundary = cs.operations[0]['boundary']
old_boundary = get_region(name, id)['boundary']
new_center = cs.operations[0]['center']
f_new_center = f"'{new_center}'"
new_nodes = cs.operations[0]['nodes']
str_new_nodes = str(new_nodes).replace("'", "''")
old = get_virtual_district(name, id)
old_center = old['center']
f_old_center = f"'{old_center}'"
old_nodes = old['nodes']
str_old_nodes = str(old_nodes).replace("'", "''")
redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';"
redo_sql += f"update region_vd set center = {f_new_center}, nodes = '{str_new_nodes}' where id = '{id}';"
undo_sql = f"update region_vd set center = {f_old_center}, nodes = '{str_old_nodes}' where id = '{id}';"
undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';"
redo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': new_boundary, 'center': new_center, 'nodes': new_nodes }
undo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': old_boundary, 'center': old_center, 'nodes': old_nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
vd = get_virtual_district(name, op['id'])
if vd == {}:
return ChangeSet()
if 'boundary' not in op:
op['boundary'] = vd['boundary']
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'center' not in op:
op['center'] = vd['center']
if not is_node(name, op['center']):
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = vd['nodes']
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _set_virtual_district(name, cs))
def _add_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
boundary = cs.operations[0]['boundary']
center = cs.operations[0]['center']
f_center = f"'{center}'"
nodes = cs.operations[0]['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');"
redo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');"
undo_sql = f"delete from region_vd where id = '{id}';"
undo_sql += f"delete from region where id = '{id}';"
redo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes }
undo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def add_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
vd = get_virtual_district(name, op['id'])
if vd != {}:
return ChangeSet()
if 'boundary' not in op:
return ChangeSet()
else:
b = op['boundary']
if len(b) < 4 or b[0] != b[-1]:
return ChangeSet()
if 'center' not in op:
return ChangeSet()
if not is_node(name, op['center']):
return ChangeSet()
if 'nodes' not in op:
op['nodes'] = []
else:
for node in op['nodes']:
if not is_node(name, node):
return ChangeSet()
return execute_command(name, _add_virtual_district(name, cs))
def _delete_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet:
id = cs.operations[0]['id']
vd = get_virtual_district(name, id)
boundary = vd['boundary']
center = vd['center']
f_center = f"'{center}'"
nodes = vd['nodes']
str_nodes = str(nodes).replace("'", "''")
redo_sql = f"delete from region_vd where id = '{id}';"
redo_sql += f"delete from region where id = '{id}';"
undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');"
undo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');"
redo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id }
undo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes }
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def delete_virtual_district(name: str, cs: ChangeSet) -> ChangeSet:
ops = cs.operations
if len(cs.operations) == 0:
return ChangeSet()
op = ops[0]
if 'id' not in op:
return ChangeSet()
vd = get_virtual_district(name, op['id'])
if vd == {}:
return ChangeSet()
return execute_command(name, _delete_virtual_district(name, cs))
def get_all_virtual_district_ids(name: str) -> list[str]:
ids = []
for row in read_all(name, f"select id from region_vd"):
ids.append(row['id'])
return ids
def get_all_virtual_districts(name: str) -> list[dict[str, Any]]:
result = []
for id in get_all_virtual_district_ids(name):
result.append(get_virtual_district(name, id))
return result
-66
View File
@@ -1,66 +0,0 @@
from .database import *
from .s0_base import get_node_links
def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[Any]]:
write(name, 'delete from temp_vd_topology')
# map node name to index
i = 0
isolated_nodes = []
node_index: dict[str, int] = {}
for row in read_all(name, 'select id from _node'):
node = str(row['id'])
if get_node_links(name, node) == []:
isolated_nodes.append(node)
continue
i += 1
node_index[node] = i
# build topology graph
pipes = read_all(name, 'select node1, node2, length from pipes')
for pipe in pipes:
source = node_index[str(pipe['node1'])]
target = node_index[str(pipe['node2'])]
cost = float(pipe['length'])
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, {cost})")
pumps = read_all(name, 'select node1, node2 from pumps')
for pump in pumps:
source = node_index[str(pump['node1'])]
target = node_index[str(pump['node2'])]
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)")
valves = read_all(name, 'select node1, node2 from valves')
for valve in valves:
source = node_index[str(valve['node1'])]
target = node_index[str(valve['node2'])]
write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)")
# dijkstra distance
node_distance: dict[str, dict[str, Any]] = {}
for center in centers:
for node, index in node_index.items():
if node == center:
node_distance[node] = { 'center': center, 'distance' : 0.0 }
continue
# TODO: check none
distance = float(read(name, f"select max(agg_cost) as distance from pgr_dijkstraCost('select id, source, target, cost from temp_vd_topology', {index}, {node_index[center]}, false)")['distance'])
if node not in node_distance:
node_distance[node] = { 'center': center, 'distance' : distance }
elif distance < node_distance[node]['distance']:
node_distance[node] = { 'center': center, 'distance' : distance }
write(name, 'delete from temp_vd_topology')
# reorganize the distance result
center_node: dict[str, list[str]] = {}
for node, value in node_distance.items():
if value['center'] not in center_node:
center_node[value['center']] = []
center_node[value['center']].append(node)
vds: list[dict[str, Any]] = []
for center, value in center_node.items():
vds.append({ 'center': center, 'nodes': value })
return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes }
-21
View File
@@ -1,21 +0,0 @@
from .s32_region_util import calculate_boundary, inflate_boundary
from .s35_vd_cal import *
from .s35_vd import get_all_virtual_district_ids
from .batch_exe import execute_batch_command
def generate_virtual_district(name: str, centers: list[str], inflate_delta: float = 0.5) -> ChangeSet:
cs = ChangeSet()
for id in get_all_virtual_district_ids(name):
cs.delete({'type': 'virtual_district', 'id': id})
vds = calculate_virtual_district(name, centers)['virtual_districts']
for vd in vds:
center = vd['center']
nodes = vd['nodes']
boundary = calculate_boundary(name, nodes)
boundary = inflate_boundary(name, boundary, inflate_delta)
cs.add({ 'type': 'virtual_district', 'id': f"VD_{center}", 'boundary': boundary, 'center': center, 'nodes': nodes })
return execute_batch_command(name, cs)
View File
-104
View File
@@ -1,104 +0,0 @@
from .database import ChangeSet
from .s0_base import is_junction, get_nodes
from .s9_demands import get_demand
from .s32_region_util import Topology, get_nodes_in_region
from .batch_exe import execute_batch_command
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
-58
View File
@@ -1,58 +0,0 @@
from .database import *
def get_scada_info_schema(name: str) -> dict[str, dict[str, Any]]:
return {
"id": {"type": "str", "optional": False, "readonly": True},
"type": {"type": "str", "optional": False, "readonly": True},
"x": {"type": "float", "optional": False, "readonly": False},
"y": {"type": "float", "optional": False, "readonly": False},
"query_api_id": {"type": "str", "optional": False, "readonly": False},
"transmission_mode": {"type": "str", "optional": True, "readonly": True},
"transmission_frequency": {"type": "float", "optional": True, "readonly": True},
"reliability": {"type": "float", "optional": True, "readonly": True},
"associated_element_id": {"type": "str", "optional": False, "readonly": True},
}
def get_scada_info(name: str, id: str) -> dict[str, Any]:
si = try_read(name, f"select * from scada_info where id = '{id}'")
if si is None:
return {}
d = {}
d["id"] = si["id"]
d["type"] = si["type"]
d["x"] = float(si["x_coor"])
d["y"] = float(si["y_coor"])
d["api_query_id"] = si["api_query_id"]
d["transmission_mode"] = si.get("transmission_mode")
d["transmission_frequency"] = si.get("transmission_frequency")
d["reliability"] = si.get("reliability")
d["associated_element_id"] = si["associated_element_id"]
return d
def get_all_scada_info(name: str) -> list[dict[str, Any]]:
sis = read_all(name, f"select * from scada_info")
if sis is None:
return []
d = []
for si in sis:
d.append(
{
"id": si["id"],
"type": si["type"],
"x": float(si["x_coor"]),
"y": float(si["y_coor"]),
"api_query_id": si["api_query_id"],
"transmission_mode": si.get("transmission_mode"),
"transmission_frequency": si.get("transmission_frequency"),
"reliability": si.get("reliability"),
"associated_element_id": si["associated_element_id"],
}
)
return d
-33
View File
@@ -1,33 +0,0 @@
from .database import *
from .s0_base import *
def get_scheme_schema(name: str) -> dict[str, dict[Any, Any]]:
return {
"id": {"type": "str", "optional": False, "readonly": True},
"name": {"type": "str", "optional": False, "readonly": False},
"type": {"type": "str", "optional": False, "readonly": False},
"create_time": {"type": "str", "optional": False, "readonly": True},
"start_time": {"type": "str", "optional": False, "readonly": True},
"detail": {"type": "str", "optional": False, "readonly": True},
}
def get_scheme(name: str, schema_name: str) -> dict[Any, Any]:
t = try_read(name, f"select * from scheme_list where scheme_name = '{schema_name}'")
if t == None:
return {}
d = {}
d["id"] = str(t["scheme_id"])
d["name"] = str(t["scheme_name"])
d["type"] = str(t["scheme_type"])
d["create_time"] = str(t["create_time"])
d["start_time"] = str(t["start_time"])
d["detail"] = str(t["detail"])
return d
def get_all_schemes(name: str) -> list[dict[Any, Any]]:
return read_all(name, "select * from scheme_list")
@@ -1,92 +0,0 @@
from .database import *
from .connection import project_connection
from .s0_base import *
from psycopg.rows import dict_row
import json
def get_pipe_risk_probability_now(name: str, pipe_id: str) -> dict[str, Any]:
t = try_read(name, f"select * from pipe_risk_probability where pipeid = '{pipe_id}'")
if t == None:
return {}
d = {}
d['pipeid'] = str(t['pipeid'])
d['pipeage'] = t['pipeage']
d['risk_probability_now'] = t['risk_probability_now']
return d
def get_pipe_risk_probability(name: str, pipe_id: str) -> dict[str, Any]:
t = try_read(name, f"select * from pipe_risk_probability where pipeid = '{pipe_id}'")
if t == None:
return {}
d = {}
d['pipeid'] = t['pipeid']
d['x'] = t['x']
d['y'] = t['y']
return d
def get_network_pipe_risk_probability_now(name: str) -> list[dict[str, Any]]:
pipe_risk_probability_list = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from pipe_risk_probability")
for record in cur:
#pipe_risk_probability_list.append(record)
t = {}
t['pipeid'] = record['pipeid']
t['pipeage'] = record['pipeage']
t['risk_probability_now'] = record['risk_probability_now']
pipe_risk_probability_list.append(t)
return pipe_risk_probability_list
def get_pipes_risk_probability(name: str, pipe_ids: list[str]) -> list[dict[str, Any]]:
pipe_risk_probability_list = []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select * from pipe_risk_probability")
for record in cur:
if record['pipeid'] in pipe_ids:
t = {}
t['pipeid'] = record['pipeid']
t['x'] = record['x']
t['y'] = record['y']
pipe_risk_probability_list.append(t)
return pipe_risk_probability_list
def get_pipe_risk_probability_geometries(name: str) -> dict[str, Any]:
'''
获取管道的几何信息
返回一个字典,key 是管道的 id,value 是管道的几何信息
几何信息是一个字典,包含 start 和 end 两个 key,value 是管道的起点和终点的坐标
'''
pipe_risk_probability_geometries = {}
key_pipeId = '编码'
# key_startnode = '上游节点'
# key_endnode = '下游节点'
key_geometry = 'geometry'
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(f"select *, ST_AsGeoJSON(geometry) AS {key_geometry} from gis_pipe")
for record in cur:
id = record[key_pipeId]
geom = json.loads(record[key_geometry])
pipe_risk_probability_geometries[id] = {
'points': geom['coordinates']
}
for col in record:
if col != key_geometry:
pipe_risk_probability_geometries[id][col] = record[col]
# print(len(pipe_risk_probability_geometries))
return pipe_risk_probability_geometries
-124
View File
@@ -1,124 +0,0 @@
from typing import Any
from psycopg.rows import dict_row
from .connection import project_connection
from .database import read_all
def get_all_sensor_placements(name: str) -> list[dict[str, Any]]:
return read_all(name, "select * from sensor_placement")
def create_sensor_placement(
name: str,
*,
scheme_name: str,
min_diameter: int,
username: str,
sensor_location: list[str],
) -> dict[str, Any]:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
INSERT INTO sensor_placement (
scheme_name,
sensor_number,
min_diameter,
username,
sensor_location
)
VALUES (%s, %s, %s, %s, %s)
RETURNING *
""",
(
scheme_name,
len(sensor_location),
min_diameter,
username,
sensor_location,
),
)
created = cur.fetchone()
if created is None:
raise RuntimeError("监测点方案写入失败")
return dict(created)
def get_sensor_placement(name: str, scheme_id: int) -> dict[str, Any] | None:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(
"SELECT * FROM sensor_placement WHERE id = %s",
(scheme_id,),
)
return cur.fetchone()
def get_sensor_placement_nodes(
name: str,
node_ids: list[str],
) -> list[dict[str, Any]]:
if not node_ids:
return []
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
WITH incident_pipe_diameters AS (
SELECT node_id, MAX(diameter) AS max_pipe_diameter
FROM (
SELECT node1 AS node_id, diameter
FROM pipes
WHERE node1 = ANY(%s)
UNION ALL
SELECT node2 AS node_id, diameter
FROM pipes
WHERE node2 = ANY(%s)
) AS incident_pipes
GROUP BY node_id
)
SELECT DISTINCT ON (gj.id)
gj.id AS node_id,
ipd.max_pipe_diameter,
gj.elevation,
ST_X(c.coord) AS project_x,
ST_Y(c.coord) AS project_y,
ST_X(gj.geom) AS map_x,
ST_Y(gj.geom) AS map_y
FROM geo_junctions_mat AS gj
JOIN coordinates AS c ON c.node = gj.id
LEFT JOIN incident_pipe_diameters AS ipd ON ipd.node_id = gj.id
WHERE gj.id = ANY(%s)
ORDER BY gj.id
""",
(node_ids, node_ids, node_ids),
)
return list(cur.fetchall())
def update_sensor_placement(
name: str,
scheme_id: int,
*,
expected_sensor_location: list[str],
sensor_location: list[str],
) -> dict[str, Any] | None:
with project_connection(name) as conn:
with conn.cursor(row_factory=dict_row) as cur:
cur.execute(
"""
UPDATE sensor_placement
SET sensor_location = %s, sensor_number = %s
WHERE id = %s AND sensor_location = %s
RETURNING *
""",
(
sensor_location,
len(sensor_location),
scheme_id,
expected_sensor_location,
),
)
return cur.fetchone()
@@ -1,6 +0,0 @@
from .database import *
from .s0_base import *
import json
def get_all_burst_locate_results(name: str) -> list[dict[Any, Any]]:
return read_all(name, "select * from burst_locate_result")
-142
View File
@@ -1,142 +0,0 @@
from typing import Any
from .database import ChangeSet, execute_command, try_read, read_all, DbChangeSet, g_update_prefix
TAG_TYPE_NODE = 'NODE'
TAG_TYPE_LINK = 'LINK'
def get_tag_schema(name: str) -> dict[str, dict[str, Any]]:
return { 't_type' : {'type': 'str' , 'optional': False , 'readonly': False},
'id' : {'type': 'str' , 'optional': False , 'readonly': False},
'tag' : {'type': 'str' , 'optional': True , 'readonly': False},}
def get_tags(name: str) -> list[dict[str, Any]]:
results: list[dict[str, Any]] = []
rows = read_all(name, "select * from tags_node")
for row in rows:
tag = str(row['tag']) if row['tag'] != None else None
results.append({ 't_type': TAG_TYPE_NODE, 'id': str(row['id']), 'tag': tag })
rows = read_all(name, "select * from tags_link")
for row in rows:
tag = str(row['tag']) if row['tag'] != None else None
results.append({ 't_type': TAG_TYPE_LINK, 'id': str(row['id']), 'tag': tag })
return results
def get_tag(name: str, t_type: str, id: str) -> dict[str, Any]:
t = None
if t_type == TAG_TYPE_NODE:
t = try_read(name, f"select * from tags_node where id = '{id}'")
elif t_type == TAG_TYPE_LINK:
t = try_read(name, f"select * from tags_link where id = '{id}'")
if t is None:
return { 't_type': t_type, 'id': id, 'tag': None }
d = {}
d['t_type'] = t_type
d['id'] = str(t['id'])
d['tag'] = str(t['tag']) if t['tag'] is not None else None
return d
class Tag(object):
def __init__(self, input: dict[str, Any]) -> None:
self.type = 'tag'
self.t_type = str(input['t_type'])
self.id = str(input['id'])
self.tag = str(input['tag']) if 'tag' in input and input['tag'] != None else None
self.f_type = f"'{self.type}'"
self.f_t_type = f"'{self.t_type}'"
self.f_id = f"'{self.id}'"
self.f_tag = f"'{self.tag}'" if self.tag != None else 'null'
def as_dict(self) -> dict[str, Any]:
return { 'type': self.type, 't_type': self.t_type, 'id': self.id, 'tag': self.tag }
def _set_tag(name: str, cs: ChangeSet) -> DbChangeSet:
old = Tag(get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id']))
raw_new = get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id'])
new_dict = cs.operations[0]
schema = get_tag_schema(name)
for key, value in schema.items():
if key in new_dict and not value['readonly']:
raw_new[key] = new_dict[key]
new = Tag(raw_new)
table = ''
if cs.operations[0]['t_type'] == TAG_TYPE_NODE:
table = 'tags_node'
elif cs.operations[0]['t_type'] == TAG_TYPE_LINK:
table = 'tags_link'
else:
raise Exception('Only support NODE and Link')
redo_sql = f"delete from {table} where id = {new.f_id};"
if new.tag is not None:
redo_sql += f"\ninsert into {table} (id, tag) values ({new.f_id}, {new.f_tag});"
undo_sql = f"delete from {table} where id = {old.f_id};"
if old.tag is not None:
undo_sql += f"\ninsert into {table} (id, tag) values ({old.f_id}, {old.f_tag});"
redo_cs = g_update_prefix | new.as_dict()
undo_cs = g_update_prefix | old.as_dict()
return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs])
def set_tag(name: str, cs: ChangeSet) -> ChangeSet:
if 't_type' not in cs.operations[0] or 'id' not in cs.operations[0] or 'tag' not in cs.operations[0]:
return ChangeSet()
return execute_command(name, _set_tag(name, cs))
def inp_in_tag(line: str) -> str:
tokens = line.split()
num = len(tokens)
has_desc = tokens[-1].startswith(';')
num_without_desc = (num - 1) if has_desc else num
t_type = str(tokens[0].upper())
id = str(tokens[1])
tag = str(tokens[2])
if t_type == TAG_TYPE_NODE:
return str(f"insert into tags_node (id, tag) values ('{id}', '{tag}');")
elif t_type == TAG_TYPE_LINK:
return str(f"insert into tags_link (id, tag) values ('{id}', '{tag}');")
return str('')
def inp_out_tag(name: str) -> list[str]:
lines = []
objs = read_all(name, 'select * from tags_node')
for obj in objs:
t_type = TAG_TYPE_NODE
id = obj['id']
tag = obj['tag']
lines.append(f'{t_type} {id} {tag}')
objs = read_all(name, 'select * from tags_link')
for obj in objs:
t_type = TAG_TYPE_LINK
id = obj['id']
tag = obj['tag']
lines.append(f'{t_type} {id} {tag}')
return lines
def delete_tag_by_node(name: str, node: str) -> ChangeSet:
row = try_read(name, f"select * from tags_node where id = '{node}'")
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'tag', 't_type': TAG_TYPE_NODE, 'id': node, 'tag': None })
def delete_tag_by_link(name: str, link: str) -> ChangeSet:
row = try_read(name, f"select * from tags_link where id = '{link}'")
if row is None:
return ChangeSet()
return ChangeSet(g_update_prefix | {'type': 'tag', 't_type': TAG_TYPE_LINK, 'id': link, 'tag': None })