587 lines
26 KiB
Python
587 lines
26 KiB
Python
import numpy as np
|
||
from app.infra.epanet import run_project
|
||
from app.native.wndb.core.database import ChangeSet
|
||
from app.native.wndb.model.demands import get_demand, set_demand
|
||
from app.native.wndb.model.options import get_option
|
||
from app.native.wndb.model.patterns import get_pattern, set_pattern
|
||
from app.native.wndb.model.pumps import get_pump
|
||
from app.native.wndb.model.reservoirs import get_reservoir
|
||
from app.native.wndb.model.status import get_status, set_status
|
||
from app.native.wndb.model.tanks import get_tank, set_tank
|
||
from app.native.wndb.model.times import get_time, set_time
|
||
|
||
# from get_real_status import *
|
||
from datetime import datetime
|
||
from math import modf
|
||
import json
|
||
import pytz
|
||
import time
|
||
from uuid import UUID
|
||
import logging
|
||
from app.infra.db.postgresql.scada import (
|
||
ScadaElementMappings,
|
||
load_realtime_element_mappings,
|
||
)
|
||
from app.domain.time import parse_beijing_time, parse_clock_duration_seconds
|
||
from app.native.wndb.core.connection import project_transaction
|
||
from app.native.wndb.core.database import refresh_materialized_views_after_commit
|
||
from app.infra.db.timescaledb.internal_queries import (
|
||
InternalQueries as TimescaleInternalQueries,
|
||
)
|
||
from app.services.scheme_management import create_analysis_run, update_analysis_run
|
||
from app.infra.db.timescaledb.internal_queries import (
|
||
InternalStorage as TimescaleInternalStorage,
|
||
)
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||
)
|
||
|
||
RESERVOIR_BASIC_HEIGHT = 250.35
|
||
|
||
|
||
def _primary_demand(demand_set: dict) -> dict:
|
||
"""Return sequence-zero demand, creating it when a junction has none."""
|
||
demands = demand_set.setdefault("demands", [])
|
||
if not demands:
|
||
demands.append({"demand": 0.0, "pattern": None, "category": None})
|
||
return demands[0]
|
||
|
||
|
||
def _primary_demand_pattern(demand_set: dict) -> str:
|
||
"""Return the first configured pattern in deterministic sequence order."""
|
||
pattern = next(
|
||
(
|
||
demand.get("pattern")
|
||
for demand in demand_set.get("demands", [])
|
||
if demand.get("pattern")
|
||
),
|
||
None,
|
||
)
|
||
if pattern is None:
|
||
raise ValueError(
|
||
f"Junction {demand_set.get('junction')!r} has no demand pattern"
|
||
)
|
||
return str(pattern)
|
||
|
||
|
||
def query_corresponding_element_id_and_query_id(name: str) -> ScadaElementMappings:
|
||
"""Return an immutable project-local SCADA-to-model mapping snapshot."""
|
||
return load_realtime_element_mappings(name)
|
||
|
||
|
||
def get_pattern_index(cur_datetime: str, pattern_time_step: float) -> int:
|
||
"""
|
||
根据给定的日期时间字符串,计算并返回对应的模式索引。
|
||
:param cur_datetime: str, 当前的日期时间字符串,格式为“YYYY-MM-DD HH:MM:SS”。
|
||
:return: int, 基于预定义的时间步长 PATTERN_TIME_STEP。
|
||
"""
|
||
str_format = "%Y-%m-%d %H:%M:%S"
|
||
dt = datetime.strptime(cur_datetime, str_format)
|
||
hr = dt.hour
|
||
mnt = dt.minute
|
||
i = int((hr * 60 + mnt) / pattern_time_step)
|
||
return i
|
||
|
||
|
||
def get_pattern_index_str(current_time: str, pattern_time_step: float) -> str:
|
||
"""
|
||
根据当前时间获取时间步长的模式索引,并将其格式化为“HH:MM:00”字符串。
|
||
:param current_time: str, 当前时间,格式为"YYYY-MM-DD HH:MM:SS"
|
||
:return: str, 以“HH:MM:00”格式返回
|
||
"""
|
||
i = get_pattern_index(current_time, pattern_time_step)
|
||
[minN, hrN] = modf(i * pattern_time_step / 60)
|
||
minN_str = str(int(minN * 60))
|
||
minN_str = minN_str.zfill(2)
|
||
hrN_str = str(int(hrN))
|
||
hrN_str = hrN_str.zfill(2)
|
||
str_i = "{}:{}:00".format(hrN_str, minN_str)
|
||
return str_i
|
||
|
||
|
||
def from_seconds_to_clock(secs: int) -> str:
|
||
"""
|
||
从秒格式化为“HH:MM:00”字符串
|
||
:param secs: int,秒
|
||
:return: str, 以“HH:MM:00”格式返回
|
||
"""
|
||
hrs = int(secs / 3600)
|
||
minutes = int((secs - hrs * 3600) / 60)
|
||
seconds = secs - hrs * 3600 - minutes * 60
|
||
hrs_str = str(hrs).zfill(2)
|
||
minutes_str = str(minutes).zfill(2)
|
||
seconds_str = str(seconds).zfill(2)
|
||
str_clock = "{}:{}:{}".format(hrs_str, minutes_str, seconds_str)
|
||
return str_clock
|
||
|
||
|
||
def convert_time_format(original_time: str) -> str:
|
||
"""
|
||
格式转换,将带时区的 ISO 8601 / RFC3339 时间转为北京时间的“YYYY-MM-DD HH:MM:SS”
|
||
:param original_time: str,带显式时区的时间
|
||
:return: str,“2024-04-13 08:00:00”格式的时间
|
||
"""
|
||
normalized_time = parse_beijing_time(
|
||
original_time, field_name="modify_pattern_start_time"
|
||
)
|
||
return normalized_time.replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
|
||
def _apply_valve_control(
|
||
project_name: str, valve_control: dict[str, dict]
|
||
) -> None:
|
||
"""Apply explicit valve status, setting, and opening controls."""
|
||
for valve_name, control in valve_control.items():
|
||
valve_status = get_status(project_name, valve_name)
|
||
if "status" in control:
|
||
valve_status["status"] = control["status"]
|
||
if "setting" in control:
|
||
valve_status["setting"] = control["setting"]
|
||
if "k" in control:
|
||
valve_k = control["k"]
|
||
if valve_k == 0:
|
||
valve_status["status"] = "CLOSED"
|
||
else:
|
||
valve_status["setting"] = 0.1036 * pow(valve_k, -3.105)
|
||
|
||
cs = ChangeSet()
|
||
cs.append(valve_status)
|
||
set_status(project_name, cs)
|
||
|
||
|
||
# 2025/01/11
|
||
def run_simulation(
|
||
name: str,
|
||
simulation_type: str,
|
||
modify_pattern_start_time: str,
|
||
modify_total_duration: int = 0,
|
||
modify_reservoir_head_pattern: dict[str, list] = None,
|
||
modify_tank_initial_level: dict[str, float] = None,
|
||
modify_junction_base_demand: dict[str, float] = None,
|
||
modify_junction_damand_pattern: dict[str, list] = None,
|
||
modify_fixed_pump_pattern: dict[str, list] = None,
|
||
modify_variable_pump_pattern: dict[str, list] = None,
|
||
modify_valve_opening: dict[str, float] = None,
|
||
scheme_type: str = None,
|
||
scheme_name: str = None,
|
||
result_db_name: str = None,
|
||
valve_control: dict[str, dict] = None,
|
||
scheme_username: str = "system",
|
||
scheme_detail: dict | None = None,
|
||
scada_mappings: ScadaElementMappings | None = None,
|
||
) -> UUID | None:
|
||
"""
|
||
传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果
|
||
:param name: 模型名称,数据库中对应的名字
|
||
:param simulation_type: 模拟的类型,realtime为实时模拟,修改原数据库;extended为多步长模拟,需要复制数据库
|
||
:param modify_pattern_start_time: 模拟开始时间,格式为'2024-11-25T09:00:00+08:00'
|
||
:param modify_total_duration: 模拟总历时,秒
|
||
:param modify_reservoir_head_pattern: dict中包含多个水库模式,str为水库head_pattern的id,list为修改后的head_pattern
|
||
:param modify_tank_initial_level: dict中包含多个水塔,str为水塔的id,float为修改后的initial_level
|
||
:param modify_junction_base_demand: dict中包含多个节点,str为节点的id,float为修改后的base_demand
|
||
:param modify_junction_damand_pattern: dict中包含多个节点模式,str为节点demand_pattern的id,list为修改后的demand_pattern
|
||
:param modify_fixed_pump_pattern: dict中包含多个水泵模式,str为工频水泵的id,list为修改后的pattern
|
||
:param modify_variable_pump_pattern: dict中包含多个水泵模式,str为变频水泵的id,list为修改后的pattern
|
||
:param modify_valve_opening: dict中包含多个阀门开启度,str为阀门的id,float为修改后的阀门开启度
|
||
:param valve_control: dict中可分别指定阀门的status、setting和k;存在时优先于modify_valve_opening
|
||
:param scheme_type: 模拟方案类型
|
||
:param scheme_name:模拟方案名称
|
||
:return:
|
||
"""
|
||
# 记录开始时间
|
||
time_cost_start = time.perf_counter()
|
||
|
||
print("name", name)
|
||
print("simulation_type", simulation_type)
|
||
print("modify_pattern_start_time", modify_pattern_start_time)
|
||
print("modify_total_duration", modify_total_duration)
|
||
print("modify_reservoir_head_pattern", modify_reservoir_head_pattern)
|
||
print("modify_tank_initial_level", modify_tank_initial_level)
|
||
print("modify_junction_base_demand", modify_junction_base_demand)
|
||
|
||
print(
|
||
"{} -- Hydraulic simulation started.".format(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
|
||
)
|
||
)
|
||
|
||
# 判断是实时模拟还是多步长模拟
|
||
# if simulation_type.upper() == 'REALTIME': # 实时模拟(修改原数据库)
|
||
# name_c = name
|
||
# elif simulation_type.upper() == 'EXTENDED': # 扩展模拟(复制数据库)
|
||
# name_c = '_'.join([name, 'c'])
|
||
# if have_project(name_c):
|
||
# delete_project(name_c)
|
||
# copy_project(name, name_c) # 备份项目
|
||
# else:
|
||
# raise Exception('Incorrect simulation type, choose in (realtime, extended)')
|
||
name_c = name
|
||
with project_transaction(name_c):
|
||
dic_time = get_time(name_c)
|
||
|
||
print(dic_time)
|
||
|
||
# 获取水力模拟步长,如’0:15:00‘
|
||
hydraulic_timestep = dic_time["HYDRAULIC TIMESTEP"]
|
||
# 转换为分钟浮点数,兼容 EPANET 的 H:MM 和 H:MM:SS 写法
|
||
pattern_time_step = (
|
||
parse_clock_duration_seconds(
|
||
hydraulic_timestep,
|
||
field_name="HYDRAULIC TIMESTEP",
|
||
)
|
||
/ 60
|
||
)
|
||
project_scada = scada_mappings or load_realtime_element_mappings(name_c)
|
||
# 对输入的时间参数进行处理
|
||
pattern_start_time = convert_time_format(modify_pattern_start_time)
|
||
# 获取模拟开始时间是对应pattern的第几个数
|
||
modify_index = get_pattern_index(pattern_start_time, pattern_time_step)
|
||
# 遍历水泵的pattern_id,并根据输入的pump_pattern修改pattern的值
|
||
# for pump_pattern_id in pump_pattern_ids:
|
||
# # 检查pump_pattern中pump_pattern_id对应的第一个频率值是否为有效数字(非空、非NaN)。如果该值有效,则继续执行代码块。
|
||
# if not np.isnan(modify_pump_pattern[pump_pattern_id][0]):
|
||
# # 取出数据库中的pattern
|
||
# pump_pattern = get_pattern(name_c, get_pump(name_c, pump_pattern_id)['pattern'])
|
||
# # 替换数据库中的pattern为modify_pump_pattern
|
||
# pump_pattern['factors'][modify_index: modify_index + len(modify_pump_pattern[pump_pattern_id])] \
|
||
# = modify_pump_pattern[pump_pattern_id]
|
||
# cs = ChangeSet()
|
||
# cs.append(pump_pattern)
|
||
# set_pattern(name_c, cs)
|
||
# 修改模拟开始的时间
|
||
str_pattern_start = get_pattern_index_str(
|
||
convert_time_format(modify_pattern_start_time), pattern_time_step
|
||
)
|
||
dic_time = get_time(name_c)
|
||
dic_time["PATTERN START"] = str_pattern_start
|
||
dic_time["DURATION"] = from_seconds_to_clock(modify_total_duration)
|
||
if simulation_type.upper() == "REALTIME":
|
||
dic_time["DURATION"] = 0
|
||
cs = ChangeSet()
|
||
cs.operations.append(dic_time)
|
||
set_time(name_c, cs)
|
||
# 根据SCADA实时数据进行修改,如果没有对应的SCADA数据,如未来的时间点,则不改变pg数据库的数据
|
||
if project_scada.reservoirs:
|
||
# reservoirs_id = {'ZBBDJSCP000002': '2497', 'R00003': '2571'}
|
||
# 1.获取reservoir的SCADA数据,形式如{'2497': '3.1231', '2571': '2.7387'}
|
||
reservoir_SCADA_data_dict = TimescaleInternalQueries.query_scada_by_ids_time(
|
||
device_ids=list(project_scada.reservoirs.values()),
|
||
query_time=modify_pattern_start_time,
|
||
db_name=name,
|
||
)
|
||
# 2.构建出新字典,形式如{'ZBBDJSCP000002': '3.1231', 'R00003': '2.7387'}
|
||
reservoir_dict = {
|
||
key: reservoir_SCADA_data_dict[value]
|
||
for key, value in project_scada.reservoirs.items()
|
||
}
|
||
# 3.修改reservoir液位模式
|
||
for reservoir_name, value in reservoir_dict.items():
|
||
if value and float(value) != 0:
|
||
# 先根据reservoir获取对应的pattern,再对pattern进行修改
|
||
reservoir_pattern = get_pattern(
|
||
name_c, get_reservoir(name_c, reservoir_name)["pattern"]
|
||
)
|
||
reservoir_pattern["factors"][modify_index] = (
|
||
float(value) + RESERVOIR_BASIC_HEIGHT
|
||
)
|
||
cs = ChangeSet()
|
||
cs.append(reservoir_pattern)
|
||
set_pattern(name_c, cs)
|
||
if project_scada.tanks:
|
||
# 修改tank初始液位
|
||
tank_SCADA_data_dict = TimescaleInternalQueries.query_scada_by_ids_time(
|
||
device_ids=list(project_scada.tanks.values()),
|
||
query_time=modify_pattern_start_time,
|
||
db_name=name,
|
||
)
|
||
tank_dict = {
|
||
key: tank_SCADA_data_dict[value]
|
||
for key, value in project_scada.tanks.items()
|
||
}
|
||
for tank_name, value in tank_dict.items():
|
||
if value and float(value) != 0:
|
||
tank = get_tank(name_c, tank_name)
|
||
tank["init_level"] = float(value)
|
||
cs = ChangeSet()
|
||
cs.append(tank)
|
||
set_tank(name_c, cs)
|
||
if project_scada.fixed_pumps:
|
||
# 修改工频泵的pattern
|
||
fixed_pump_SCADA_data_dict = TimescaleInternalQueries.query_scada_by_ids_time(
|
||
device_ids=list(project_scada.fixed_pumps.values()),
|
||
query_time=modify_pattern_start_time,
|
||
db_name=name,
|
||
)
|
||
# print(fixed_pump_SCADA_data_dict)
|
||
fixed_pump_dict = {
|
||
key: fixed_pump_SCADA_data_dict[value]
|
||
for key, value in project_scada.fixed_pumps.items()
|
||
}
|
||
# print(fixed_pump_dict)
|
||
for fixed_pump_name, value in fixed_pump_dict.items():
|
||
if value:
|
||
pump_pattern = get_pattern(
|
||
name_c, get_pump(name_c, fixed_pump_name)["pattern"]
|
||
)
|
||
# print(pump_pattern)
|
||
pump_pattern["factors"][modify_index] = float(value)
|
||
# print(pump_pattern['factors'][modify_index])
|
||
cs = ChangeSet()
|
||
cs.append(pump_pattern)
|
||
set_pattern(name_c, cs)
|
||
if project_scada.variable_pumps:
|
||
# 修改变频泵的pattern
|
||
variable_pump_SCADA_data_dict = (
|
||
TimescaleInternalQueries.query_scada_by_ids_time(
|
||
device_ids=list(project_scada.variable_pumps.values()),
|
||
query_time=modify_pattern_start_time,
|
||
db_name=name,
|
||
)
|
||
)
|
||
variable_pump_dict = {
|
||
key: variable_pump_SCADA_data_dict[value]
|
||
for key, value in project_scada.variable_pumps.items()
|
||
}
|
||
for variable_pump_name, value in variable_pump_dict.items():
|
||
if value:
|
||
pump_pattern = get_pattern(
|
||
name_c, get_pump(name_c, variable_pump_name)["pattern"]
|
||
)
|
||
pump_pattern["factors"][modify_index] = float(value) / 50
|
||
cs = ChangeSet()
|
||
cs.append(pump_pattern)
|
||
set_pattern(name_c, cs)
|
||
if project_scada.demand:
|
||
# 基于实时数据,修改大用户节点的pattern
|
||
demand_SCADA_data_dict = TimescaleInternalQueries.query_scada_by_ids_time(
|
||
device_ids=list(project_scada.demand.values()),
|
||
query_time=modify_pattern_start_time,
|
||
db_name=name,
|
||
)
|
||
demand_dict = {
|
||
key: demand_SCADA_data_dict[value]
|
||
for key, value in project_scada.demand.items()
|
||
}
|
||
for demand_name, value in demand_dict.items():
|
||
if value is not None and not np.isnan(float(value)):
|
||
demand_set = get_demand(name_c, demand_name)
|
||
demand_pattern = get_pattern(
|
||
name_c, _primary_demand_pattern(demand_set)
|
||
)
|
||
if get_option(name_c)["UNITS"] == "LPS":
|
||
demand_pattern["factors"][modify_index] = (
|
||
float(value) / 3.6
|
||
) # 默认SCADA数据获取的是流量单位是m3/h, 转换为 L/s
|
||
elif get_option(name_c)["UNITS"] == "CMH":
|
||
demand_pattern["factors"][modify_index] = float(value)
|
||
cs = ChangeSet()
|
||
cs.append(demand_pattern)
|
||
set_pattern(name_c, cs)
|
||
# 水质、压力实时数据使用方法待补充
|
||
#############################
|
||
# 显式请求参数覆盖实时设备数据,用于扩展模拟。
|
||
# 修改清水池(reservoir)液位的pattern
|
||
if modify_reservoir_head_pattern:
|
||
for reservoir_name in modify_reservoir_head_pattern.keys():
|
||
# 这句代码的作用是判断modify_reservoir_head_pattern[reservoir_name][0]是否不是NaN。
|
||
# 如果modify_reservoir_head_pattern[reservoir_name][0]不是NaN,则条件成立,代码块会执行
|
||
if not np.isnan(modify_reservoir_head_pattern[reservoir_name][0]):
|
||
# 给 list 中的所有元素加上 RESERVOIR_BASIC_HEIGHT
|
||
modified_values = [
|
||
value + RESERVOIR_BASIC_HEIGHT
|
||
for value in modify_reservoir_head_pattern[reservoir_name]
|
||
]
|
||
reservoir_pattern = get_pattern(
|
||
name_c, get_reservoir(name_c, reservoir_name)["pattern"]
|
||
)
|
||
reservoir_pattern["factors"][
|
||
modify_index : modify_index + len(modified_values)
|
||
] = modified_values
|
||
cs = ChangeSet()
|
||
cs.append(reservoir_pattern)
|
||
set_pattern(name_c, cs)
|
||
# 修改调节池(tank)初始液位
|
||
if modify_tank_initial_level:
|
||
for tank_name in modify_tank_initial_level.keys():
|
||
if (not np.isnan(modify_tank_initial_level[tank_name])) and (
|
||
modify_tank_initial_level[tank_name] != 0
|
||
):
|
||
tank = get_tank(name_c, tank_name)
|
||
tank["init_level"] = modify_tank_initial_level[tank_name]
|
||
cs = ChangeSet()
|
||
cs.append(tank)
|
||
set_tank(name_c, cs)
|
||
# 修改节点(junction)基础水量(demand)
|
||
if modify_junction_base_demand:
|
||
for junction_name in modify_junction_base_demand.keys():
|
||
if not np.isnan(modify_junction_base_demand[junction_name]):
|
||
junction = get_demand(name_c, junction_name)
|
||
_primary_demand(junction)["demand"] = (
|
||
modify_junction_base_demand[junction_name]
|
||
)
|
||
cs = ChangeSet()
|
||
cs.append(junction)
|
||
set_demand(name_c, cs)
|
||
# 修改节点(junction)的水量模式(pattern)
|
||
if modify_junction_damand_pattern:
|
||
for pattern_name in modify_junction_damand_pattern.keys():
|
||
if not np.isnan(modify_junction_damand_pattern[pattern_name][0]):
|
||
junction_pattern = get_pattern(name_c, pattern_name)
|
||
junction_pattern["factors"][
|
||
modify_index : modify_index
|
||
+ len(modify_junction_damand_pattern[pattern_name])
|
||
] = modify_junction_damand_pattern[pattern_name]
|
||
cs = ChangeSet()
|
||
cs.append(junction_pattern)
|
||
set_pattern(name_c, cs)
|
||
# 修改工频水泵(fixed_pump)的pattern
|
||
if modify_fixed_pump_pattern:
|
||
for pump_name in modify_fixed_pump_pattern.keys():
|
||
if not np.isnan(modify_fixed_pump_pattern[pump_name][0]):
|
||
pump_pattern = get_pattern(
|
||
name_c, get_pump(name_c, pump_name)["pattern"]
|
||
)
|
||
pump_pattern["factors"][
|
||
modify_index
|
||
: modify_index + len(modify_fixed_pump_pattern[pump_name])
|
||
] = modify_fixed_pump_pattern[pump_name]
|
||
cs = ChangeSet()
|
||
cs.append(pump_pattern)
|
||
set_pattern(name_c, cs)
|
||
# 修改变频水泵(variable_pump)的pattern
|
||
if modify_variable_pump_pattern:
|
||
for pump_name in modify_variable_pump_pattern.keys():
|
||
if not np.isnan(modify_variable_pump_pattern[pump_name][0]):
|
||
# 给 list 中的所有元素除以 50Hz
|
||
modified_values = [
|
||
value / 50 for value in modify_variable_pump_pattern[pump_name]
|
||
]
|
||
pump_pattern = get_pattern(
|
||
name_c, get_pump(name_c, pump_name)["pattern"]
|
||
)
|
||
pump_pattern["factors"][
|
||
modify_index : modify_index + len(modified_values)
|
||
] = modified_values
|
||
cs = ChangeSet()
|
||
cs.append(pump_pattern)
|
||
set_pattern(name_c, cs)
|
||
# 显式阀门控制优先于旧的开度参数。
|
||
if valve_control is not None:
|
||
_apply_valve_control(name_c, valve_control)
|
||
# 保留原开度参数逻辑,兼容现有方案调用。
|
||
elif modify_valve_opening:
|
||
for valve_name in modify_valve_opening.keys():
|
||
if not np.isnan(modify_valve_opening[valve_name]):
|
||
valve_status = get_status(name_c, valve_name)
|
||
if modify_valve_opening[valve_name] == 0:
|
||
valve_status["status"] = "CLOSED"
|
||
valve_status["setting"] = 0
|
||
elif modify_valve_opening[valve_name] < 1:
|
||
valve_status["status"] = "OPEN"
|
||
valve_status["setting"] = 0.1036 * pow(
|
||
modify_valve_opening[valve_name], -3.105
|
||
)
|
||
elif modify_valve_opening[valve_name] == 1:
|
||
valve_status["status"] = "OPEN"
|
||
valve_status["setting"] = 0
|
||
cs = ChangeSet()
|
||
cs.append(valve_status)
|
||
set_status(name_c, cs)
|
||
# 运行并返回结果
|
||
result_data = json.loads(run_project(name_c))
|
||
refresh_materialized_views_after_commit(name_c)
|
||
time_cost_end = time.perf_counter()
|
||
print(
|
||
"{} -- Hydraulic simulation finished, cost time: {:.2f} s.".format(
|
||
datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S"),
|
||
time_cost_end - time_cost_start,
|
||
)
|
||
)
|
||
output_data = result_data.get("output")
|
||
if not isinstance(output_data, dict):
|
||
raise RuntimeError("run_project did not return JSON output content")
|
||
node_result = output_data.get("node_results")
|
||
link_result = output_data.get("link_results")
|
||
if node_result is None or link_result is None:
|
||
raise RuntimeError("run_project output missing node_results or link_results")
|
||
|
||
# link_flow = []
|
||
# for link in link_result:
|
||
# link_flow.append(link['result'][-1]['flow'])
|
||
# print(link_flow)
|
||
times_info = output_data.get("times") or {}
|
||
num_periods_result = times_info.get("num_periods")
|
||
if num_periods_result is None:
|
||
raise RuntimeError("run_project output missing times.num_periods")
|
||
print("simulation_type", simulation_type)
|
||
print("before store result")
|
||
# print(num_periods_result)
|
||
# print(node_result)
|
||
# 存储
|
||
starttime = time.time()
|
||
# 临时处理输入的name问题,后续需要优化,需要传递/获取iot数据库的名字
|
||
db_name = result_db_name or name
|
||
if simulation_type.upper() == "REALTIME":
|
||
TimescaleInternalStorage.store_realtime_simulation(
|
||
node_result, link_result, modify_pattern_start_time, db_name=db_name
|
||
)
|
||
elif simulation_type.upper() == "EXTENDED":
|
||
result_timestep_seconds = times_info.get("report_step")
|
||
if result_timestep_seconds is None:
|
||
raise RuntimeError("run_project output missing times.report_step")
|
||
if not scheme_type or not scheme_name:
|
||
raise ValueError("extended simulation requires analysis run type and name")
|
||
detail = dict(scheme_detail or {})
|
||
result_units = output_data.get("units")
|
||
if isinstance(result_units, dict):
|
||
detail["result_units"] = {
|
||
key: str(result_units[key]).strip()
|
||
for key in ("flow", "pressure")
|
||
if result_units.get(key)
|
||
}
|
||
run_id = create_analysis_run(
|
||
name=db_name,
|
||
scheme_name=scheme_name,
|
||
scheme_type=scheme_type,
|
||
username=scheme_username,
|
||
scheme_start_time=modify_pattern_start_time,
|
||
scheme_detail=detail,
|
||
)
|
||
try:
|
||
TimescaleInternalStorage.store_analysis_simulation(
|
||
run_id,
|
||
node_result,
|
||
link_result,
|
||
modify_pattern_start_time,
|
||
num_periods_result,
|
||
result_timestep_seconds,
|
||
db_name=db_name,
|
||
)
|
||
except Exception:
|
||
try:
|
||
update_analysis_run(
|
||
db_name,
|
||
run_id,
|
||
status="failed",
|
||
username=scheme_username,
|
||
scheme_detail=detail,
|
||
)
|
||
except Exception:
|
||
logging.exception("failed to mark analysis run %s as failed", run_id)
|
||
raise
|
||
update_analysis_run(
|
||
db_name,
|
||
run_id,
|
||
status="completed",
|
||
username=scheme_username,
|
||
scheme_detail=detail,
|
||
)
|
||
endtime = time.time()
|
||
logging.info("store time: %f", endtime - starttime)
|
||
# 暂不需要再次存储 SCADA 模拟信息
|
||
# TimescaleInternalQueries.fill_scheme_simulation_result_to_SCADA(scheme_type=scheme_type, scheme_name=scheme_name)
|
||
|
||
print("after store result")
|
||
return run_id if simulation_type.upper() == "EXTENDED" else None
|