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.
190 lines
6.6 KiB
Python
190 lines
6.6 KiB
Python
from typing import Any
|
|
from uuid import UUID, uuid4
|
|
|
|
from psycopg.rows import dict_row
|
|
from psycopg.types.json import Jsonb
|
|
|
|
from app.native.wndb.core.connection import project_connection
|
|
|
|
|
|
RUN_TYPE = "sensor_placement"
|
|
RESULT_TYPE = "sensor_placement"
|
|
|
|
|
|
def _placement_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
payload = row.get("payload") if isinstance(row.get("payload"), dict) else {}
|
|
locations = [str(item) for item in payload.get("sensor_locations", [])]
|
|
return {
|
|
"run_id": row["run_id"],
|
|
"name": row["name"],
|
|
"sensor_count": len(locations),
|
|
"min_diameter": int(payload.get("minimum_diameter", 0)),
|
|
"created_by": row["created_by"],
|
|
"created_at": row["created_at"],
|
|
"status": row["status"],
|
|
"sensor_locations": locations,
|
|
}
|
|
|
|
|
|
def get_all_sensor_placements(name: str) -> list[dict[str, Any]]:
|
|
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
|
|
cur.execute(
|
|
"""
|
|
SELECT r.run_id, r.name, r.created_by, r.created_at, r.status,
|
|
result.payload
|
|
FROM analysis.runs AS r
|
|
JOIN LATERAL (
|
|
SELECT payload
|
|
FROM analysis.results
|
|
WHERE run_id = r.run_id AND result_type = %s
|
|
ORDER BY created_at DESC, result_id DESC
|
|
LIMIT 1
|
|
) AS result ON true
|
|
WHERE r.run_type = %s
|
|
ORDER BY r.created_at DESC, r.run_id
|
|
""",
|
|
(RESULT_TYPE, RUN_TYPE),
|
|
)
|
|
return [_placement_row(row) for row in cur.fetchall()]
|
|
|
|
|
|
def create_sensor_placement(
|
|
name: str,
|
|
*,
|
|
run_name: str,
|
|
min_diameter: int,
|
|
created_by: str,
|
|
sensor_locations: list[str],
|
|
) -> dict[str, Any]:
|
|
run_id = uuid4()
|
|
payload = {
|
|
"sensor_number": len(sensor_locations),
|
|
"minimum_diameter": min_diameter,
|
|
"sensor_locations": sensor_locations,
|
|
}
|
|
with project_connection(name) as conn, conn.transaction():
|
|
with conn.cursor(row_factory=dict_row) as cur:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO analysis.runs
|
|
(run_id, name, run_type, created_by, started_at, status, parameters)
|
|
VALUES (%s, %s, %s, %s, now(), 'completed', '{}'::jsonb)
|
|
RETURNING run_id, name, created_by, created_at, status
|
|
""",
|
|
(run_id, run_name, RUN_TYPE, created_by),
|
|
)
|
|
created = cur.fetchone()
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO analysis.results (run_id, result_type, payload)
|
|
VALUES (%s, %s, %s)
|
|
""",
|
|
(run_id, RESULT_TYPE, Jsonb(payload)),
|
|
)
|
|
if created is None:
|
|
raise RuntimeError("监测点优化运行写入失败")
|
|
return _placement_row(dict(created) | {"payload": payload})
|
|
|
|
|
|
def get_sensor_placement(name: str, run_id: UUID) -> dict[str, Any] | None:
|
|
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
|
|
cur.execute(
|
|
"""
|
|
SELECT r.run_id, r.name, r.created_by, r.created_at, r.status,
|
|
result.payload
|
|
FROM analysis.runs AS r
|
|
JOIN LATERAL (
|
|
SELECT payload
|
|
FROM analysis.results
|
|
WHERE run_id = r.run_id AND result_type = %s
|
|
ORDER BY created_at DESC, result_id DESC
|
|
LIMIT 1
|
|
) AS result ON true
|
|
WHERE r.run_id = %s AND r.run_type = %s
|
|
""",
|
|
(RESULT_TYPE, run_id, RUN_TYPE),
|
|
)
|
|
row = cur.fetchone()
|
|
return _placement_row(row) if row else None
|
|
|
|
|
|
def get_sensor_placement_nodes(
|
|
name: str,
|
|
node_ids: list[str],
|
|
) -> list[dict[str, Any]]:
|
|
if not node_ids:
|
|
return []
|
|
with project_connection(name) as conn, conn.cursor(row_factory=dict_row) as cur:
|
|
cur.execute(
|
|
"""
|
|
WITH incident_pipe_diameters AS (
|
|
SELECT node_id, MAX(diameter) AS max_pipe_diameter
|
|
FROM (
|
|
SELECT l.start_node_id AS node_id, p.diameter
|
|
FROM network.pipes AS p
|
|
JOIN network.links AS l ON l.id = p.link_id
|
|
WHERE l.start_node_id = ANY(%s)
|
|
UNION ALL
|
|
SELECT l.end_node_id AS node_id, p.diameter
|
|
FROM network.pipes AS p
|
|
JOIN network.links AS l ON l.id = p.link_id
|
|
WHERE l.end_node_id = ANY(%s)
|
|
) AS incident_pipes
|
|
GROUP BY node_id
|
|
)
|
|
SELECT j.node_id,
|
|
ipd.max_pipe_diameter,
|
|
j.elevation,
|
|
ST_X(g.geom) AS project_x,
|
|
ST_Y(g.geom) AS project_y,
|
|
ST_X(ST_Transform(g.geom, 3857)) AS map_x,
|
|
ST_Y(ST_Transform(g.geom, 3857)) AS map_y
|
|
FROM network.junctions AS j
|
|
JOIN gis.node_geometries AS g ON g.node_id = j.node_id
|
|
LEFT JOIN incident_pipe_diameters AS ipd ON ipd.node_id = j.node_id
|
|
WHERE j.node_id = ANY(%s)
|
|
ORDER BY j.node_id
|
|
""",
|
|
(node_ids, node_ids, node_ids),
|
|
)
|
|
return list(cur.fetchall())
|
|
|
|
|
|
def update_sensor_placement(
|
|
name: str,
|
|
run_id: UUID,
|
|
*,
|
|
expected_sensor_locations: list[str],
|
|
sensor_locations: list[str],
|
|
) -> dict[str, Any] | None:
|
|
with project_connection(name) as conn, conn.transaction():
|
|
with conn.cursor(row_factory=dict_row) as cur:
|
|
cur.execute(
|
|
"""
|
|
SELECT result_id, payload
|
|
FROM analysis.results
|
|
WHERE run_id = %s AND result_type = %s
|
|
ORDER BY created_at DESC, result_id DESC
|
|
LIMIT 1
|
|
FOR UPDATE
|
|
""",
|
|
(run_id, RESULT_TYPE),
|
|
)
|
|
result = cur.fetchone()
|
|
if result is None:
|
|
return None
|
|
payload = result["payload"] if isinstance(result["payload"], dict) else {}
|
|
current = [str(item) for item in payload.get("sensor_locations", [])]
|
|
if current != expected_sensor_locations:
|
|
return None
|
|
payload = {
|
|
**payload,
|
|
"sensor_number": len(sensor_locations),
|
|
"sensor_locations": sensor_locations,
|
|
}
|
|
cur.execute(
|
|
"UPDATE analysis.results SET payload = %s WHERE result_id = %s",
|
|
(Jsonb(payload), result["result_id"]),
|
|
)
|
|
return get_sensor_placement(name, run_id)
|