"""Structural-integrity and D-175 coverage validation. ``validate`` is a pre-commit hard blocker; the coverage validators run after commit (Phase 2 gate, exit 2) — see the entrypoint's main() for the contract. """ import sqlite3 def validate(conn: sqlite3.Connection) -> list[str]: """Validate structural integrity of imported data. Checks FK integrity, chain commodity references, and chain completeness. These are hard blockers — broken data must not be committed. Coverage validation (commodity/system thresholds) is separate and runs after commit via validate_commodity_coverage() and validate_system_coverage(). """ errors: list[str] = [] # FK integrity fk_issues = conn.execute("PRAGMA foreign_key_check").fetchall() if fk_issues: for issue in fk_issues[:10]: errors.append(f"FK violation: table={issue[0]} rowid={issue[1]} " f"parent={issue[2]} fkid={issue[3]}") # Chain inputs reference valid commodities orphan_inputs = conn.execute(""" SELECT ci.chain_id, ci.input_commodity_id FROM chain_inputs ci LEFT JOIN commodities c ON ci.input_commodity_id = c.commodity_id WHERE c.commodity_id IS NULL """).fetchall() for chain_id, cid in orphan_inputs: errors.append(f"chain_inputs: chain '{chain_id}' references unknown commodity '{cid}'") # Chain outputs reference valid commodities orphan_outputs = conn.execute(""" SELECT pc.chain_id, pc.output_commodity_id FROM production_chains pc LEFT JOIN commodities c ON pc.output_commodity_id = c.commodity_id WHERE c.commodity_id IS NULL """).fetchall() for chain_id, cid in orphan_outputs: errors.append(f"production_chains: chain '{chain_id}' outputs unknown commodity '{cid}'") # economic_role must be one of the D-194 canonical 10 values valid_roles = { 'manufacturing', 'financial', 'agricultural', 'extraction', 'service_mixed', 'institutional', 'transit_hub', 'research', 'military', 'residential', } bad_roles = conn.execute(""" SELECT DISTINCT economic_role, COUNT(*) as cnt FROM bodies WHERE economic_role IS NOT NULL AND economic_role NOT IN ( 'manufacturing', 'financial', 'agricultural', 'extraction', 'service_mixed', 'institutional', 'transit_hub', 'research', 'military', 'residential' ) GROUP BY economic_role """).fetchall() for role, cnt in bad_roles: errors.append( f"bodies.economic_role: non-canonical value '{role}' on {cnt} row(s) — " f"valid values: {sorted(valid_roles)}" ) # Chain completeness: every intermediate commodity must have at least one producer missing_chains = conn.execute(""" SELECT c.commodity_id, c.name FROM commodities c WHERE c.tier = 'intermediate' AND c.commodity_id NOT IN (SELECT output_commodity_id FROM production_chains) ORDER BY c.commodity_id """).fetchall() for cid, name in missing_chains: errors.append(f"chain completeness: no production chain produces intermediate '{cid}' ({name})") return errors def validate_commodity_coverage( conn: sqlite3.Connection, wiki_corps: list[dict], commodity_ids: set[str] ) -> list[str]: """3+ corporations per major commodity type (raw + intermediate). D-175.""" errors: list[str] = [] major = [ r[0] for r in conn.execute( "SELECT commodity_id FROM commodities " "WHERE tier IN ('raw', 'intermediate') ORDER BY commodity_id" ).fetchall() ] # Build commodity → corp set from wiki tags filtered to known commodity IDs coverage: dict[str, set[str]] = {cid: set() for cid in major} for corp in wiki_corps: for tag in corp.get("tags", []): if tag in coverage: coverage[tag].add(corp["corp_id"]) for cid in major: n = len(coverage[cid]) if n < 3: corp_list = sorted(coverage[cid]) if coverage[cid] else ["none"] errors.append( f"commodity coverage: '{cid}' has {n}/3 corp(s) — {corp_list}" ) return errors def validate_system_coverage( conn: sqlite3.Connection, wiki_corps: list[dict] ) -> list[str]: """1+ corporation per inhabited system with population > 100K. D-175. Uses wiki_corps headquarters data (not DB corp_presence) so this check is accurate in both dry-run and real-run modes. """ covered = {c["system_id"] for c in wiki_corps if c.get("system_id")} populated = conn.execute(""" SELECT se.system_id, ss.proper_name, se.population FROM system_economy se JOIN star_systems ss ON se.system_id = ss.system_id WHERE se.population > 100000 ORDER BY se.system_id """).fetchall() return [ f"system coverage: no corp presence in '{sid}' ({name}, pop={pop:,})" for sid, name, pop in populated if sid not in covered ]