96 lines
4.0 KiB
Python
96 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Read-only audit of the exact Operations boundary in V1 and authoritative V2."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from app.config import settings
|
|
from app.operations_service import get_operations_summary
|
|
|
|
JSON_PATH = Path("/tmp/blif_flow_v2_authoritative_simulation.json")
|
|
TEXT_PATH = Path("/tmp/blif_flow_v2_authoritative_operations.txt")
|
|
CURRENT = {"do_now", "review", "blocked", "exception"}
|
|
|
|
|
|
def _all(summary):
|
|
return list(summary.get("work_items") or []) + list(summary.get("waiting_items") or []) + list(summary.get("backlog_items") or []) + list(summary.get("not_current_items") or [])
|
|
|
|
|
|
def _metrics(summary):
|
|
items = _all(summary)
|
|
queues = [str(item.get("operational_queue") or "") for item in items]
|
|
return {
|
|
"current": sum(queue in CURRENT for queue in queues),
|
|
"do_now": queues.count("do_now"), "review": queues.count("review"),
|
|
"waiting": queues.count("waiting"), "backlog": queues.count("backlog"),
|
|
"not_current": queues.count("not_current"),
|
|
}
|
|
|
|
|
|
def _identity(item):
|
|
return str(item.get("canonical_opportunity_id") or item.get("opportunity_id") or item.get("process_key") or item.get("work_item_key"))
|
|
|
|
|
|
def _classification(old, new):
|
|
if new is not None:
|
|
return "REPLACED_BY_CORRECT_ACTION"
|
|
decision = old.get("decision") or {}
|
|
code = str(decision.get("reason_code") or old.get("eligibility_reason_code") or "").upper()
|
|
if "DUPLICATE" in code:
|
|
return "DUPLICATE_SUPPRESSED"
|
|
if "SATISF" in code or "ANSWERED" in code:
|
|
return "SATISFIED"
|
|
if "SUPERSE" in code or "TERMINAL" in code:
|
|
return "SUPERSEDED"
|
|
if "PREMATURE" in code:
|
|
return "PREMATURE_REMOVED"
|
|
if old.get("source") in {"task", "opportunity"}:
|
|
return "LEGACY_ONLY"
|
|
return "UNSAFE_FALSE_NEGATIVE"
|
|
|
|
|
|
def simulate():
|
|
original = settings.blif_flow_v2_mode
|
|
try:
|
|
settings.blif_flow_v2_mode = "off"
|
|
v1 = get_operations_summary(limit=200)
|
|
settings.blif_flow_v2_mode = "authoritative"
|
|
v2 = get_operations_summary(limit=200)
|
|
finally:
|
|
settings.blif_flow_v2_mode = original
|
|
v1_current = {_identity(item): item for item in _all(v1) if item.get("operational_queue") in CURRENT}
|
|
v2_current = {_identity(item): item for item in _all(v2) if item.get("operational_queue") in CURRENT}
|
|
removed = []
|
|
for key, old in v1_current.items():
|
|
new = v2_current.get(key)
|
|
if new is None or new.get("action_code") != old.get("action_code"):
|
|
removed.append({"identity": key, "v1_action": old.get("action_code"),
|
|
"v2_action": (new or {}).get("action_code"),
|
|
"classification": _classification(old, new)})
|
|
added = [{"identity": key, "action": item.get("action_code")}
|
|
for key, item in v2_current.items()
|
|
if key not in v1_current or v1_current[key].get("action_code") != item.get("action_code")]
|
|
result = {"v1": _metrics(v1), "authoritative_v2": _metrics(v2),
|
|
"cards_removed": removed, "cards_added_or_replaced": added,
|
|
"unsafe_false_negatives": sum(row["classification"] == "UNSAFE_FALSE_NEGATIVE" for row in removed)}
|
|
JSON_PATH.write_text(json.dumps(result, indent=2, ensure_ascii=False, default=str) + "\n")
|
|
lines = ["BLIF Flow v2 authoritative Operations simulation", "", f"V1: {result['v1']}",
|
|
f"Authoritative V2: {result['authoritative_v2']}",
|
|
f"Cards removed: {len(removed)}", f"Cards added/replaced: {len(added)}",
|
|
f"UNSAFE_FALSE_NEGATIVE: {result['unsafe_false_negatives']}"]
|
|
TEXT_PATH.write_text("\n".join(lines) + "\n")
|
|
return result
|
|
|
|
|
|
if __name__ == "__main__":
|
|
audit = simulate()
|
|
print(json.dumps(audit, indent=2, ensure_ascii=False, default=str))
|
|
if audit["unsafe_false_negatives"]:
|
|
raise SystemExit(2)
|