150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Dict, List, Tuple
|
|
|
|
import pg8000.dbapi as pg
|
|
|
|
# Separate configuration so legacy fs_law_risk integration keeps using PG_*
|
|
LIC_DEFAULT_DB = "licensing_risks"
|
|
|
|
|
|
def _lic_pg_conn(autocommit: bool = False) -> pg.Connection:
|
|
host = os.getenv("LIC_PG_HOST", "172.24.240.1")
|
|
port = int(os.getenv("LIC_PG_PORT", os.getenv("PG_PORT", "5432")))
|
|
user = os.getenv("LIC_PG_USER", os.getenv("PG_USER", "postgres"))
|
|
password = os.getenv("LIC_PG_PASSWORD", "")
|
|
database = os.getenv("LIC_PG_DATABASE", LIC_DEFAULT_DB)
|
|
conn = pg.connect(host=host, port=port, user=user, password=password, database=database)
|
|
conn.autocommit = autocommit
|
|
return conn
|
|
|
|
|
|
def list_region_theme_options() -> List[Dict[str, str]]:
|
|
"""Return all region-theme pairs usable for LLM selection."""
|
|
sql = """
|
|
SELECT
|
|
rt.region_id,
|
|
r.name AS region_name,
|
|
rt.theme_id,
|
|
t.name AS theme_name
|
|
FROM region_themes rt
|
|
JOIN regions r ON r.id = rt.region_id
|
|
JOIN themes t ON t.id = rt.theme_id
|
|
ORDER BY r.name, t.name
|
|
"""
|
|
out: List[Dict[str, str]] = []
|
|
with _lic_pg_conn() as conn:
|
|
cur = conn.cursor()
|
|
cur.execute(sql)
|
|
for region_id, region_name, theme_id, theme_name in cur.fetchall():
|
|
rid = str(region_id)
|
|
tid = str(theme_id)
|
|
out.append(
|
|
{
|
|
"option_id": f"{rid}:{tid}",
|
|
"region_id": rid,
|
|
"region_name": str(region_name),
|
|
"theme_id": tid,
|
|
"theme_name": str(theme_name),
|
|
"display_name": f"{region_name} · {theme_name}",
|
|
}
|
|
)
|
|
return out
|
|
|
|
|
|
def load_business_scopes(region_id: str) -> List[Dict[str, str]]:
|
|
"""List business scopes bound to a region."""
|
|
sql = """
|
|
SELECT bs.id, bs.description
|
|
FROM region_scopes rs
|
|
JOIN business_scopes bs ON bs.id = rs.scope_id
|
|
WHERE rs.region_id = %s
|
|
ORDER BY bs.description
|
|
"""
|
|
scopes: List[Dict[str, str]] = []
|
|
with _lic_pg_conn() as conn:
|
|
cur = conn.cursor()
|
|
cur.execute(sql, (region_id,))
|
|
for scope_id, description in cur.fetchall():
|
|
scopes.append({"id": str(scope_id), "description": str(description)})
|
|
return scopes
|
|
|
|
|
|
def load_permits_and_risks(region_id: str, theme_id: str) -> List[Dict[str, object]]:
|
|
"""Return permits with attached risk entries for a region-theme pair."""
|
|
sql = """
|
|
SELECT
|
|
p.id AS permit_id,
|
|
p.name AS permit_name,
|
|
rk.id AS risk_id,
|
|
rk.risk_content,
|
|
rk.legal_basis,
|
|
rk.document_no,
|
|
rk.summary
|
|
FROM region_theme_permits rtp
|
|
JOIN permits p ON p.id = rtp.permit_id
|
|
LEFT JOIN region_permit_risks rpr
|
|
ON rpr.region_id = rtp.region_id
|
|
AND rpr.permit_id = rtp.permit_id
|
|
LEFT JOIN risks rk ON rk.id = rpr.risk_id
|
|
WHERE rtp.region_id = %s AND rtp.theme_id = %s
|
|
ORDER BY p.name, rk.risk_content
|
|
"""
|
|
permits: Dict[str, Dict[str, object]] = {}
|
|
with _lic_pg_conn() as conn:
|
|
cur = conn.cursor()
|
|
cur.execute(sql, (region_id, theme_id))
|
|
for row in cur.fetchall():
|
|
permit_id, permit_name, risk_id, risk_content, legal_basis, document_no, summary = row
|
|
pid = str(permit_id)
|
|
entry = permits.setdefault(
|
|
pid,
|
|
{
|
|
"id": pid,
|
|
"name": str(permit_name),
|
|
"risks": [],
|
|
},
|
|
)
|
|
if risk_id is None:
|
|
continue
|
|
entry["risks"].append(
|
|
{
|
|
"id": str(risk_id),
|
|
"risk_content": risk_content or "",
|
|
"legal_basis": legal_basis or "",
|
|
"document_no": document_no or "",
|
|
"summary": summary or "",
|
|
}
|
|
)
|
|
return list(permits.values())
|
|
|
|
|
|
def load_theme_payload(region_id: str, theme_id: str) -> Dict[str, object]:
|
|
"""Assemble full data bundle for a region-theme selection."""
|
|
info_sql = """
|
|
SELECT r.id, r.name, t.id, t.name
|
|
FROM regions r
|
|
JOIN region_themes rt ON rt.region_id = r.id
|
|
JOIN themes t ON t.id = rt.theme_id
|
|
WHERE r.id = %s AND t.id = %s
|
|
LIMIT 1
|
|
"""
|
|
with _lic_pg_conn() as conn:
|
|
cur = conn.cursor()
|
|
cur.execute(info_sql, (region_id, theme_id))
|
|
row = cur.fetchone()
|
|
if not row:
|
|
raise ValueError("Region/theme combination not found")
|
|
region_uuid, region_name, theme_uuid, theme_name = row
|
|
|
|
scopes = load_business_scopes(region_id)
|
|
permits = load_permits_and_risks(region_id, theme_id)
|
|
return {
|
|
"region": {"id": str(region_uuid), "name": str(region_name)},
|
|
"theme": {"id": str(theme_uuid), "name": str(theme_name)},
|
|
"business_scopes": scopes,
|
|
"permits": permits,
|
|
}
|
|
|