"""Read-only projections backed by the GIS materialized-view query layer.""" from typing import Any from ..core.database import read, read_all def _node_coord_rows( rows: list[Any], ) -> dict[str, dict[str, Any]]: return { str(row["id"]): { "x": float(row["x"]), "y": float(row["y"]), "type": str(row["node_type"]), } for row in rows } def get_network_node_coords(name: str) -> dict[str, dict[str, Any]]: """Return every publishable node with one view-backed query.""" rows = read_all( name, """ SELECT id, x, y, node_type FROM gis.network_nodes ORDER BY id """, ) return _node_coord_rows(rows) def get_major_node_coords( name: str, diameter: int ) -> dict[str, dict[str, Any]]: """Return endpoints of pipes above the requested diameter.""" rows = read_all( name, """ SELECT n.id, n.x, n.y, n.node_type FROM gis.pipes AS p CROSS JOIN LATERAL ( VALUES (p.start_node_id), (p.end_node_id) ) AS endpoint(id) JOIN gis.network_nodes AS n ON n.id = endpoint.id WHERE p.diameter > %s GROUP BY n.id, n.x, n.y, n.node_type ORDER BY n.id """, (diameter,), ) return _node_coord_rows(rows) def get_network_link_nodes(name: str) -> list[str]: """Return every publishable link in the established API wire format.""" rows = read_all( name, """ SELECT id, link_type, start_node_id, end_node_id FROM gis.network_links ORDER BY id """, ) return [ f"{row['id']}:{row['link_type']}:{row['start_node_id']}:{row['end_node_id']}" for row in rows ] def get_major_pipe_nodes(name: str, diameter: int) -> list[str]: """Return large pipes in the established API wire format.""" rows = read_all( name, """ SELECT id, start_node_id, end_node_id FROM gis.pipes WHERE diameter > %s ORDER BY id """, (diameter,), ) return [ f"{row['id']}:pipe:{row['start_node_id']}:{row['end_node_id']}" for row in rows ] def get_topology_rows( name: str, node_ids: list[str] ) -> tuple[list[Any], list[Any]]: """Load selected nodes and their internal links with two batch queries.""" if not node_ids: return [], [] nodes = read_all( name, """ SELECT n.id, ST_X(g.geom) AS x, ST_Y(g.geom) AS y, n.node_type::text AS node_type FROM network.nodes AS n JOIN gis.node_geometries AS g ON g.node_id = n.id WHERE n.id = ANY(%s) ORDER BY n.id """, (node_ids,), ) links = read_all( name, """ SELECT l.id, l.start_node_id, l.end_node_id, COALESCE(p.length, 0.0) AS length FROM network.links AS l LEFT JOIN network.pipes AS p ON p.link_id = l.id WHERE l.start_node_id = ANY(%s) AND l.end_node_id = ANY(%s) ORDER BY l.id """, (node_ids, node_ids), ) return nodes, links def get_boundary_link_ids(name: str, node_ids: list[str]) -> list[str]: """Return links with exactly one endpoint inside the supplied node set.""" if not node_ids: return [] rows = read_all( name, """ SELECT id FROM network.links WHERE (start_node_id = ANY(%s)) <> (end_node_id = ANY(%s)) ORDER BY id """, (node_ids, node_ids), ) return [str(row["id"]) for row in rows] def get_junction_demands( name: str, node_ids: list[str] ) -> dict[str, list[dict[str, Any]]]: """Read authoritative demands for selected junctions from model tables.""" if not node_ids: return {} rows = read_all( name, """ SELECT junction_id, sequence_no, base_demand, pattern_id, category FROM network.demands WHERE junction_id = ANY(%s) ORDER BY junction_id, sequence_no """, (node_ids,), ) result: dict[str, list[dict[str, Any]]] = {} for row in rows: result.setdefault(str(row["junction_id"]), []).append( { "demand": float(row["base_demand"]), "pattern": row["pattern_id"], "category": row["category"], } ) return result def sum_junction_base_demand(name: str, node_ids: list[str]) -> float: """Sum selected junction demand from the authoritative model table.""" if not node_ids: return 0.0 row = read( name, """ SELECT COALESCE(SUM(base_demand), 0.0) AS total_base_demand FROM network.demands WHERE junction_id = ANY(%s) """, (node_ids,), ) return float(row["total_base_demand"])