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:
@@ -0,0 +1 @@
|
||||
"""Water-network model persistence grouped by domain entity."""
|
||||
@@ -0,0 +1,54 @@
|
||||
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]]:
|
||||
return { 'controls' : {'type': 'str_list' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_control(name: str) -> dict[str, Any]:
|
||||
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) -> 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)});"
|
||||
|
||||
change = g_update_prefix | { 'type': 'control', 'controls': cs.operations[0]['controls'] }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_control(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_control(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# LINK linkID setting IF NODE nodeID {BELOW/ABOVE} level
|
||||
# LINK linkID setting AT TIME value (units)
|
||||
# LINK linkID setting AT CLOCKTIME value (units)
|
||||
# (0) (1) (2) (3) (4) (5) (6) (7)
|
||||
# todo...
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_control(line: str) -> str:
|
||||
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]:
|
||||
return get_control(name)['controls']
|
||||
@@ -0,0 +1,174 @@
|
||||
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'
|
||||
CURVE_TYPE_VOLUME = 'VOLUME'
|
||||
CURVE_TYPE_HEADLOSS = 'HEADLOSS'
|
||||
|
||||
curve_types = [CURVE_TYPE_PUMP, CURVE_TYPE_EFFICIENCY, CURVE_TYPE_VOLUME, CURVE_TYPE_HEADLOSS]
|
||||
|
||||
def get_curve_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'c_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'coords' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'x' : {'type': 'float' , 'optional': False , 'readonly': False },
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False } }}}
|
||||
|
||||
|
||||
def get_curve(name: str, id: str) -> dict[str, Any]:
|
||||
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, "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['curve_type']
|
||||
d['coords'] = cs
|
||||
return d
|
||||
|
||||
|
||||
def _set_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
old = get_curve(name, id)
|
||||
|
||||
new = { 'id': id }
|
||||
if 'coords' in cs.operations[0]:
|
||||
new['coords'] = cs.operations[0]['coords']
|
||||
else:
|
||||
new['coords'] = old['coords']
|
||||
if 'c_type' in cs.operations[0]:
|
||||
new['c_type'] = cs.operations[0]['c_type']
|
||||
else:
|
||||
new['c_type'] = old['c_type']
|
||||
new_f_type = sql_literal(new['c_type'])
|
||||
|
||||
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});"
|
||||
|
||||
change = g_update_prefix | { 'type': 'curve' } | new
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_curve(name, cs))
|
||||
|
||||
|
||||
def _add_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
new = { 'id': id, 'c_type': cs.operations[0]['c_type'], 'coords': [] }
|
||||
new_f_type = sql_literal(new['c_type'])
|
||||
|
||||
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 = 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 })
|
||||
|
||||
change = g_add_prefix | { 'type': 'curve' } | new
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_curve(name, cs))
|
||||
|
||||
|
||||
def _delete_curve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
statement = f"delete from network.curves where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | { 'type': 'curve' } | { 'id' : id }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_curve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_curve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_curve(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# ;type: desc
|
||||
# id x y
|
||||
#--------------------------------------------------------------
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# id type
|
||||
# id x y
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_curve(line: str) -> str:
|
||||
tokens = line.split()
|
||||
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, "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, "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']
|
||||
y = obj['y']
|
||||
lines.append(f'{id} {x} {y}')
|
||||
return lines
|
||||
|
||||
|
||||
def inp_out_curve_v3(name: str) -> list[str]:
|
||||
lines = []
|
||||
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, "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']
|
||||
y = obj['y']
|
||||
lines.append(f'{id} {x} {y}')
|
||||
return lines
|
||||
@@ -0,0 +1,109 @@
|
||||
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]]:
|
||||
return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'demands' : {'type': 'list' , 'optional': False , 'readonly': False,
|
||||
'element': { 'demand' : {'type': 'float' , 'optional': False , 'readonly': False },
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False },
|
||||
'category': {'type': 'str' , 'optional': True , 'readonly': False }}}}
|
||||
|
||||
|
||||
def get_demand(name: str, junction: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['demand'] = float(r['demand'])
|
||||
d['pattern'] = str(r['pattern']) if r['pattern'] != None else None
|
||||
d['category'] = str(r['category']) if r['category'] != None else None
|
||||
ds.append(d)
|
||||
return { 'junction': junction, 'demands': ds }
|
||||
|
||||
|
||||
def _set_demand(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
junction = cs.operations[0]['junction']
|
||||
new = { 'junction': junction, 'demands': [] }
|
||||
|
||||
f_junction = sql_literal(junction)
|
||||
|
||||
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 = 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 })
|
||||
|
||||
change = g_update_prefix | { 'type': 'demand' } | new
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_demand(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_demand(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node base_demand (pattern) ;category
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_demand(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
junction = str(tokens[0])
|
||||
demand = float(tokens[1])
|
||||
pattern = str(tokens[2]) if num_without_desc >= 3 else None
|
||||
category = str(tokens[3]) if num_without_desc >= 4 else None
|
||||
|
||||
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 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']
|
||||
pattern = obj['pattern'] if obj['pattern'] is not None else ''
|
||||
category = f";{obj['category']}" if obj['category'] is not None else ';'
|
||||
lines.append(f'{junction} {demand} {pattern} {category}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_demand_by_junction(name: str, junction: str) -> ChangeSet:
|
||||
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': []})
|
||||
|
||||
|
||||
def unset_demand_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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']:
|
||||
d['pattern'] = None
|
||||
cs.append(g_update_prefix | {'type': 'demand', 'junction': row['junction'], 'demands': ds['demands']})
|
||||
|
||||
return cs
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,102 @@
|
||||
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]]:
|
||||
return { 'junction' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'coefficient' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_emitter(name: str, junction: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['junction'] = str(e['junction'])
|
||||
d['coefficient'] = float(e['coefficient']) if e['coefficient'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Emitter(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'emitter'
|
||||
self.junction = str(input['junction'])
|
||||
self.coefficient = float(input['coefficient']) if 'coefficient' in input and input['coefficient'] != None else None
|
||||
|
||||
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) -> DatabaseCommand:
|
||||
raw_new = get_emitter(name, cs.operations[0]['junction'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_emitter_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Emitter(raw_new)
|
||||
|
||||
statement = f"delete from network.emitters where junction_id = {new.f_junction};"
|
||||
if new.coefficient != None:
|
||||
statement += f"\ninsert into network.emitters (junction_id, coefficient) values ({new.f_junction}, {new.f_coefficient});"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_emitter(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_emitter(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# node Ke
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# node Ke (exponent pattern)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_emitter(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
junction = str(tokens[0])
|
||||
coefficient = float(tokens[1])
|
||||
|
||||
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 junction_id as junction, coefficient from network.emitters order by junction_id')
|
||||
for obj in objs:
|
||||
junction = obj['junction']
|
||||
coefficient = obj['coefficient']
|
||||
lines.append(f'{junction} {coefficient}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_emitter_by_junction(name: str, junction: str) -> ChangeSet:
|
||||
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
|
||||
@@ -0,0 +1,206 @@
|
||||
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]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'elevation' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_junction(name: str, id: str) -> dict[str, Any]:
|
||||
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 {}
|
||||
d = {}
|
||||
d['id'] = str(j['id'])
|
||||
d['x'] = float(j['x'] or 0.0)
|
||||
d['y'] = float(j['y'] or 0.0)
|
||||
d['elevation'] = float(j['elevation'])
|
||||
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,
|
||||
"""
|
||||
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'])
|
||||
d['id'] = id
|
||||
d['x'] = float(row['x'] or 0.0)
|
||||
d['y'] = float(row['y'] or 0.0)
|
||||
d['elevation'] = float(row['elevation'])
|
||||
d['links'] = links_by_node.get(id, [])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Junction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'junction'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.elevation = float(input['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 _set_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_junction(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_junction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Junction(raw_new)
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_junction(name, cs))
|
||||
|
||||
|
||||
def _add_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Junction(cs.operations[0])
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_junction(name, cs))
|
||||
|
||||
|
||||
def _delete_junction(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = sql_delete_coord(element_id)
|
||||
statement += f"\ndelete from network.nodes where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'junction', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_junction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_junction(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_junction(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# [IN]
|
||||
# id elev. (demand) (demand pattern) ;desc
|
||||
# [OUT]
|
||||
# id elev. ;desc
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3]
|
||||
# [IN]
|
||||
# id elev. (demand) (demand pattern)
|
||||
# [OUT]
|
||||
# id elev. * * minpressure fullpressure
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_junction(line: str, demand_outside: bool) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
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
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
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 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 node_id as id, elevation from network.junctions order by node_id')
|
||||
for obj in objs:
|
||||
id = obj['id']
|
||||
elev = obj['elevation']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {elev} {desc}')
|
||||
return lines
|
||||
@@ -0,0 +1,149 @@
|
||||
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'
|
||||
MIXING_MODEL_FIFO = 'FIFO'
|
||||
MIXING_MODEL_LIFO = 'LIFO'
|
||||
|
||||
def get_mixing_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'tank' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'model' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'value' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_mixing(name: str, tank: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['tank'] = str(m['tank'])
|
||||
d['model'] = str(m['model'])
|
||||
d['value'] = float(m['value']) if m['value'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Mixing(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'mixing'
|
||||
self.tank = str(input['tank'])
|
||||
self.model = str(input['model'])
|
||||
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
|
||||
|
||||
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 _set_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_mixing(name, cs.operations[0]['tank'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_mixing_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Mixing(raw_new)
|
||||
|
||||
statement = f"update network.tank_mixing set model = {new.f_model}, value = {new.f_value} where tank_id = {new.f_tank};"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_mixing(name, cs))
|
||||
|
||||
|
||||
def _add_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Mixing(cs.operations[0])
|
||||
|
||||
statement = f"insert into network.tank_mixing (tank_id, model, value) values ({new.f_tank}, {new.f_model}, {new.f_value});"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_mixing(name, cs))
|
||||
|
||||
|
||||
def _delete_mixing(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
tank = str(cs.operations[0]['tank'])
|
||||
f_tank = sql_literal(tank)
|
||||
|
||||
statement = f"delete from network.tank_mixing where tank_id = {f_tank};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'mixing', 'tank': tank}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_mixing(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'tank' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_mixing(name, cs.operations[0]['tank']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_mixing(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# TankID MixModel FractVolume
|
||||
# FractVolume if type == MIX2
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_mixing(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
tank = str(tokens[0])
|
||||
model = str(tokens[1].upper())
|
||||
value = float(tokens[3]) if num_without_desc >= 4 else None
|
||||
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 tank_id as tank, model, value from network.tank_mixing order by tank_id')
|
||||
for obj in objs:
|
||||
tank = obj['tank']
|
||||
model = obj['model']
|
||||
value = obj['value'] if obj['value'] != None else ''
|
||||
lines.append(f'{tank} {model} {value}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_mixing_by_tank(name: str, tank: str) -> ChangeSet:
|
||||
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})
|
||||
@@ -0,0 +1,383 @@
|
||||
from typing import Any
|
||||
|
||||
from ..core.database import (
|
||||
ChangeSet,
|
||||
DatabaseCommand,
|
||||
execute_command,
|
||||
g_update_prefix,
|
||||
read_all,
|
||||
sql_literal,
|
||||
)
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPANET2][IN][OUT]
|
||||
# UNITS CFS/GPM/MGD/IMGD/AFD/LPS/LPM/MLD/CMH/CMD/SI
|
||||
# PRESSURE PSI/KPA/M
|
||||
# HEADLOSS H-W/D-W/C-M
|
||||
# QUALITY NONE/AGE/TRACE/CHEMICAL (TraceNode)
|
||||
# UNBALANCED STOP/CONTINUE {Niter}
|
||||
# PATTERN id
|
||||
# DEMAND MODEL DDA/PDA
|
||||
# DEMAND MULTIPLIER value
|
||||
# EMITTER EXPONENT value
|
||||
# VISCOSITY value
|
||||
# DIFFUSIVITY value
|
||||
# SPECIFIC GRAVITY value
|
||||
# TRIALS value
|
||||
# ACCURACY value#
|
||||
# HEADERROR value
|
||||
# FLOWCHANGE value
|
||||
# MINIMUM PRESSURE value
|
||||
# REQUIRED PRESSURE value
|
||||
# PRESSURE EXPONENT value#
|
||||
# TOLERANCE value
|
||||
# HTOL value
|
||||
# QTOL value
|
||||
# RQTOL value
|
||||
# CHECKFREQ value
|
||||
# MAXCHECK value
|
||||
# DAMPLIMIT value
|
||||
# ---- Unsupported Options -----
|
||||
# HYDRAULICS USE/SAVE filename
|
||||
# MAP filename
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
element_schema = {'type': 'str' , 'optional': True , 'readonly': False}
|
||||
|
||||
|
||||
OPTION_UNITS_CFS = 'CFS'
|
||||
OPTION_UNITS_GPM = 'GPM'
|
||||
OPTION_UNITS_MGD = 'MGD'
|
||||
OPTION_UNITS_IMGD = 'IMGD'
|
||||
OPTION_UNITS_AFD = 'AFD'
|
||||
OPTION_UNITS_LPS = 'LPS'
|
||||
OPTION_UNITS_LPM = 'LPM'
|
||||
OPTION_UNITS_MLD = 'MLD'
|
||||
OPTION_UNITS_CMH = 'CMH'
|
||||
OPTION_UNITS_CMD = 'CMD'
|
||||
|
||||
OPTION_PRESSURE_PSI = 'PSI'
|
||||
OPTION_PRESSURE_KPA = 'KPA'
|
||||
OPTION_PRESSURE_METERS = 'METERS'
|
||||
|
||||
OPTION_HEADLOSS_HW = 'H-W'
|
||||
OPTION_HEADLOSS_DW = 'D-W'
|
||||
OPTION_HEADLOSS_CM = 'C-M'
|
||||
|
||||
OPTION_UNBALANCED_STOP = 'STOP'
|
||||
OPTION_UNBALANCED_CONTINUE = 'CONTINUE'
|
||||
|
||||
OPTION_DEMAND_MODEL_DDA = 'DDA'
|
||||
OPTION_DEMAND_MODEL_PDA = 'PDA'
|
||||
|
||||
OPTION_QUALITY_NONE = 'NONE'
|
||||
OPTION_QUALITY_CHEMICAL = 'CHEMICAL'
|
||||
OPTION_QUALITY_AGE = 'AGE'
|
||||
OPTION_QUALITY_TRACE = 'TRACE'
|
||||
|
||||
|
||||
def get_option_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'UNITS' : element_schema,
|
||||
'PRESSURE' : element_schema,
|
||||
'HEADLOSS' : element_schema,
|
||||
'QUALITY' : element_schema,
|
||||
'UNBALANCED' : element_schema,
|
||||
'PATTERN' : element_schema,
|
||||
'DEMAND MODEL' : element_schema,
|
||||
'DEMAND MULTIPLIER' : element_schema,
|
||||
'EMITTER EXPONENT' : element_schema,
|
||||
'VISCOSITY' : element_schema,
|
||||
'DIFFUSIVITY' : element_schema,
|
||||
'SPECIFIC GRAVITY' : element_schema,
|
||||
'TRIALS' : element_schema,
|
||||
'ACCURACY' : element_schema,
|
||||
'HEADERROR' : element_schema,
|
||||
'FLOWCHANGE' : element_schema,
|
||||
'MINIMUM PRESSURE' : element_schema,
|
||||
'REQUIRED PRESSURE' : element_schema,
|
||||
'PRESSURE EXPONENT' : element_schema,
|
||||
'TOLERANCE' : element_schema,
|
||||
'HTOL' : element_schema,
|
||||
'QTOL' : element_schema,
|
||||
'RQTOL' : element_schema,
|
||||
'CHECKFREQ' : element_schema,
|
||||
'MAXCHECK' : element_schema,
|
||||
'DAMPLIMIT' : element_schema }
|
||||
|
||||
|
||||
def get_option(name: str) -> dict[str, Any]:
|
||||
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) -> DatabaseCommand:
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_option_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
change = g_update_prefix | { 'type' : 'option' }
|
||||
|
||||
statement = ''
|
||||
for key, value in new.items():
|
||||
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 }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_option(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_option(name, cs))
|
||||
|
||||
|
||||
OPTION_V3_FLOW_UNITS_CFS = OPTION_UNITS_CFS
|
||||
OPTION_V3_FLOW_UNITS_GPM = OPTION_UNITS_GPM
|
||||
OPTION_V3_FLOW_UNITS_MGD = OPTION_UNITS_MGD
|
||||
OPTION_V3_FLOW_UNITS_IMGD = OPTION_UNITS_IMGD
|
||||
OPTION_V3_FLOW_UNITS_AFD = OPTION_UNITS_AFD
|
||||
OPTION_V3_FLOW_UNITS_LPS = OPTION_UNITS_LPS
|
||||
OPTION_V3_FLOW_UNITS_LPM = OPTION_UNITS_LPM
|
||||
OPTION_V3_FLOW_UNITS_MLD = OPTION_UNITS_MLD
|
||||
OPTION_V3_FLOW_UNITS_CMH = OPTION_UNITS_CMH
|
||||
OPTION_V3_FLOW_UNITS_CMD = OPTION_UNITS_CMD
|
||||
|
||||
OPTION_V3_PRESSURE_UNITS_PSI = OPTION_PRESSURE_PSI
|
||||
OPTION_V3_PRESSURE_UNITS_KPA = OPTION_PRESSURE_KPA
|
||||
OPTION_V3_PRESSURE_UNITS_METERS = OPTION_PRESSURE_METERS
|
||||
|
||||
OPTION_V3_HEADLOSS_MODEL_HW = OPTION_HEADLOSS_HW
|
||||
OPTION_V3_HEADLOSS_MODEL_DW = OPTION_HEADLOSS_DW
|
||||
OPTION_V3_HEADLOSS_MODEL_CM = OPTION_HEADLOSS_CM
|
||||
|
||||
OPTION_V3_STEP_SIZING_FULL = 'FULL'
|
||||
OPTION_V3_STEP_SIZING_RELAXATION = 'RELAXATION'
|
||||
OPTION_V3_STEP_SIZING_LINESEARCH = 'LINESEARCH'
|
||||
|
||||
OPTION_V3_IF_UNBALANCED_STOP = OPTION_UNBALANCED_STOP
|
||||
OPTION_V3_IF_UNBALANCED_CONTINUE = OPTION_UNBALANCED_CONTINUE
|
||||
|
||||
OPTION_V3_DEMAND_MODEL_FIXED = 'FIXED'
|
||||
OPTION_V3_DEMAND_MODEL_CONSTRAINED = 'CONSTRAINED'
|
||||
OPTION_V3_DEMAND_MODEL_POWER = 'POWER'
|
||||
OPTION_V3_DEMAND_MODEL_LOGISTIC = 'LOGISTIC'
|
||||
|
||||
OPTION_V3_LEAKAGE_MODEL_NONE = 'NONE'
|
||||
OPTION_V3_LEAKAGE_MODEL_POWER = 'POWER'
|
||||
OPTION_V3_LEAKAGE_MODEL_FAVAD = 'FAVAD'
|
||||
|
||||
OPTION_V3_QUALITY_MODEL_NONE = OPTION_QUALITY_NONE
|
||||
OPTION_V3_QUALITY_MODEL_CHEMICAL = OPTION_QUALITY_CHEMICAL
|
||||
OPTION_V3_QUALITY_MODEL_AGE = OPTION_QUALITY_AGE
|
||||
OPTION_V3_QUALITY_MODEL_TRACE = OPTION_QUALITY_TRACE
|
||||
|
||||
OPTION_V3_QUALITY_UNITS_HRS = 'HRS'
|
||||
OPTION_V3_QUALITY_UNITS_PCNT = 'PCNT'
|
||||
OPTION_V3_QUALITY_UNITS_MGL = 'MG/L'
|
||||
OPTION_V3_QUALITY_UNITS_UGL = 'UG/L'
|
||||
|
||||
|
||||
def get_option_v3_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'FLOW_UNITS' : element_schema,
|
||||
'PRESSURE_UNITS' : element_schema,
|
||||
'HEADLOSS_MODEL' : element_schema,
|
||||
'SPECIFIC_GRAVITY' : element_schema,
|
||||
'SPECIFIC_VISCOSITY' : element_schema,
|
||||
'MAXIMUM_TRIALS' : element_schema,
|
||||
'HEAD_TOLERANCE' : element_schema,
|
||||
'FLOW_TOLERANCE' : element_schema,
|
||||
'FLOW_CHANGE_LIMIT' : element_schema,
|
||||
'RELATIVE_ACCURACY' : element_schema,
|
||||
'TIME_WEIGHT' : element_schema,
|
||||
'STEP_SIZING' : element_schema,
|
||||
'IF_UNBALANCED' : element_schema,
|
||||
'DEMAND_MODEL' : element_schema,
|
||||
'DEMAND_PATTERN' : element_schema,
|
||||
'DEMAND_MULTIPLIER' : element_schema,
|
||||
'MINIMUM_PRESSURE' : element_schema,
|
||||
'SERVICE_PRESSURE' : element_schema,
|
||||
'PRESSURE_EXPONENT' : element_schema,
|
||||
'LEAKAGE_MODEL' : element_schema,
|
||||
'LEAKAGE_COEFF1' : element_schema,
|
||||
'LEAKAGE_COEFF2' : element_schema,
|
||||
'EMITTER_EXPONENT' : element_schema,
|
||||
'QUALITY_MODEL' : element_schema,
|
||||
'QUALITY_NAME' : element_schema,
|
||||
'QUALITY_UNITS' : element_schema,
|
||||
'TRACE_NODE' : element_schema,
|
||||
'SPECIFIC_DIFFUSIVITY' : element_schema,
|
||||
'QUALITY_TOLERANCE' : element_schema }
|
||||
|
||||
|
||||
def get_option_v3(name: str) -> dict[str, Any]:
|
||||
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) -> DatabaseCommand:
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_option_v3_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
change = g_update_prefix | { 'type' : 'option_v3' }
|
||||
|
||||
statement = ''
|
||||
for key, value in new.items():
|
||||
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 }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_option_v3(name, cs))
|
||||
|
||||
|
||||
_key_map_23 = {
|
||||
'UNITS' : 'FLOW_UNITS',
|
||||
'PRESSURE' : 'PRESSURE_UNITS',
|
||||
'HEADLOSS' : 'HEADLOSS_MODEL',
|
||||
'QUALITY' : 'QUALITY_MODEL',
|
||||
'UNBALANCED' : 'IF_UNBALANCED',
|
||||
'PATTERN' : 'DEMAND_PATTERN',
|
||||
'DEMAND MODEL' : 'DEMAND_MODEL',
|
||||
'DEMAND MULTIPLIER' : 'DEMAND_MULTIPLIER',
|
||||
'EMITTER EXPONENT' : 'EMITTER_EXPONENT',
|
||||
'VISCOSITY' : 'SPECIFIC_VISCOSITY',
|
||||
'DIFFUSIVITY' : 'SPECIFIC_DIFFUSIVITY',
|
||||
'SPECIFIC GRAVITY' : 'SPECIFIC_GRAVITY',
|
||||
'TRIALS' : 'MAXIMUM_TRIALS',
|
||||
'ACCURACY' : 'RELATIVE_ACCURACY',
|
||||
#'HEADERROR' : '',
|
||||
'FLOWCHANGE' : 'FLOW_CHANGE_LIMIT',
|
||||
'MINIMUM PRESSURE' : 'MINIMUM_PRESSURE',
|
||||
'REQUIRED PRESSURE' : 'SERVICE_PRESSURE',
|
||||
'PRESSURE EXPONENT' : 'PRESSURE_EXPONENT',
|
||||
'TOLERANCE' : 'QUALITY_TOLERANCE',
|
||||
'HTOL' : 'HEAD_TOLERANCE',
|
||||
'QTOL' : 'FLOW_TOLERANCE',
|
||||
#'RQTOL' : '',
|
||||
#'CHECKFREQ' : '',
|
||||
#'MAXCHECK' : '',
|
||||
#'DAMPLIMIT' : '',
|
||||
}
|
||||
|
||||
|
||||
_key_map_32 = {
|
||||
'FLOW_UNITS' : 'UNITS',
|
||||
'PRESSURE_UNITS' : 'PRESSURE',
|
||||
'HEADLOSS_MODEL' : 'HEADLOSS',
|
||||
'SPECIFIC_GRAVITY' : 'SPECIFIC GRAVITY',
|
||||
'SPECIFIC_VISCOSITY' : 'VISCOSITY',
|
||||
'MAXIMUM_TRIALS' : 'TRIALS',
|
||||
'HEAD_TOLERANCE' : 'HTOL',
|
||||
'FLOW_TOLERANCE' : 'QTOL',
|
||||
'FLOW_CHANGE_LIMIT' : 'FLOWCHANGE',
|
||||
'RELATIVE_ACCURACY' : 'ACCURACY',
|
||||
#'TIME_WEIGHT' : '',
|
||||
#'STEP_SIZING' : '',
|
||||
'IF_UNBALANCED' : 'UNBALANCED',
|
||||
'DEMAND_MODEL' : 'DEMAND MODEL',
|
||||
'DEMAND_PATTERN' : 'PATTERN',
|
||||
'DEMAND_MULTIPLIER' : 'DEMAND MULTIPLIER',
|
||||
'MINIMUM_PRESSURE' : 'MINIMUM PRESSURE',
|
||||
'SERVICE_PRESSURE' : 'REQUIRED PRESSURE',
|
||||
'PRESSURE_EXPONENT' : 'PRESSURE EXPONENT',
|
||||
#'LEAKAGE_MODEL' : '',
|
||||
#'LEAKAGE_COEFF1' : '',
|
||||
#'LEAKAGE_COEFF2' : '',
|
||||
'EMITTER_EXPONENT' : 'EMITTER EXPONENT',
|
||||
'QUALITY_MODEL' : 'QUALITY',
|
||||
#'QUALITY_NAME' : '',
|
||||
#'QUALITY_UNITS' : '',
|
||||
#'TRACE_NODE' : '',
|
||||
'SPECIFIC_DIFFUSIVITY' : 'DIFFUSIVITY',
|
||||
'QUALITY_TOLERANCE' : 'TOLERANCE'
|
||||
}
|
||||
|
||||
|
||||
def generate_v2(cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
|
||||
if op['type'] == 'option':
|
||||
return cs
|
||||
|
||||
map = _key_map_32
|
||||
|
||||
cs_v2 = {}
|
||||
for key in op:
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
|
||||
if key in map.keys():
|
||||
if key != 'QUALITY_MODEL' and key != 'DEMAND_MODEL':
|
||||
cs_v2 |= { map[key] : op[key] }
|
||||
elif key == 'QUALITY_MODEL':
|
||||
if str(op[key]).upper() == OPTION_QUALITY_TRACE and 'TRACE_NODE' in op.keys():
|
||||
cs_v2 |= { map[key] : f"{OPTION_QUALITY_TRACE} {op['TRACE_NODE']}" }
|
||||
else:
|
||||
cs_v2 |= { map[key] : str(op[key]).upper() }
|
||||
elif key == 'DEMAND_MODEL':
|
||||
if op[key] == OPTION_V3_DEMAND_MODEL_FIXED:
|
||||
cs_v2 |= { map[key] : OPTION_DEMAND_MODEL_DDA }
|
||||
else:
|
||||
cs_v2 |= { map[key] : OPTION_DEMAND_MODEL_PDA }
|
||||
|
||||
if len(cs_v2) > 0:
|
||||
cs_v2 |= g_update_prefix | { 'type' : 'option' }
|
||||
return ChangeSet(cs_v2)
|
||||
|
||||
return ChangeSet()
|
||||
|
||||
|
||||
def generate_v3(cs: ChangeSet) -> ChangeSet:
|
||||
op = cs.operations[0]
|
||||
|
||||
if op['type'] == 'option_v3':
|
||||
return cs
|
||||
|
||||
map = _key_map_23
|
||||
|
||||
cs_v3 = {}
|
||||
for key in op:
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
|
||||
if key in map.keys():
|
||||
if key != 'QUALITY' and key != 'DEMAND MODEL':
|
||||
cs_v3 |= { map[key] : op[key] }
|
||||
elif key == 'QUALITY':
|
||||
tokens = str(op[key]).split()
|
||||
if len(tokens) >= 1:
|
||||
cs_v3 |= { map[key] : tokens[0].upper() }
|
||||
if tokens[0].upper() == OPTION_QUALITY_TRACE and len(tokens) >= 2:
|
||||
cs_v3 |= { 'TRACE_NODE' : tokens[1] }
|
||||
elif key == 'DEMAND MODEL':
|
||||
if op[key] == OPTION_DEMAND_MODEL_DDA:
|
||||
cs_v3 |= { map[key] : OPTION_V3_DEMAND_MODEL_FIXED }
|
||||
else:
|
||||
cs_v3 |= { map[key] : OPTION_V3_DEMAND_MODEL_POWER }
|
||||
|
||||
if len(cs_v3) > 0:
|
||||
cs_v3 |= g_update_prefix | { 'type' : 'option_v3' }
|
||||
return ChangeSet(cs_v3)
|
||||
|
||||
return ChangeSet()
|
||||
@@ -0,0 +1,83 @@
|
||||
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
|
||||
@@ -0,0 +1,81 @@
|
||||
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]:
|
||||
cs_v2 = g_update_prefix | { 'type' : 'option' }
|
||||
for s in v2_lines:
|
||||
tokens = s.split()
|
||||
if tokens[0].upper() == 'PATTERN': # can not upper id
|
||||
value = tokens[1] if len(tokens) > 1 else ''
|
||||
cs_v2 |= { 'PATTERN' : value }
|
||||
elif tokens[0].upper() == 'QUALITY': # can not upper trace node
|
||||
value = tokens[1]
|
||||
if len(tokens) > 2:
|
||||
value += f' {tokens[2]}'
|
||||
cs_v2 |= { 'QUALITY' : value }
|
||||
else:
|
||||
line = s.upper().strip()
|
||||
for key in get_option_schema('').keys():
|
||||
if line.startswith(key):
|
||||
value = line.removeprefix(key).strip()
|
||||
cs_v2 |= { key : value }
|
||||
return cs_v2
|
||||
|
||||
|
||||
def _inp_in_option_v3(section: list[str]) -> ChangeSet:
|
||||
if len(section) <= 0:
|
||||
return ChangeSet()
|
||||
|
||||
cs_v3 = g_update_prefix | { 'type' : 'option_v3' }
|
||||
v2_lines = []
|
||||
for s in section:
|
||||
if s.startswith(';'):
|
||||
continue
|
||||
|
||||
tokens = s.strip().split()
|
||||
key = tokens[0]
|
||||
if key in get_option_v3_schema('').keys():
|
||||
value = ''
|
||||
if len(tokens) == 2:
|
||||
value = tokens[1]
|
||||
elif len(tokens) > 2:
|
||||
value = ' '.join(tokens[1:])
|
||||
cs_v3 |= { key : value }
|
||||
else:
|
||||
v2_lines.append(s.strip())
|
||||
|
||||
# unlikely...
|
||||
cs_v2 = _parse_v2(v2_lines)
|
||||
|
||||
result = ChangeSet(cs_v3)
|
||||
result.merge(generate_v3(ChangeSet(cs_v2)))
|
||||
result.merge(generate_v2(result))
|
||||
return result
|
||||
|
||||
|
||||
def inp_in_option_v3(section: list[str]) -> str:
|
||||
sql = ''
|
||||
result = _inp_in_option_v3(section)
|
||||
for op in result.operations:
|
||||
for key in op.keys():
|
||||
if key == 'operation' or key == 'type':
|
||||
continue
|
||||
if op['type'] == 'option_v3':
|
||||
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 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, "select key, value from network.simulation_settings where engine_version = 'v3' order by key")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
if str(value).strip() != '':
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,162 @@
|
||||
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'
|
||||
|
||||
pattern_v3_types = [PATTERN_V3_TYPE_FIXED, PATTERN_V3_TYPE_VARIABLE]
|
||||
|
||||
def get_pattern_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'factors' : {'type': 'float_list' , 'optional': False , 'readonly': False } }
|
||||
|
||||
|
||||
def get_pattern(name: str, id: str) -> dict[str, Any]:
|
||||
p_one = try_read(name, "select id from network.patterns where id = %s", (id,))
|
||||
if p_one == None:
|
||||
return {}
|
||||
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) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
old = get_pattern(name, id)
|
||||
|
||||
new = { 'id': id }
|
||||
if 'factors' in cs.operations[0]:
|
||||
new['factors'] = cs.operations[0]['factors']
|
||||
else:
|
||||
new['factors'] = old['factors']
|
||||
|
||||
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});"
|
||||
|
||||
change = g_update_prefix | { 'type': 'pattern' } | new
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pattern(name, cs))
|
||||
|
||||
|
||||
def _add_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
new = { 'id': id, 'factors': cs.operations[0]['factors'] }
|
||||
|
||||
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});"
|
||||
|
||||
change = g_add_prefix | { 'type': 'pattern' } | new
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pattern(name, cs))
|
||||
|
||||
|
||||
def _delete_pattern(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
id = cs.operations[0]['id']
|
||||
f_id = sql_literal(id)
|
||||
|
||||
statement = f"delete from network.patterns where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | { 'type': 'pattern' } | { 'id': id }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_pattern(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pattern(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pattern(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][IN][OUT]
|
||||
# ;desc
|
||||
# id mult1 mult2 .....
|
||||
#--------------------------------------------------------------
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3][IN][OUT]
|
||||
# id FIXED (interval)
|
||||
# id factor1 factor2 ...
|
||||
# id VARIABLE
|
||||
# id time1 factor1 time2 factor2 ...
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
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:]:
|
||||
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]:
|
||||
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, "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']
|
||||
lines.append(f'{id} {factor}')
|
||||
return lines
|
||||
|
||||
|
||||
def inp_out_pattern_v3(name: str) -> list[str]:
|
||||
lines = []
|
||||
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']
|
||||
if id not in ids:
|
||||
# for EPA3, ignore time of variable pattern...
|
||||
lines.append(f'{id} FIXED')
|
||||
ids.append(id)
|
||||
factor = obj['factor']
|
||||
lines.append(f'{id} {factor}')
|
||||
return lines
|
||||
@@ -0,0 +1,254 @@
|
||||
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'
|
||||
PIPE_STATUS_CLOSED = 'CLOSED'
|
||||
PIPE_STATUS_CV = 'CV'
|
||||
|
||||
|
||||
def get_pipe_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'length' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'roughness' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'status' : {'type': 'str' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pipe(name: str, id: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['length'] = float(p['length'])
|
||||
d['diameter'] = float(p['diameter'])
|
||||
d['roughness'] = float(p['roughness'])
|
||||
d['minor_loss'] = float(p['minor_loss'])
|
||||
d['status'] = str(p['status'])
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_pipes(name: str) -> list[dict[str, Any]]:
|
||||
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 []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['length'] = float(row['length'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
d['roughness'] = float(row['roughness'])
|
||||
d['minor_loss'] = float(row['minor_loss'])
|
||||
d['status'] = str(row['status'])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_pipes_by_property(
|
||||
name: str,
|
||||
fields: list[str] | None = None,
|
||||
property_conditions: dict[str, Any] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if not fields:
|
||||
fields = [
|
||||
'id',
|
||||
'node1',
|
||||
'node2',
|
||||
'length',
|
||||
'diameter',
|
||||
'roughness',
|
||||
'minor_loss',
|
||||
'status',
|
||||
]
|
||||
|
||||
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 []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
if property_conditions:
|
||||
matched = True
|
||||
for key, value in property_conditions.items():
|
||||
if row[key] != value:
|
||||
matched = False
|
||||
break
|
||||
if not matched:
|
||||
continue
|
||||
|
||||
d = {}
|
||||
for field in fields:
|
||||
value = row[field]
|
||||
if field in ('length', 'diameter', 'roughness', 'minor_loss') and value is not None:
|
||||
d[field] = float(value)
|
||||
elif field in ('id', 'node1', 'node2', 'status') and value is not None:
|
||||
d[field] = str(value)
|
||||
else:
|
||||
d[field] = value
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Pipe(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pipe'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.length = float(input['length'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.roughness = float(input['roughness'])
|
||||
self.minor_loss = float(input['minor_loss'])
|
||||
self.status = str(input['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 _set_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_pipe(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pipe_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Pipe(raw_new)
|
||||
|
||||
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()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pipe(name, cs))
|
||||
|
||||
|
||||
def _add_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Pipe(cs.operations[0])
|
||||
|
||||
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});"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pipe(name, cs))
|
||||
|
||||
|
||||
def _delete_pipe(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = f"delete from network.links where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'pipe', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_pipe(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pipe(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pipe(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3]
|
||||
# [IN]
|
||||
# id node1 node2 length diam rcoeff (lcoeff status) ;desc
|
||||
# [OUT]
|
||||
# id node1 node2 length diam rcoeff lcoeff (status) ;desc
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_pipe(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
length = float(tokens[3])
|
||||
diameter = float(tokens[4])
|
||||
roughness = float(tokens[5])
|
||||
minor_loss = float(tokens[6])
|
||||
# status is must-have, here fix input
|
||||
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 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 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']
|
||||
node2 = obj['node2']
|
||||
length = obj['length']
|
||||
diameter = obj['diameter']
|
||||
roughness = obj['roughness']
|
||||
minor_loss = obj['minor_loss']
|
||||
status = obj['status']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {length} {diameter} {roughness} {minor_loss} {status} {desc}')
|
||||
return lines
|
||||
@@ -0,0 +1,218 @@
|
||||
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]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'power' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'head' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'speed' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pump(name: str, id: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['power'] = float(p['power']) if p['power'] != None else None
|
||||
d['head'] = str(p['head']) if p['head'] != None else None
|
||||
d['speed'] = float(p['speed']) if p['speed'] != None else None
|
||||
d['pattern'] = str(p['pattern']) if p['pattern'] != None else None
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_pumps(name: str) -> list[dict[str, Any]]:
|
||||
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 []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['power'] = float(row['power']) if row['power'] != None else None
|
||||
d['head'] = str(row['head']) if row['head'] != None else None
|
||||
d['speed'] = float(row['speed']) if row['speed'] != None else None
|
||||
d['pattern'] = str(row['pattern']) if row['pattern'] != None else None
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class Pump(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pump'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.power = float(input['power']) if 'power' in input and input['power'] != None else None
|
||||
self.head = str(input['head']) if 'head' in input and input['head'] != None else None
|
||||
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 = 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 _set_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_pump(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pump_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Pump(raw_new)
|
||||
|
||||
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()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_pump(name, cs))
|
||||
|
||||
|
||||
def _add_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Pump(cs.operations[0])
|
||||
|
||||
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});"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_pump(name, cs))
|
||||
|
||||
|
||||
def _delete_pump(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = f"delete from network.links where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'pump', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_pump(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_pump(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_pump(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id node1 node2 KEYWORD value {KEYWORD value ...} ;desc
|
||||
# where KEYWORD = [POWER,HEAD,PATTERN,SPEED]
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_pump(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
props = {}
|
||||
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
|
||||
head = str(props['head']) if 'head' in props else None
|
||||
speed = float(props['speed']) if 'speed' in props else None
|
||||
pattern = str(props['pattern']) if 'pattern' in props else None
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
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 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']
|
||||
node2 = obj['node2']
|
||||
power = f"POWER {obj['power']}" if obj['power'] != None else ''
|
||||
head = f"HEAD {obj['head']}" if obj['head'] != None else ''
|
||||
speed = f"SPEED {obj['speed']}" if obj['speed'] != None else ''
|
||||
pattern = f"PATTERN {obj['pattern']}" if obj['pattern'] != None else ''
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {power} {head} {speed} {pattern} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_pump_by_curve(name: str, curve: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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})
|
||||
else: # workaround to prevent pump deletion... and I don't want to remove constraint...
|
||||
cs.append(g_update_prefix | {'type': 'pump', 'id': row['id'], 'head': None, 'power': 0.0})
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
def unset_pump_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,99 @@
|
||||
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]]:
|
||||
return { 'node' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'quality' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_quality(name: str, node: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['node'] = str(e['node'])
|
||||
d['quality'] = float(e['quality']) if e['quality'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Quality(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'quality'
|
||||
self.node = str(input['node'])
|
||||
self.quality = float(input['quality']) if 'quality' in input and input['quality'] != None else None
|
||||
|
||||
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) -> DatabaseCommand:
|
||||
raw_new = get_quality(name, cs.operations[0]['node'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_quality_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Quality(raw_new)
|
||||
|
||||
statement = f"delete from network.initial_quality where node_id = {new.f_node};"
|
||||
if new.quality != None:
|
||||
statement += f"\ninsert into network.initial_quality (node_id, value) values ({new.f_node}, {new.f_quality});"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_quality(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_quality(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node initqual
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_quality(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
node = str(tokens[0])
|
||||
quality = float(tokens[1])
|
||||
|
||||
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 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']
|
||||
lines.append(f'{node} {quality}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_quality_by_node(name: str, node: str) -> ChangeSet:
|
||||
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})
|
||||
@@ -0,0 +1,243 @@
|
||||
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_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'ORDER BULK' : element_schema,
|
||||
'ORDER WALL' : element_schema,
|
||||
'ORDER TANK' : element_schema,
|
||||
'GLOBAL BULK' : element_schema,
|
||||
'GLOBAL WALL' : element_schema,
|
||||
'LIMITING POTENTIAL' : element_schema,
|
||||
'ROUGHNESS CORRELATION' : element_schema }
|
||||
|
||||
|
||||
def get_reaction(name: str) -> dict[str, Any]:
|
||||
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) -> DatabaseCommand:
|
||||
new = {}
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_reaction_schema(name)
|
||||
for key in schema.keys():
|
||||
if key in new_dict:
|
||||
new[key] = str(new_dict[key])
|
||||
|
||||
change = g_update_prefix | { 'type' : 'reaction' }
|
||||
|
||||
statement = ''
|
||||
for key, value in new.items():
|
||||
if statement != '':
|
||||
statement += '\n'
|
||||
statement += f"update network.reaction_settings set value = {sql_literal(value)} where key = {sql_literal(key)};"
|
||||
change |= { key: value }
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_reaction(name, cs))
|
||||
|
||||
|
||||
def get_pipe_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'pipe' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'bulk' : {'type': 'float' , 'optional': True , 'readonly': False},
|
||||
'wall' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_pipe_reaction(name: str, pipe: str) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['pipe'] = pipe
|
||||
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
|
||||
|
||||
|
||||
class PipeReaction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'pipe_reaction'
|
||||
self.pipe = str(input['pipe'])
|
||||
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 = 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) -> DatabaseCommand:
|
||||
raw_new = get_pipe_reaction(name, cs.operations[0]['pipe'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_pipe_reaction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = PipeReaction(raw_new)
|
||||
|
||||
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});"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_pipe_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_pipe_reaction(name, cs))
|
||||
|
||||
|
||||
def get_tank_reaction_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'tank' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'value' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_tank_reaction(name: str, tank: str) -> dict[str, Any]:
|
||||
d = {}
|
||||
d['tank'] = tank
|
||||
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
|
||||
|
||||
|
||||
class TankReaction(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'tank_reaction'
|
||||
self.tank = str(input['tank'])
|
||||
self.value = float(input['value']) if 'value' in input and input['value'] != None else None
|
||||
|
||||
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) -> DatabaseCommand:
|
||||
raw_new = get_tank_reaction(name, cs.operations[0]['tank'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_tank_reaction_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = TankReaction(raw_new)
|
||||
|
||||
statement = f"delete from network.tank_reaction_coefficients where tank_id = {new.f_tank};"
|
||||
if new.value != None:
|
||||
statement += f"\ninsert into network.tank_reaction_coefficients (tank_id, coefficient) values ({new.f_tank}, {new.f_value});"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_tank_reaction(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_tank_reaction(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# ORDER {BULK/WALL/TANK} value
|
||||
# GLOBAL BULK coeff
|
||||
# GLOBAL WALL coeff
|
||||
# BULK link1 (link2) coeff
|
||||
# WALL link1 (link2) coeff
|
||||
# TANK node1 (node2) coeff
|
||||
# LIMITING POTENTIAL value
|
||||
# ROUGHNESS CORRELATION value
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_reaction(line: str) -> str:
|
||||
tokens = line.split()
|
||||
token0 = tokens[0].upper()
|
||||
if token0 == 'BULK' or token0 == 'WALL':
|
||||
pipe = tokens[1]
|
||||
key = token0.lower()
|
||||
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 = 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 network.reaction_settings set value = {sql_literal(value)} where key = {sql_literal(key)};")
|
||||
|
||||
return str('')
|
||||
|
||||
|
||||
def inp_out_reaction(name: str) -> list[str]:
|
||||
lines = []
|
||||
|
||||
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, "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, "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, "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']
|
||||
lines.append(f'TANK {tank} {value}')
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def delete_pipe_reaction_by_pipe(name: str, pipe: str) -> ChangeSet:
|
||||
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, "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})
|
||||
@@ -0,0 +1,34 @@
|
||||
from ..core.database import read_all
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# PAGE linesperpage
|
||||
# STATUS {NONE/YES/FULL}
|
||||
# SUMMARY {YES/NO}
|
||||
# MESSAGES {YES/NO}
|
||||
# ENERGY {NO/YES}
|
||||
# NODES {NONE/ALL}
|
||||
# NODES node1 node2 ...
|
||||
# LINKS {NONE/ALL}
|
||||
# LINKS link1 link2 ...
|
||||
# FILE filename
|
||||
# variable {YES/NO}
|
||||
# variable {BELOW/ABOVE/PRECISION} value
|
||||
# [EPA3][NOT SUPPORT]
|
||||
# TRIALS {YES/NO}
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_report(section: list[str]) -> str:
|
||||
return ''
|
||||
|
||||
|
||||
def inp_out_report(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, "select key, value from network.report_settings order by key")
|
||||
for obj in objs:
|
||||
key = obj['key']
|
||||
value = obj['value']
|
||||
lines.append(f'{key} {value}')
|
||||
return lines
|
||||
@@ -0,0 +1,194 @@
|
||||
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]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'x' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'y' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'head' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_reservoir(name: str, id: str) -> dict[str, Any]:
|
||||
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)
|
||||
d = {}
|
||||
d['id'] = str(r['id'])
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['head'] = float(r['head'])
|
||||
d['pattern'] = str(r['pattern']) if r['pattern'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_reservoirs(name: str) -> list[dict[str, Any]]:
|
||||
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'])
|
||||
d['id'] = id
|
||||
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'] = links_by_node.get(id, [])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Reservoir(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'reservoir'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.head = float(input['head'])
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
|
||||
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 _set_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_reservoir(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_reservoir_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Reservoir(raw_new)
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_reservoir(name, cs))
|
||||
|
||||
|
||||
def _add_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Reservoir(cs.operations[0])
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_reservoir(name, cs))
|
||||
|
||||
|
||||
def _delete_reservoir(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = sql_delete_coord(element_id)
|
||||
statement += f"\ndelete from network.nodes where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'reservoir', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_reservoir(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_reservoir(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_reservoir(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id elev (pattern) ;desc
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_reservoir(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
head = float(tokens[1])
|
||||
pattern = str(tokens[2]) if num_without_desc >= 3 else None
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
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 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']
|
||||
pattern = obj['pattern'] if obj['pattern'] != None else ''
|
||||
desc = ';'
|
||||
lines.append(f'{id} {head} {pattern} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_reservoir_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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})
|
||||
|
||||
return cs
|
||||
@@ -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']
|
||||
@@ -0,0 +1,152 @@
|
||||
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'
|
||||
SOURCE_TYPE_FLOWPACED = 'FLOWPACED'
|
||||
SOURCE_TYPE_SETPOINT = 'SETPOINT'
|
||||
|
||||
def get_source_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'node' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
's_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'strength' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'pattern' : {'type': 'str' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_source(name: str, node: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['node'] = str(s['node'])
|
||||
d['s_type'] = str(s['s_type'])
|
||||
d['strength'] = float(s['strength'])
|
||||
d['pattern'] = str(s['pattern']) if s['pattern'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Source(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'source'
|
||||
self.node = str(input['node'])
|
||||
self.s_type = str(input['s_type'])
|
||||
self.strength = float(input['strength'])
|
||||
self.pattern = str(input['pattern']) if 'pattern' in input and input['pattern'] != None else None
|
||||
|
||||
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 _set_source(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_source(name, cs.operations[0]['node'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_source_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Source(raw_new)
|
||||
|
||||
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};"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
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) -> DatabaseCommand:
|
||||
new = Source(cs.operations[0])
|
||||
|
||||
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});"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
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) -> DatabaseCommand:
|
||||
node = str(cs.operations[0]['node'])
|
||||
f_node = sql_literal(node)
|
||||
|
||||
statement = f"delete from network.sources where node_id = {f_node};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'source', 'node': node}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_source(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _delete_source(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# node sourcetype quality (pattern)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_source(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
node = str(tokens[0])
|
||||
s_type = str(tokens[1].upper())
|
||||
strength = float(tokens[2])
|
||||
pattern = str(tokens[3]) if num_without_desc >= 4 else None
|
||||
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 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']
|
||||
strength = obj['strength']
|
||||
pattern = obj['pattern'] if obj['pattern'] != None else ''
|
||||
lines.append(f'{node} {s_type} {strength} {pattern}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_source_by_node(name: str, node: str) -> ChangeSet:
|
||||
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})
|
||||
|
||||
|
||||
def unset_source_by_pattern(name: str, pattern: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,114 @@
|
||||
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'
|
||||
LINK_STATUS_CLOSED = 'CLOSED'
|
||||
LINK_STATUS_ACTIVE = 'ACTIVE'
|
||||
|
||||
|
||||
def get_status_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'link' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'status' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'setting' : {'type': 'float' , 'optional': True , 'readonly': False} }
|
||||
|
||||
|
||||
def get_status(name: str, link: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['link'] = str(s['link'])
|
||||
d['status'] = str(s['status']) if s['status'] != None else None
|
||||
d['setting'] = float(s['setting']) if s['setting'] != None else None
|
||||
return d
|
||||
|
||||
|
||||
class Status(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'status'
|
||||
self.link = str(input['link'])
|
||||
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 = 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) -> DatabaseCommand:
|
||||
raw_new = get_status(name, cs.operations[0]['link'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_status_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Status(raw_new)
|
||||
|
||||
statement = f"delete from network.link_initial_settings where link_id = {new.f_link};"
|
||||
if new.status != None or new.setting != None:
|
||||
statement += f"\ninsert into network.link_initial_settings (link_id, status, setting) values ({new.f_link}, {new.f_status}, {new.f_setting});"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_status(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
return execute_command(name, _set_status(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# link value
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_status(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
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 network.link_initial_settings (link_id, status, setting) values ({sql_literal(link)}, {sql_literal(value)}, null);")
|
||||
else:
|
||||
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 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 ''
|
||||
setting = obj['setting'] if obj['setting'] != None else ''
|
||||
if status != '':
|
||||
lines.append(f'{link} {status}')
|
||||
if setting != '':
|
||||
lines.append(f'{link} {setting}')
|
||||
return lines
|
||||
|
||||
|
||||
def delete_status_by_link(name: str, link: str) -> ChangeSet:
|
||||
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()
|
||||
)
|
||||
@@ -0,0 +1,252 @@
|
||||
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'
|
||||
OVERFLOW_NO = 'NO'
|
||||
|
||||
|
||||
def get_tank_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},
|
||||
'elevation' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'init_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'min_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'max_level' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'min_vol' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'vol_curve' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'overflow' : {'type': 'str' , 'optional': True , 'readonly': False},
|
||||
'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } }
|
||||
|
||||
|
||||
def get_tank(name: str, id: str) -> dict[str, Any]:
|
||||
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)
|
||||
d = {}
|
||||
d['id'] = str(t['id'])
|
||||
d['x'] = float(xy['x'])
|
||||
d['y'] = float(xy['y'])
|
||||
d['elevation'] = float(t['elevation'])
|
||||
d['init_level'] = float(t['init_level'])
|
||||
d['min_level'] = float(t['min_level'])
|
||||
d['max_level'] = float(t['max_level'])
|
||||
d['diameter'] = float(t['diameter'])
|
||||
d['min_vol'] = float(t['min_vol'])
|
||||
d['vol_curve'] = str(t['vol_curve']) if t['vol_curve'] != None else None
|
||||
d['overflow'] = str(t['overflow']) if t['overflow'] != None else None
|
||||
d['links'] = get_node_links(name, id)
|
||||
return d
|
||||
|
||||
# DingZQ, 2025-03-29
|
||||
def get_all_tanks(name: str) -> list[dict[str, Any]]:
|
||||
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'])
|
||||
d['id'] = id
|
||||
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'])
|
||||
d['max_level'] = float(row['max_level'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
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'] = links_by_node.get(id, [])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
class Tank(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'tank'
|
||||
self.id = str(input['id'])
|
||||
self.x = float(input['x'])
|
||||
self.y = float(input['y'])
|
||||
self.elevation = float(input['elevation'])
|
||||
self.init_level = float(input['init_level'])
|
||||
self.min_level = float(input['min_level'])
|
||||
self.max_level = float(input['max_level'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.min_vol = float(input['min_vol'])
|
||||
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 = 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 _set_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_tank(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_tank_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Tank(raw_new)
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_update_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_tank(name, cs))
|
||||
|
||||
|
||||
def _add_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Tank(cs.operations[0])
|
||||
|
||||
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)}"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_tank(name, cs))
|
||||
|
||||
|
||||
def _delete_tank(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = sql_delete_coord(element_id)
|
||||
statement += f"\ndelete from network.nodes where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'tank', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_tank(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_tank(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_tank(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2]
|
||||
# [IN]
|
||||
# id elev initlevel minlevel maxlevel diam (minvol vcurve overflow) ;desc
|
||||
# xxx
|
||||
# * YES
|
||||
# [OUT]
|
||||
# id elev initlevel minlevel maxlevel diam minvol (vcurve overflow) ;desc
|
||||
#--------------------------------------------------------------
|
||||
# [EPA3]
|
||||
# id elev initlevel minlevel maxlevel diam minvol (vcurve)
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_tank(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
elevation = float(tokens[1])
|
||||
init_level = float(tokens[2])
|
||||
min_level = float(tokens[3])
|
||||
max_level = float(tokens[4])
|
||||
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
|
||||
overflow = str(tokens[8].upper()) if num_without_desc >= 9 else None
|
||||
desc = str(tokens[-1]) if has_desc else None
|
||||
|
||||
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 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']
|
||||
init_level = obj['init_level']
|
||||
min_level = obj['min_level']
|
||||
max_level = obj['max_level']
|
||||
diameter = obj['diameter']
|
||||
min_vol = obj['min_vol']
|
||||
vol_curve = obj['vol_curve'] if obj['vol_curve'] != None else ''
|
||||
overflow = obj['overflow'] if obj['overflow'] != None else ''
|
||||
if vol_curve == '' and overflow != '':
|
||||
vol_curve = '*'
|
||||
desc = ';'
|
||||
lines.append(f'{id} {elevation} {init_level} {min_level} {max_level} {diameter} {min_vol} {vol_curve} {overflow} {desc}')
|
||||
return lines
|
||||
|
||||
|
||||
def unset_tank_by_curve(name: str, curve: str) -> ChangeSet:
|
||||
cs = ChangeSet()
|
||||
|
||||
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})
|
||||
|
||||
return cs
|
||||
@@ -0,0 +1,110 @@
|
||||
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")
|
||||
@@ -0,0 +1,200 @@
|
||||
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'
|
||||
VALVES_TYPE_PSV = 'PSV'
|
||||
VALVES_TYPE_PBV = 'PBV'
|
||||
VALVES_TYPE_FCV = 'FCV'
|
||||
VALVES_TYPE_TCV = 'TCV'
|
||||
VALVES_TYPE_GPV = 'GPV'
|
||||
|
||||
|
||||
def get_valve_schema(name: str) -> dict[str, dict[str, Any]]:
|
||||
return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True },
|
||||
'node1' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'node2' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'diameter' : {'type': 'float' , 'optional': False , 'readonly': False},
|
||||
'v_type' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'setting' : {'type': 'str' , 'optional': False , 'readonly': False},
|
||||
'minor_loss' : {'type': 'float' , 'optional': False , 'readonly': False} }
|
||||
|
||||
|
||||
def get_valve(name: str, id: str) -> dict[str, Any]:
|
||||
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 = {}
|
||||
d['id'] = str(p['id'])
|
||||
d['node1'] = str(p['node1'])
|
||||
d['node2'] = str(p['node2'])
|
||||
d['diameter'] = float(p['diameter'])
|
||||
d['v_type'] = str(p['v_type'])
|
||||
d['setting'] = str(p['setting'])
|
||||
d['minor_loss'] = float(p['minor_loss'])
|
||||
return d
|
||||
|
||||
def get_all_valves(name: str) -> list[dict[str, Any]]:
|
||||
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 []
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
d = {}
|
||||
d['id'] = str(row['id'])
|
||||
d['node1'] = str(row['node1'])
|
||||
d['node2'] = str(row['node2'])
|
||||
d['diameter'] = float(row['diameter'])
|
||||
d['v_type'] = str(row['v_type'])
|
||||
d['setting'] = str(row['setting'])
|
||||
d['minor_loss'] = float(row['minor_loss'])
|
||||
result.append(d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
class Valve(object):
|
||||
def __init__(self, input: dict[str, Any]) -> None:
|
||||
self.type = 'valve'
|
||||
self.id = str(input['id'])
|
||||
self.node1 = str(input['node1'])
|
||||
self.node2 = str(input['node2'])
|
||||
self.diameter = float(input['diameter'])
|
||||
self.v_type = str(input['v_type'])
|
||||
self.setting = str(input['setting'])
|
||||
self.minor_loss = float(input['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 _set_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
raw_new = get_valve(name, cs.operations[0]['id'])
|
||||
|
||||
new_dict = cs.operations[0]
|
||||
schema = get_valve_schema(name)
|
||||
for key, value in schema.items():
|
||||
if key in new_dict and not value['readonly']:
|
||||
raw_new[key] = new_dict[key]
|
||||
new = Valve(raw_new)
|
||||
|
||||
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()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def set_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _set_valve(name, cs))
|
||||
|
||||
|
||||
def _add_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
new = Valve(cs.operations[0])
|
||||
|
||||
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});"
|
||||
|
||||
change = g_add_prefix | new.as_dict()
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def add_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) != {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _add_valve(name, cs))
|
||||
|
||||
|
||||
def _delete_valve(name: str, cs: ChangeSet) -> DatabaseCommand:
|
||||
element_id = str(cs.operations[0]['id'])
|
||||
f_id = sql_literal(element_id)
|
||||
|
||||
statement = f"delete from network.links where id = {f_id};"
|
||||
|
||||
change = g_delete_prefix | {'type': 'valve', 'id': element_id}
|
||||
|
||||
return DatabaseCommand(statement, [change])
|
||||
|
||||
|
||||
def delete_valve(name: str, cs: ChangeSet) -> ChangeSet:
|
||||
if 'id' not in cs.operations[0]:
|
||||
return ChangeSet()
|
||||
if get_valve(name, cs.operations[0]['id']) == {}:
|
||||
return ChangeSet()
|
||||
return execute_command(name, _delete_valve(name, cs))
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
# [EPA2][EPA3][IN][OUT]
|
||||
# id node1 node2 diam type setting (lcoeff lcurve)
|
||||
# for GPV, setting is string = head curve id
|
||||
# [NOT SUPPORT] for PCV, add loss curve if present
|
||||
#--------------------------------------------------------------
|
||||
|
||||
|
||||
def inp_in_valve(line: str) -> str:
|
||||
tokens = line.split()
|
||||
|
||||
num = len(tokens)
|
||||
has_desc = tokens[-1].startswith(';')
|
||||
num_without_desc = (num - 1) if has_desc else num
|
||||
|
||||
id = str(tokens[0])
|
||||
node1 = str(tokens[1])
|
||||
node2 = str(tokens[2])
|
||||
diameter = float(tokens[3])
|
||||
v_type = str(tokens[4].upper())
|
||||
setting = str(tokens[5])
|
||||
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 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 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']
|
||||
node2 = obj['node2']
|
||||
diameter = obj['diameter']
|
||||
v_type = obj['v_type']
|
||||
setting = obj['setting']
|
||||
minor_loss = obj['minor_loss']
|
||||
desc = ';'
|
||||
lines.append(f'{id} {node1} {node2} {diameter} {v_type} {setting} {minor_loss} {desc}')
|
||||
return lines
|
||||
Reference in New Issue
Block a user