from __future__ import annotations import argparse import json import re from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_JSON = ROOT / "GatherTradingData.json" DEFAULT_OUT = ROOT / "Temp" / "decision_evidence_score_v2.json" def _load(path: Path) -> dict[str, Any]: try: data = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return data if isinstance(data, dict) else {} def _rows(v: Any) -> list[dict[str, Any]]: if isinstance(v, list): return [x for x in v if isinstance(x, dict)] if isinstance(v, str): try: return _rows(json.loads(v)) except Exception: return [] return [] def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--json", default=str(DEFAULT_JSON)) ap.add_argument("--out", default=str(DEFAULT_OUT)) args = ap.parse_args() jp = Path(args.json) op = Path(args.out) if not jp.is_absolute(): jp = ROOT / jp if not op.is_absolute(): op = ROOT / op payload = _load(jp) h = {} if isinstance(payload.get("data"), dict) and isinstance(payload["data"].get("_harness_context"), dict): h.update(payload["data"]["_harness_context"]) if isinstance(payload.get("hApex"), dict): h.update(payload["hApex"]) bp = _rows(h.get("order_blueprint_json")) actionable = [r for r in bp if str(r.get("order_type") or "").upper() in {"BUY", "ADD_ON", "STAGED_BUY", "SELL", "STOP_LOSS", "TRIM"}] rationale_re = re.compile(r"([A-Z][A-Z0-9_]*_V[0-9]+|NO_EXECUTION:[A-Z_]+|EXPORT_GATE_[A-Z_]+)") rationale_total = 0 rationale_ok = 0 free_text_violation = 0 numeric_source_cov = 100.0 for row in actionable: rc = str(row.get("rationale_code") or "") if rc: rationale_total += 1 if rationale_re.search(rc): rationale_ok += 1 else: free_text_violation += 1 for k in ("quantity", "limit_price_krw", "stop_price_krw", "take_profit_price_krw"): if k in row and row.get(k) not in (None, "") and "rationale_code" not in row: numeric_source_cov = 0.0 if len(actionable) == 0: score = None score_state = "N_A_NO_ACTIONABLE_ORDERS" gate = "NO_ACTIONABLE_ORDERS_NEUTRAL" else: rq = 100.0 if rationale_total == 0 else (rationale_ok / max(1, rationale_total)) * 100.0 score = round(max(0.0, min(100.0, rq * 0.7 + numeric_source_cov * 0.3)), 2) score_state = "SCORED" gate = "PASS" if score >= 92 else ("WARN" if score >= 80 else "FAIL") out = { "formula_id": "DECISION_EVIDENCE_SCORE_V2", "score": score, "score_state": score_state, "evidence_rows": len(bp), "rationale_rows": rationale_total, "numeric_source_coverage_pct": numeric_source_cov, "free_text_rationale_violation_count": free_text_violation, "gate": gate, "metrics": { "rationale_ok": rationale_ok, "actionable_rows": len(actionable) } } op.parent.mkdir(parents=True, exist_ok=True) op.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(out, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())