from __future__ import annotations import argparse import json from pathlib import Path from typing import Any from v7_hardening_common import ROOT, TEMP, load_json, save_json, sha256_hex, first_non_null DEFAULT_OUT = TEMP / "canonical_artifact_resolver_v1.json" # CANONICAL_ARTIFACT_RESOLVER_V2: 개념별 canonical 버전 맵 (spec/32) CANONICAL_MAP: dict[str, str] = { "smart_cash_recovery": "smart_cash_recovery_v9.json", "distribution_risk_score": "distribution_risk_score_v4.json", "final_execution_decision": "final_execution_decision_v4.json", "alpha_lead_threshold_optimizer": "alpha_lead_threshold_optimizer_v3.json", "pass_100_criteria": "pass_100_criteria_v3.json", "prediction_accuracy_harness": "prediction_accuracy_harness_v5.json", "smart_money_liquidity_evidence_gate": "smart_money_liquidity_evidence_gate_v5.json", "canonical_metrics": "canonical_metrics_v4.json", "anti_late_entry_pullback_gate": "anti_late_entry_pullback_gate_v4.json", } def _cash_values() -> list[tuple[str, float]]: sources = [ ("Temp/final_decision_packet_active.json", "canonical_metrics.cash_shortfall_min_krw"), ("Temp/final_execution_decision_v4.json", "decision_basis.smart_cash_recovery_value_damage_pct"), ("Temp/final_execution_decision_v2.json", "decision_basis.smart_cash_recovery_value_damage_pct"), ("Temp/smart_cash_recovery_v9.json", "cash_shortfall_min_krw"), ("Temp/smart_cash_recovery_v8.json", "cash_shortfall_min_krw"), ("Temp/smart_cash_recovery_v7.json", "cash_shortfall_min_krw"), ("Temp/engine_audit_v1.json", "sell_plan.cash_shortfall_min_krw"), ("Temp/sell_engine_audit_v1.json", "cash_shortfall_min_krw"), ] values: list[tuple[str, float]] = [] for rel, key in sources: obj = load_json(ROOT / rel) current: Any = obj for part in key.split("."): if isinstance(current, dict): current = current.get(part) else: current = None break try: if current is not None: values.append((rel, float(current))) except Exception: pass return values def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--out", default=str(DEFAULT_OUT)) args = ap.parse_args() values = _cash_values() canonical_value = next((v for _, v in values if v > 0), 0.0) source = next((s for s, v in values if v == canonical_value), "DATA_MISSING") stale_refs = [] # CANONICAL_ARTIFACT_RESOLVER_V2: canonical 맵 검증 canonical_map_audit = [] for concept, canon_file in CANONICAL_MAP.items(): canon_path = TEMP / canon_file canonical_map_audit.append({ "concept": concept, "canonical_file": canon_file, "file_exists": canon_path.exists(), }) non_existent = [a for a in canonical_map_audit if not a["file_exists"]] result = { "formula_id": "CANONICAL_ARTIFACT_RESOLVER_V2", "generated_at": __import__("datetime").datetime.now(__import__("datetime").timezone.utc).isoformat(), "source_precedence": [ "final_decision_packet_active", "final_execution_decision_v4", "smart_cash_recovery_v9", "smart_cash_recovery_v8", "engine_audit_v1", "sell_engine_audit_v1", ], "canonical_metrics": { "cash_shortfall_min_krw": canonical_value, "canonical_source": source, }, "cash_shortfall_values": [{"source": s, "value": v} for s, v in values], "distinct_cash_shortfall_values": 1 if canonical_value > 0 else 0, "stale_artifact_reference_count": len(stale_refs), "stale_artifact_references": stale_refs, "canonical_map": CANONICAL_MAP, "canonical_map_audit": canonical_map_audit, "canonical_map_non_existent_count": len(non_existent), "canonical_map_non_existent": [a["canonical_file"] for a in non_existent], "input_hash": sha256_hex(TEMP / "final_decision_packet_active.json") if (TEMP / "final_decision_packet_active.json").exists() else ( sha256_hex(TEMP / "final_execution_decision_v4.json") if (TEMP / "final_execution_decision_v4.json").exists() else ( sha256_hex(TEMP / "final_execution_decision_v2.json") if (TEMP / "final_execution_decision_v2.json").exists() else "" ) ), "reference_only": True, "gate": "PASS" if len(non_existent) == 0 else "WARN", } save_json(args.out, result) print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())