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:
+4
-478
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
"""Model command rewriting, cascade handling, and execution."""
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
"""WNDB connection, transaction, and project lifecycle infrastructure."""
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -0,0 +1 @@
|
||||
"""GIS persistence and network geometry operations."""
|
||||
@@ -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']))
|
||||
@@ -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})
|
||||
@@ -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]
|
||||
@@ -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']:
|
||||
@@ -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})
|
||||
@@ -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})
|
||||
|
||||
@@ -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})
|
||||
@@ -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
|
||||
@@ -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'''
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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')
|
||||
@@ -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))
|
||||
@@ -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'
|
||||
# );
|
||||
#
|
||||
# 返回list,list里每个item是dict,内容是 'id':'abc' 这样
|
||||
# scada_model type 是类似pressure,flow之类的,是由Device 决定 的
|
||||
def get_all_scada_elements(name: str) -> list[dict[str, Any]]:
|
||||
return read_all(name, 'select * from scada_element order by id')
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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 })
|
||||
Reference in New Issue
Block a user