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:
2026-08-25 18:35:05 +08:00
parent fdbcc5c033
commit fa188af0b1
181 changed files with 8446 additions and 33546 deletions
+38 -56
View File
@@ -5,15 +5,16 @@ from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import pandas as pd
from psycopg import AsyncConnection
from uuid import UUID
import app.native.wndb as wndb
from app.algorithms.cleaning.flow import clean_flow_data_df_kf
from app.algorithms.cleaning.pressure import clean_pressure_data_df_km
from app.algorithms.health.analyzer import PipelineHealthAnalyzer
from app.infra.db.postgresql.scada import ScadaInfoRepository
from app.infra.db.timescaledb.repositories.realtime import RealtimeRepository
from app.infra.db.timescaledb.repositories.scheme import SchemeRepository
from app.infra.db.timescaledb.repositories.analysis import AnalysisResultsRepository
from app.infra.db.timescaledb.repositories.scada import ScadaRepository
from app.native.wndb.model.pipes import get_pipes_by_property
class CompositeQueries:
@@ -26,7 +27,7 @@ class CompositeQueries:
postgres_conn: AsyncConnection,
) -> Dict[str, Dict[str, Any]]:
scadas = await ScadaInfoRepository.get_scadas(postgres_conn)
return {scada["id"]: scada for scada in scadas}
return {scada["device_id"]: scada for scada in scadas}
@staticmethod
async def get_scada_associated_realtime_simulation_data(
@@ -63,8 +64,12 @@ class CompositeQueries:
if not target_scada:
raise ValueError(f"SCADA device {device_id} not found")
element_id = target_scada["associated_element_id"]
scada_type = target_scada["type"]
scada_type = target_scada["device_type"]
element_id = (
target_scada["link_id"]
if scada_type in {"pipe_flow", "flow"}
else target_scada["node_id"]
)
if scada_type == "pipe_flow":
# 查询 link 模拟数据
@@ -85,17 +90,16 @@ class CompositeQueries:
return result
@staticmethod
async def get_scada_associated_scheme_simulation_data(
async def get_scada_associated_analysis_simulation_data(
timescale_conn: AsyncConnection,
postgres_conn: AsyncConnection,
device_ids: List[str],
start_time: datetime,
end_time: datetime,
scheme_type: str,
scheme_name: str,
run_id: UUID,
) -> Dict[str, List[Dict[str, Any]]]:
"""
获取 SCADA 关联的 link/node scheme 模拟值
获取 SCADA 关联的 link/node 分析模拟值
根据传入的 SCADA device_ids,找到关联的 link/node
并根据对应的 type,查询对应的模拟数据
@@ -121,30 +125,22 @@ class CompositeQueries:
if not target_scada:
raise ValueError(f"SCADA device {device_id} not found")
element_id = target_scada["associated_element_id"]
scada_type = target_scada["type"]
scada_type = target_scada["device_type"]
element_id = (
target_scada["link_id"]
if scada_type in {"pipe_flow", "flow"}
else target_scada["node_id"]
)
if scada_type == "pipe_flow":
# 查询 link 模拟数据
res = await SchemeRepository.get_link_field_by_scheme_and_time_range(
timescale_conn,
scheme_type,
scheme_name,
start_time,
end_time,
element_id,
"flow",
res = await AnalysisResultsRepository.get_link_series(
timescale_conn, run_id, element_id, start_time, end_time, "flow"
)
elif scada_type == "pressure":
# 查询 node 模拟数据
res = await SchemeRepository.get_node_field_by_scheme_and_time_range(
timescale_conn,
scheme_type,
scheme_name,
start_time,
end_time,
element_id,
"pressure",
res = await AnalysisResultsRepository.get_node_series(
timescale_conn, run_id, element_id, start_time, end_time, "pressure"
)
else:
raise ValueError(f"Unknown SCADA type: {scada_type}")
@@ -201,16 +197,15 @@ class CompositeQueries:
return result
@staticmethod
async def get_scheme_simulation_data(
async def get_analysis_simulation_data(
timescale_conn: AsyncConnection,
feature_infos: List[Tuple[str, str]],
start_time: datetime,
end_time: datetime,
scheme_type: str,
scheme_name: str,
run_id: UUID,
) -> Dict[str, List[Dict[str, Any]]]:
"""
获取 link/node scheme 模拟值
获取 link/node 分析模拟值
根据传入的 feature_infos,找到关联的 link/node
并根据对应的 type,查询对应的模拟数据
@@ -220,8 +215,7 @@ class CompositeQueries:
feature_infos: 传入的 feature 信息列表,包含 (element_id, type)
start_time: 开始时间
end_time: 结束时间
scheme_type: 工况类型
scheme_name: 工况名称
run_id: 分析运行 ID
Returns:
模拟数据字典,以 feature_id 为键,值为数据列表,每个数据包含 time, value 和 feature_id
@@ -233,25 +227,13 @@ class CompositeQueries:
for feature_id, feature_type in feature_infos:
if feature_type.lower() == "pipe":
# 查询 link 模拟数据
res = await SchemeRepository.get_link_field_by_scheme_and_time_range(
timescale_conn,
scheme_type,
scheme_name,
start_time,
end_time,
feature_id,
"flow",
res = await AnalysisResultsRepository.get_link_series(
timescale_conn, run_id, feature_id, start_time, end_time, "flow"
)
elif feature_type.lower() == "junction":
# 查询 node 模拟数据
res = await SchemeRepository.get_node_field_by_scheme_and_time_range(
timescale_conn,
scheme_type,
scheme_name,
start_time,
end_time,
feature_id,
"pressure",
res = await AnalysisResultsRepository.get_node_series(
timescale_conn, run_id, feature_id, start_time, end_time, "pressure"
)
else:
raise ValueError(f"Unknown type: {feature_type}")
@@ -296,7 +278,7 @@ class CompositeQueries:
(
scada
for scada in scada_by_id.values()
if scada["associated_element_id"] == element_id
if (scada.get("node_id") or scada.get("link_id")) == element_id
),
None,
)
@@ -304,7 +286,7 @@ class CompositeQueries:
if not associated_scada:
return None
device_id = associated_scada["id"]
device_id = associated_scada["device_id"]
data_field = "cleaned_value" if use_cleaned else "monitored_value"
@@ -358,7 +340,7 @@ class CompositeQueries:
unsupported_ids = [
device_id
for device_id in device_ids
if scada_by_id[device_id]["type"] not in supported_types
if scada_by_id[device_id]["device_type"] not in supported_types
]
if unsupported_ids:
raise ValueError(
@@ -368,7 +350,7 @@ class CompositeQueries:
device_ids = [
device_id
for device_id, info in scada_by_id.items()
if info["type"] in supported_types
if info["device_type"] in supported_types
]
if not device_ids:
@@ -409,12 +391,12 @@ class CompositeQueries:
pressure_ids = [
device_id
for device_id in df.columns
if scada_by_id[device_id]["type"] == "pressure"
if scada_by_id[device_id]["device_type"] == "pressure"
]
flow_ids = [
device_id
for device_id in df.columns
if scada_by_id[device_id]["type"] in {"pipe_flow", "flow"}
if scada_by_id[device_id]["device_type"] in {"pipe_flow", "flow"}
]
updated_rows = 0
@@ -497,7 +479,7 @@ class CompositeQueries:
# 批量查询这些管道的详细信息
fields = ["id", "diameter", "node1", "node2"]
all_links = wndb.get_pipes_by_property(network_name, fields=fields)
all_links = get_pipes_by_property(network_name, fields=fields)
# 转换为字典以快速查找
links_dict = {link["id"]: link for link in all_links}