659 lines
24 KiB
Python
659 lines
24 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
import pandas as pd
|
|
|
|
from app.algorithms.burst_location import run_burst_location
|
|
from app.infra.db.timescaledb.internal_queries import InternalQueries
|
|
from app.services.scheme_management import (
|
|
query_burst_location_scheme_detail,
|
|
query_burst_location_schemes,
|
|
scheme_name_exists,
|
|
store_scheme_info,
|
|
)
|
|
from app.services.tjnetwork import dump_inp, get_all_scada_info
|
|
from app.services.time_api import extract_date, parse_utc_time, utc_now
|
|
|
|
SeriesInput = pd.Series | dict[str, Any] | list[dict[str, Any]]
|
|
FLOW_SCADA_TYPES = {"pipe_flow", "flow", "demand"}
|
|
SIMULATION_DATA_SOURCES = {"monitoring", "simulation"}
|
|
DEFAULT_SIMULATION_SCHEME_TYPE = "burst_analysis"
|
|
|
|
|
|
def _normalize_series(data: SeriesInput, field_name: str) -> pd.Series:
|
|
if isinstance(data, pd.Series):
|
|
series = data.copy()
|
|
elif isinstance(data, dict):
|
|
series = pd.Series(data, dtype=float)
|
|
elif isinstance(data, list):
|
|
if len(data) == 0:
|
|
return pd.Series(dtype=float)
|
|
frame = pd.DataFrame(data)
|
|
if not {"id", "value"}.issubset(frame.columns):
|
|
raise ValueError(f"{field_name} list item must include 'id' and 'value'.")
|
|
series = pd.Series(
|
|
frame["value"].values, index=frame["id"].astype(str).values, dtype=float
|
|
)
|
|
else:
|
|
raise ValueError(f"Unsupported data format for {field_name}.")
|
|
|
|
series.index = series.index.map(str)
|
|
return pd.to_numeric(series, errors="raise")
|
|
|
|
|
|
def run_burst_location_by_network(
|
|
*,
|
|
network: str,
|
|
username: str,
|
|
data_source: str = "monitoring",
|
|
burst_leakage: float,
|
|
pressure_scada_ids: list[str] | None = None,
|
|
burst_pressure: SeriesInput | None = None,
|
|
normal_pressure: SeriesInput | None = None,
|
|
flow_scada_ids: list[str] | None = None,
|
|
burst_flow: SeriesInput | None = None,
|
|
normal_flow: SeriesInput | None = None,
|
|
min_dpressure: float = 2.0,
|
|
basic_pressure: float = 10.0,
|
|
scada_burst_start: datetime | str | None = None,
|
|
scada_burst_end: datetime | str | None = None,
|
|
use_scada_flow: bool = False,
|
|
scheme_name: str | None = None,
|
|
simulation_scheme_name: str | None = None,
|
|
simulation_scheme_type: str | None = None,
|
|
) -> dict[str, Any]:
|
|
if not network:
|
|
raise ValueError("network is required.")
|
|
normalized_data_source = _normalize_data_source(
|
|
data_source, simulation_scheme_name=simulation_scheme_name
|
|
)
|
|
resolved_simulation_scheme_type = (
|
|
simulation_scheme_type or DEFAULT_SIMULATION_SCHEME_TYPE
|
|
)
|
|
|
|
selected_pressure_ids = (
|
|
_dedupe_ids(pressure_scada_ids)
|
|
if pressure_scada_ids
|
|
else _get_sensor_nodes(network, data_type="pressure")
|
|
)
|
|
if not selected_pressure_ids:
|
|
raise ValueError("未提供有效压力传感器,且系统未识别到可用压力传感器。")
|
|
|
|
use_scada_pressure = any(
|
|
value is not None
|
|
for value in [
|
|
scada_burst_start,
|
|
scada_burst_end,
|
|
]
|
|
)
|
|
if use_scada_pressure:
|
|
burst_start_dt, burst_end_dt = _validate_scada_windows(
|
|
scada_burst_start=scada_burst_start,
|
|
scada_burst_end=scada_burst_end,
|
|
)
|
|
if normalized_data_source == "simulation":
|
|
if not simulation_scheme_name:
|
|
raise ValueError("模拟方案模式必须提供 simulation_scheme_name。")
|
|
(
|
|
burst_pressure_series,
|
|
burst_pressure_samples,
|
|
) = _build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_pressure_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="pressure",
|
|
series_name="burst_pressure",
|
|
simulation_source="scheme",
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
(
|
|
normal_pressure_series,
|
|
normal_pressure_samples,
|
|
) = _build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_pressure_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="pressure",
|
|
series_name="normal_pressure",
|
|
simulation_source="realtime",
|
|
simulation_scheme_name=None,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
observed_source = "simulation_scheme_burst_realtime_normal_timerange"
|
|
else:
|
|
(
|
|
burst_pressure_series,
|
|
burst_pressure_samples,
|
|
) = _build_observed_series_from_scada(
|
|
network=network,
|
|
sensor_ids=selected_pressure_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="pressure",
|
|
series_name="burst_pressure",
|
|
)
|
|
(
|
|
normal_pressure_series,
|
|
normal_pressure_samples,
|
|
) = _build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_pressure_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="pressure",
|
|
series_name="normal_pressure",
|
|
simulation_source="realtime",
|
|
simulation_scheme_name=None,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
observed_source = "scada_burst_realtime_normal_timerange"
|
|
else:
|
|
if burst_pressure is None or normal_pressure is None:
|
|
raise ValueError(
|
|
"未提供 burst_pressure/normal_pressure,且未提供完整 SCADA 时间窗参数。"
|
|
)
|
|
burst_pressure_series = _normalize_series(burst_pressure, "burst_pressure")
|
|
normal_pressure_series = _normalize_series(normal_pressure, "normal_pressure")
|
|
burst_pressure_samples = 1
|
|
normal_pressure_samples = 1
|
|
observed_source = "request_payload"
|
|
burst_start_dt = burst_end_dt = None
|
|
|
|
selected_flow_ids: list[str] | None = None
|
|
burst_flow_series: pd.Series | None = None
|
|
normal_flow_series: pd.Series | None = None
|
|
use_flow_scada_source = use_scada_pressure and (
|
|
use_scada_flow or flow_scada_ids is not None
|
|
)
|
|
if use_flow_scada_source:
|
|
selected_flow_ids = (
|
|
_dedupe_ids(flow_scada_ids)
|
|
if flow_scada_ids is not None
|
|
else _get_sensor_nodes(network, data_type="flow")
|
|
)
|
|
if not selected_flow_ids:
|
|
raise ValueError("未找到可用流量传感器,无法从 SCADA 查询流量数据。")
|
|
if normalized_data_source == "simulation":
|
|
if not simulation_scheme_name:
|
|
raise ValueError("模拟方案模式必须提供 simulation_scheme_name。")
|
|
burst_flow_series, burst_flow_samples = (
|
|
_build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_flow_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="flow",
|
|
series_name="burst_flow",
|
|
simulation_source="scheme",
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
)
|
|
normal_flow_series, normal_flow_samples = (
|
|
_build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_flow_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="flow",
|
|
series_name="normal_flow",
|
|
simulation_source="realtime",
|
|
simulation_scheme_name=None,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
)
|
|
else:
|
|
burst_flow_series, burst_flow_samples = _build_observed_series_from_scada(
|
|
network=network,
|
|
sensor_ids=selected_flow_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="flow",
|
|
series_name="burst_flow",
|
|
)
|
|
normal_flow_series, normal_flow_samples = (
|
|
_build_observed_series_from_simulation(
|
|
network=network,
|
|
sensor_ids=selected_flow_ids,
|
|
start_dt=burst_start_dt,
|
|
end_dt=burst_end_dt,
|
|
data_type="flow",
|
|
series_name="normal_flow",
|
|
simulation_source="realtime",
|
|
simulation_scheme_name=None,
|
|
simulation_scheme_type=resolved_simulation_scheme_type,
|
|
)
|
|
)
|
|
else:
|
|
if flow_scada_ids is not None:
|
|
selected_flow_ids = _dedupe_ids(flow_scada_ids)
|
|
burst_flow_series = (
|
|
_normalize_series(burst_flow, "burst_flow")
|
|
if burst_flow is not None
|
|
else None
|
|
)
|
|
normal_flow_series = (
|
|
_normalize_series(normal_flow, "normal_flow")
|
|
if normal_flow is not None
|
|
else None
|
|
)
|
|
burst_flow_samples = 1 if burst_flow_series is not None else 0
|
|
normal_flow_samples = 1 if normal_flow_series is not None else 0
|
|
|
|
inp_path = _prepare_burst_inp(network)
|
|
result = run_burst_location(
|
|
wn_inp_path=inp_path,
|
|
pressure_scada_ids=selected_pressure_ids,
|
|
burst_pressure=burst_pressure_series,
|
|
normal_pressure=normal_pressure_series,
|
|
burst_leakage=burst_leakage,
|
|
flow_scada_ids=selected_flow_ids,
|
|
burst_flow=burst_flow_series,
|
|
normal_flow=normal_flow_series,
|
|
min_dpressure=min_dpressure,
|
|
basic_pressure=basic_pressure,
|
|
)
|
|
|
|
payload: dict[str, Any] = {
|
|
**result,
|
|
"network": network,
|
|
"data_source": normalized_data_source,
|
|
"pressure_scada_ids": selected_pressure_ids,
|
|
"flow_scada_ids": selected_flow_ids or [],
|
|
"observed_source": observed_source,
|
|
"pressure_samples": {
|
|
"burst": burst_pressure_samples,
|
|
"normal": normal_pressure_samples,
|
|
},
|
|
"flow_samples": {"burst": burst_flow_samples, "normal": normal_flow_samples},
|
|
"burst_leakage": burst_leakage,
|
|
"min_dpressure": min_dpressure,
|
|
"basic_pressure": basic_pressure,
|
|
}
|
|
if use_scada_pressure:
|
|
payload["scada_window"] = {
|
|
"burst_start": burst_start_dt.isoformat(),
|
|
"burst_end": burst_end_dt.isoformat(),
|
|
}
|
|
if normalized_data_source == "simulation":
|
|
payload["simulation_scheme"] = {
|
|
"name": simulation_scheme_name,
|
|
"type": resolved_simulation_scheme_type,
|
|
}
|
|
if scheme_name:
|
|
_store_burst_scheme(
|
|
network=network,
|
|
scheme_name=scheme_name,
|
|
username=username,
|
|
payload=payload,
|
|
burst_leakage=burst_leakage,
|
|
min_dpressure=min_dpressure,
|
|
basic_pressure=basic_pressure,
|
|
)
|
|
return payload
|
|
|
|
|
|
def list_burst_location_schemes(
|
|
network: str, query_date: datetime | str | None = None
|
|
) -> list[dict[str, Any]]:
|
|
parsed_date = extract_date(query_date, field_name="query_date") if query_date is not None else None
|
|
return query_burst_location_schemes(
|
|
name=network, network=network, query_date=parsed_date
|
|
)
|
|
|
|
|
|
def get_burst_location_scheme_detail(network: str, scheme_name: str) -> dict[str, Any]:
|
|
result = query_burst_location_scheme_detail(network, scheme_name)
|
|
if not result:
|
|
raise ValueError(f"未找到爆管定位方案: {scheme_name}")
|
|
return result
|
|
|
|
|
|
def _store_burst_scheme(
|
|
*,
|
|
network: str,
|
|
scheme_name: str,
|
|
username: str,
|
|
payload: dict[str, Any],
|
|
burst_leakage: float,
|
|
min_dpressure: float,
|
|
basic_pressure: float,
|
|
) -> None:
|
|
if scheme_name_exists(network, scheme_name):
|
|
raise ValueError(f"方案名称已存在: {scheme_name}")
|
|
|
|
now_iso = utc_now().isoformat()
|
|
scheme_detail = {
|
|
"network": network,
|
|
"pressure_scada_ids": payload.get("pressure_scada_ids", []),
|
|
"flow_scada_ids": payload.get("flow_scada_ids", []),
|
|
"observed_source": payload.get("observed_source"),
|
|
"algorithm_params": {
|
|
"burst_leakage": burst_leakage,
|
|
"min_dpressure": min_dpressure,
|
|
"basic_pressure": basic_pressure,
|
|
},
|
|
"scada_window": payload.get("scada_window"),
|
|
"result_summary": {
|
|
"located_pipe": payload.get("located_pipe"),
|
|
"simulation_times": payload.get("simulation_times"),
|
|
"similarity_mode": payload.get("similarity_mode"),
|
|
},
|
|
"result_payload": payload,
|
|
}
|
|
store_scheme_info(
|
|
name=network,
|
|
scheme_name=scheme_name,
|
|
scheme_type="burst_location",
|
|
username=username,
|
|
scheme_start_time=now_iso,
|
|
scheme_detail=scheme_detail,
|
|
)
|
|
|
|
|
|
def _validate_scada_windows(
|
|
*,
|
|
scada_burst_start: datetime | str | None,
|
|
scada_burst_end: datetime | str | None,
|
|
) -> tuple[datetime, datetime]:
|
|
values = [scada_burst_start, scada_burst_end]
|
|
if any(v is None for v in values):
|
|
raise ValueError(
|
|
"使用后端 SCADA 查询时,必须同时提供 scada_burst_start/scada_burst_end。"
|
|
)
|
|
burst_start_dt = _to_datetime(scada_burst_start)
|
|
burst_end_dt = _to_datetime(scada_burst_end)
|
|
if burst_start_dt >= burst_end_dt:
|
|
raise ValueError(
|
|
"爆管时段 SCADA 时间窗非法:scada_burst_start 必须早于 scada_burst_end。"
|
|
)
|
|
return burst_start_dt, burst_end_dt
|
|
|
|
|
|
def _build_observed_series_from_scada(
|
|
*,
|
|
network: str,
|
|
sensor_ids: list[str],
|
|
start_dt: datetime,
|
|
end_dt: datetime,
|
|
data_type: str,
|
|
series_name: str,
|
|
) -> tuple[pd.Series, int]:
|
|
scada_mapping = _build_scada_mapping(network=network, data_type=data_type)
|
|
missing_ids = [
|
|
sensor_id for sensor_id in sensor_ids if sensor_id not in scada_mapping
|
|
]
|
|
if missing_ids:
|
|
preview = ", ".join(missing_ids[:10])
|
|
raise ValueError(f"{series_name} 缺少可用 SCADA 映射: {preview}")
|
|
|
|
query_ids = [scada_mapping[sensor_id] for sensor_id in sensor_ids]
|
|
scada_data = InternalQueries.query_scada_by_ids_timerange(
|
|
db_name=network,
|
|
device_ids=query_ids,
|
|
start_time=start_dt.isoformat(),
|
|
end_time=end_dt.isoformat(),
|
|
)
|
|
values: dict[str, float] = {}
|
|
sample_counts: list[int] = []
|
|
for sensor_id, query_id in zip(sensor_ids, query_ids):
|
|
records = scada_data.get(query_id, [])
|
|
numeric_values = [
|
|
float(item["value"]) for item in records if item.get("value") is not None
|
|
]
|
|
if not numeric_values:
|
|
raise ValueError(f"{series_name} 在时间窗内无有效数据: {sensor_id}")
|
|
values[sensor_id] = float(sum(numeric_values) / len(numeric_values))
|
|
sample_counts.append(len(numeric_values))
|
|
|
|
return pd.Series(values, dtype=float), min(sample_counts)
|
|
|
|
|
|
def _build_observed_series_from_simulation(
|
|
*,
|
|
network: str,
|
|
sensor_ids: list[str],
|
|
start_dt: datetime,
|
|
end_dt: datetime,
|
|
data_type: str,
|
|
series_name: str,
|
|
simulation_source: str,
|
|
simulation_scheme_name: str | None,
|
|
simulation_scheme_type: str,
|
|
) -> tuple[pd.Series, int]:
|
|
sensor_metadata = _build_sensor_metadata(network=network, data_type=data_type)
|
|
missing_ids = [
|
|
sensor_id for sensor_id in sensor_ids if sensor_id not in sensor_metadata
|
|
]
|
|
if missing_ids:
|
|
preview = ", ".join(missing_ids[:10])
|
|
raise ValueError(f"{series_name} 缺少可用 SCADA 映射: {preview}")
|
|
|
|
simulation_data = _query_simulation_data_by_sensor_ids(
|
|
network=network,
|
|
sensor_ids=sensor_ids,
|
|
sensor_metadata=sensor_metadata,
|
|
start_dt=start_dt,
|
|
end_dt=end_dt,
|
|
data_type=data_type,
|
|
simulation_source=simulation_source,
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=simulation_scheme_type,
|
|
)
|
|
values: dict[str, float] = {}
|
|
sample_counts: list[int] = []
|
|
for sensor_id in sensor_ids:
|
|
records = simulation_data.get(sensor_id, [])
|
|
numeric_values = [
|
|
float(item["value"]) for item in records if item.get("value") is not None
|
|
]
|
|
if not numeric_values:
|
|
raise ValueError(f"{series_name} 在时间窗内无有效模拟数据: {sensor_id}")
|
|
values[sensor_id] = float(sum(numeric_values) / len(numeric_values))
|
|
sample_counts.append(len(numeric_values))
|
|
|
|
return pd.Series(values, dtype=float), min(sample_counts)
|
|
|
|
|
|
def _query_simulation_data_by_sensor_ids(
|
|
*,
|
|
network: str,
|
|
sensor_ids: list[str],
|
|
sensor_metadata: dict[str, dict[str, str]],
|
|
start_dt: datetime,
|
|
end_dt: datetime,
|
|
data_type: str,
|
|
simulation_source: str,
|
|
simulation_scheme_name: str | None,
|
|
simulation_scheme_type: str,
|
|
) -> dict[str, list[dict[str, Any]]]:
|
|
if simulation_source not in {"scheme", "realtime"}:
|
|
raise ValueError(f"Unsupported simulation_source: {simulation_source}")
|
|
|
|
result: dict[str, list[dict[str, Any]]] = {
|
|
sensor_id: [] for sensor_id in sensor_ids
|
|
}
|
|
if data_type == "pressure":
|
|
result.update(
|
|
_query_simulation_values(
|
|
network=network,
|
|
element_ids=sensor_ids,
|
|
element_type="node",
|
|
field="pressure",
|
|
start_dt=start_dt,
|
|
end_dt=end_dt,
|
|
simulation_source=simulation_source,
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=simulation_scheme_type,
|
|
)
|
|
)
|
|
return result
|
|
|
|
if data_type != "flow":
|
|
raise ValueError(f"Unsupported data_type: {data_type}")
|
|
|
|
link_ids: list[str] = []
|
|
demand_ids: list[str] = []
|
|
unsupported_ids: list[str] = []
|
|
for sensor_id in sensor_ids:
|
|
scada_type = sensor_metadata[sensor_id]["scada_type"]
|
|
if scada_type in {"pipe_flow", "flow"}:
|
|
link_ids.append(sensor_id)
|
|
elif scada_type == "demand":
|
|
demand_ids.append(sensor_id)
|
|
else:
|
|
unsupported_ids.append(f"{sensor_id}({scada_type})")
|
|
if unsupported_ids:
|
|
preview = ", ".join(unsupported_ids[:10])
|
|
raise ValueError(f"flow 模拟数据暂不支持以下 SCADA 类型: {preview}")
|
|
|
|
if link_ids:
|
|
result.update(
|
|
_query_simulation_values(
|
|
network=network,
|
|
element_ids=link_ids,
|
|
element_type="link",
|
|
field="flow",
|
|
start_dt=start_dt,
|
|
end_dt=end_dt,
|
|
simulation_source=simulation_source,
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=simulation_scheme_type,
|
|
)
|
|
)
|
|
if demand_ids:
|
|
result.update(
|
|
_query_simulation_values(
|
|
network=network,
|
|
element_ids=demand_ids,
|
|
element_type="node",
|
|
field="actual_demand",
|
|
start_dt=start_dt,
|
|
end_dt=end_dt,
|
|
simulation_source=simulation_source,
|
|
simulation_scheme_name=simulation_scheme_name,
|
|
simulation_scheme_type=simulation_scheme_type,
|
|
)
|
|
)
|
|
return result
|
|
|
|
|
|
def _query_simulation_values(
|
|
*,
|
|
network: str,
|
|
element_ids: list[str],
|
|
element_type: str,
|
|
field: str,
|
|
start_dt: datetime,
|
|
end_dt: datetime,
|
|
simulation_source: str,
|
|
simulation_scheme_name: str | None,
|
|
simulation_scheme_type: str,
|
|
) -> dict[str, list[dict[str, Any]]]:
|
|
if not element_ids:
|
|
return {}
|
|
if simulation_source == "scheme":
|
|
if not simulation_scheme_name:
|
|
raise ValueError("读取方案模拟数据时必须提供 simulation_scheme_name。")
|
|
return InternalQueries.query_scheme_simulation_by_ids_timerange(
|
|
db_name=network,
|
|
scheme_type=simulation_scheme_type,
|
|
scheme_name=simulation_scheme_name,
|
|
element_ids=element_ids,
|
|
start_time=start_dt.isoformat(),
|
|
end_time=end_dt.isoformat(),
|
|
element_type=element_type,
|
|
field=field,
|
|
)
|
|
if simulation_source == "realtime":
|
|
return InternalQueries.query_realtime_simulation_by_ids_timerange(
|
|
db_name=network,
|
|
element_ids=element_ids,
|
|
start_time=start_dt.isoformat(),
|
|
end_time=end_dt.isoformat(),
|
|
element_type=element_type,
|
|
field=field,
|
|
)
|
|
raise ValueError(f"Unsupported simulation_source: {simulation_source}")
|
|
|
|
|
|
def _build_sensor_metadata(network: str, data_type: str) -> dict[str, dict[str, str]]:
|
|
metadata: dict[str, dict[str, str]] = {}
|
|
for item in get_all_scada_info(network):
|
|
scada_type = str(item.get("type", "")).lower()
|
|
if data_type == "pressure":
|
|
if scada_type != "pressure":
|
|
continue
|
|
elif data_type == "flow":
|
|
if scada_type not in FLOW_SCADA_TYPES:
|
|
continue
|
|
else:
|
|
raise ValueError(f"Unsupported data_type: {data_type}")
|
|
element_id = item.get("associated_element_id")
|
|
query_id = item.get("api_query_id")
|
|
if (
|
|
isinstance(element_id, str)
|
|
and element_id
|
|
and isinstance(query_id, str)
|
|
and query_id
|
|
):
|
|
metadata[element_id] = {"query_id": query_id, "scada_type": scada_type}
|
|
return metadata
|
|
|
|
|
|
def _build_scada_mapping(network: str, data_type: str) -> dict[str, str]:
|
|
metadata = _build_sensor_metadata(network=network, data_type=data_type)
|
|
return {element_id: item["query_id"] for element_id, item in metadata.items()}
|
|
|
|
|
|
def _normalize_data_source(
|
|
data_source: str | None, simulation_scheme_name: str | None = None
|
|
) -> str:
|
|
normalized = str(data_source or "").strip().lower()
|
|
if not normalized:
|
|
return "simulation" if simulation_scheme_name else "monitoring"
|
|
if normalized not in SIMULATION_DATA_SOURCES:
|
|
allowed_sources = ", ".join(sorted(SIMULATION_DATA_SOURCES))
|
|
raise ValueError(
|
|
f"Unsupported data_source: {data_source}. Allowed: {allowed_sources}"
|
|
)
|
|
return normalized
|
|
|
|
|
|
def _get_sensor_nodes(network: str, data_type: str) -> list[str]:
|
|
mapping = _build_scada_mapping(network=network, data_type=data_type)
|
|
sensor_ids = sorted(mapping.keys())
|
|
if not sensor_ids:
|
|
type_name = "压力" if data_type == "pressure" else "流量"
|
|
raise ValueError(f"未找到{type_name}传感器对应节点(scada_info.type)。")
|
|
return sensor_ids
|
|
|
|
|
|
def _dedupe_ids(ids: list[str] | None) -> list[str]:
|
|
if ids is None:
|
|
return []
|
|
return list(dict.fromkeys([str(item) for item in ids if item]))
|
|
|
|
|
|
def _to_datetime(value: datetime | str) -> datetime:
|
|
return parse_utc_time(value)
|
|
|
|
|
|
def _prepare_burst_inp(network: str) -> str:
|
|
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
db_inp_dir = os.path.join(project_root, "db_inp")
|
|
os.makedirs(db_inp_dir, exist_ok=True)
|
|
inp_path = os.path.join(db_inp_dir, f"{network}.burst.inp")
|
|
if os.path.isfile(inp_path) and os.path.getsize(inp_path) > 0:
|
|
return inp_path
|
|
dump_inp(network, inp_path, "2")
|
|
if not os.path.isfile(inp_path) or os.path.getsize(inp_path) <= 0:
|
|
raise ValueError(f"爆管定位 INP 文件无效: {inp_path}")
|
|
return inp_path
|