feat: add BLIF Flow v2 persistence scaffolding
This commit is contained in:
119
scripts/validate_blif_flow_v2_persistence.py
Normal file
119
scripts/validate_blif_flow_v2_persistence.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate migration 011 and two rebuilds using temp tables in the test DB."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
os.chdir(ROOT)
|
||||
|
||||
from app.blif_flow_v2_projection_service import rebuild_blif_flow_v2_projection
|
||||
from app.db import engine
|
||||
|
||||
|
||||
NAMED_IDS = {
|
||||
"instalbeira": "5c33db95-fab8-477a-bddd-0b9cc8f91302",
|
||||
"engexicon": "61f1c955-a372-4ea7-b9b0-b8528d74a141",
|
||||
"construrecup": "e3b23ac5-84db-4763-8a31-a684e873032c",
|
||||
"panoramic": "fd79b9a1-07e6-4f61-95e8-09eab89c155e",
|
||||
"x_mat_canonical": "dc89a466-db24-401b-bfe9-d47644b2d0c8",
|
||||
"x_mat_reconstructed": "1816a06e-9a69-4a9b-9279-1263156892d3",
|
||||
"rzsolar_reconstructed": "fd221608-e007-4043-a23d-07e0c119a345",
|
||||
"rzsolar_synthetic": "434124fb-ac19-4d78-909a-55761d7e8daa",
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT")
|
||||
try:
|
||||
identity = conn.execute(text(
|
||||
"SELECT current_database(), current_user, current_setting('transaction_read_only')"
|
||||
)).one()
|
||||
if tuple(identity[:2]) != ("clientflow_codex_test", "clientflow_codex_test"):
|
||||
raise RuntimeError(f"refusing persistence validation on {identity!r}")
|
||||
conn.exec_driver_sql("BEGIN READ WRITE")
|
||||
try:
|
||||
stage_fingerprint_before = conn.execute(text("""
|
||||
SELECT md5(string_agg(id::text || ':' || COALESCE(stage,''), ',' ORDER BY id))
|
||||
FROM public.opportunities
|
||||
""")).scalar_one()
|
||||
conn.exec_driver_sql("SET LOCAL search_path TO pg_temp, public")
|
||||
conn.exec_driver_sql("CREATE TEMP TABLE opportunities (id UUID PRIMARY KEY)")
|
||||
conn.exec_driver_sql("INSERT INTO opportunities SELECT id FROM public.opportunities")
|
||||
conn.exec_driver_sql("CREATE TEMP TABLE opportunity_events (LIKE public.opportunity_events INCLUDING DEFAULTS INCLUDING CONSTRAINTS)")
|
||||
conn.exec_driver_sql("ALTER TABLE opportunity_events ADD PRIMARY KEY (id)")
|
||||
conn.exec_driver_sql("CREATE TEMP TABLE tasks (LIKE public.tasks INCLUDING DEFAULTS INCLUDING CONSTRAINTS)")
|
||||
conn.exec_driver_sql("ALTER TABLE tasks ADD PRIMARY KEY (id)")
|
||||
conn.exec_driver_sql(Path("migrations/011_blif_flow_v2_persistence.sql").read_text())
|
||||
|
||||
first = rebuild_blif_flow_v2_projection(
|
||||
mode="shadow", target_schema="pg_temp", connection=conn,
|
||||
)
|
||||
transition_count_first = conn.execute(text(
|
||||
"SELECT count(*) FROM pg_temp.opportunity_flow_transitions"
|
||||
)).scalar_one()
|
||||
first_fingerprints = dict(conn.execute(text("""
|
||||
SELECT opportunity_id::text, source_fingerprint
|
||||
FROM pg_temp.opportunity_flow_state_v2
|
||||
""")).all())
|
||||
second = rebuild_blif_flow_v2_projection(
|
||||
mode="shadow", target_schema="pg_temp", connection=conn,
|
||||
)
|
||||
transition_count_second = conn.execute(text(
|
||||
"SELECT count(*) FROM pg_temp.opportunity_flow_transitions"
|
||||
)).scalar_one()
|
||||
second_fingerprints = dict(conn.execute(text("""
|
||||
SELECT opportunity_id::text, source_fingerprint
|
||||
FROM pg_temp.opportunity_flow_state_v2
|
||||
""")).all())
|
||||
named = {}
|
||||
for name, oid in NAMED_IDS.items():
|
||||
row = conn.execute(text("""
|
||||
SELECT opportunity_id::text, material_process_key,
|
||||
canonical_opportunity_id::text, is_duplicate_representation,
|
||||
business_state, business_next_action, diagnostic_status, confidence
|
||||
FROM pg_temp.opportunity_flow_state_v2
|
||||
WHERE opportunity_id=CAST(:oid AS UUID)
|
||||
"""), {"oid": oid}).mappings().one()
|
||||
named[name] = dict(row)
|
||||
stage_fingerprint_after = conn.execute(text("""
|
||||
SELECT md5(string_agg(id::text || ':' || COALESCE(stage,''), ',' ORDER BY id))
|
||||
FROM public.opportunities
|
||||
""")).scalar_one()
|
||||
result = {
|
||||
"database": {"name": identity[0], "user": identity[1]},
|
||||
"migration_scope": "transaction-scoped pg_temp (public tasks is postgres-owned)",
|
||||
"first_rebuild": first, "second_rebuild": second,
|
||||
"transition_count_first": transition_count_first,
|
||||
"transition_count_second": transition_count_second,
|
||||
"idempotent": transition_count_first == transition_count_second
|
||||
and first_fingerprints == second_fingerprints,
|
||||
"opportunity_stage_unchanged": stage_fingerprint_before == stage_fingerprint_after,
|
||||
"named": named,
|
||||
}
|
||||
conn.exec_driver_sql(Path("migrations/011_blif_flow_v2_persistence_down.sql").read_text())
|
||||
remaining_task_columns = conn.execute(text("""
|
||||
SELECT count(*) FROM information_schema.columns
|
||||
WHERE table_schema LIKE 'pg_temp_%' AND table_name='tasks'
|
||||
AND column_name IN ('resolution_code','resolved_at','resolved_by_event_id','superseded_by_task_id')
|
||||
""")).scalar_one()
|
||||
result["down_migration_reversible"] = (
|
||||
conn.execute(text("SELECT to_regclass('pg_temp.opportunity_flow_state_v2')")).scalar_one() is None
|
||||
and conn.execute(text("SELECT to_regclass('pg_temp.opportunity_flow_transitions')")).scalar_one() is None
|
||||
and remaining_task_columns == 0
|
||||
)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
finally:
|
||||
conn.exec_driver_sql("ROLLBACK")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user