Files
TJWaterServerBinary/app/native/wndb/model/status.py
T
jiang 9ab7b10e3c
Generic Container CI/CD / test-build-publish (push) Successful in 2m12s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 2m12s
fix(simulation): export ACTIVE valves correctly for EPANET 2
The legacy online simulation path still exported internal ACTIVE states as STATUS keywords, which EPANET 2 rejects. Preserve numeric settings and EPANET 3 output while passing the format through both exporters. Surface the original engine error inside the model transaction so failed runs roll back before result storage.

Regression guard: six failures reproduced on the deployed baseline, including native input validation. All 34 focused export, service and endpoint checks pass after the fix; the real project also returns native node and link results.
2026-11-03 13:48:50 +08:00

117 lines
4.4 KiB
Python

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, version: str = '2') -> 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 ''
# EPANET 2 valves use their configured setting by default; ACTIVE is
# an internal state, not a valid keyword in its [STATUS] section.
if status != '' and not (version == '2' and status.upper() == LINK_STATUS_ACTIVE):
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})