"""Offline reprocessing of residual questions against the final canonical identity map.""" import argparse import json from collections import defaultdict from pathlib import Path from central.app.ai.kb_consolidation import canonical_product_id from central.app.ai.workitems import build_work_items def _erp_code(question): import re match = re.search(r"PRODUCT\s+(\d+)", question.get("QUESTION", "")) return match.group(1) if match else str(question.get("DATABASE_CODE") or "") def main(questions_path, payload_path, output_path): questions_doc = json.loads(Path(questions_path).read_text(encoding="utf-8")) payload = json.loads(Path(payload_path).read_text(encoding="utf-8")) canonical = {} fingerprints = {} by_observation = defaultdict(list) for product in payload.get("products", []): cid = canonical_product_id(product) for observation in product.get("observations", []): key = f"{observation.get('database')}:{observation.get('erp_code')}" canonical[key] = cid fingerprints[key] = product.get("fingerprint") or cid questions = [dict(question, DATABASE_CODE=_erp_code(question)) for question in questions_doc.get("questions", [])] items = build_work_items(questions, fingerprints, canonical) by_task = {} for item in items: bucket = by_task.setdefault(item.task_type, {"items": 0, "cells": 0}) bucket["items"] += 1 bucket["cells"] += item.cells_covered report = { "artifact": "AI_RESIDUAL_PLAN_V3", "work_items_before": 750, "work_items_after": len(items), "llm_items_before": 511, # Candidate preparation is evidence-only; without an official validated # answer it must not reclassify a selection item as resolved. "llm_items_after": 511, "llm_reclassification": "UNCHANGED_PENDING_FINAL_PROVIDER_SELECTION", "by_task_type": by_task, "canonical_observation_keys": len(canonical), "ai_write_allowed": False, "semantic_decisions": 0, } Path(output_path).write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") return report if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--questions", default="docs/LOCAL_FISCAL_CONTEXT_QUESTIONS_V2.json") parser.add_argument("--payload", default="docs/runs/ai_kb_seed_payload.json") parser.add_argument("--output", default="docs/AI_RESIDUAL_PLAN_V3.json") args = parser.parse_args() print(json.dumps(main(args.questions, args.payload, args.output), indent=2, ensure_ascii=False))