#!/usr/bin/env python3 from __future__ import annotations import json import sys from pathlib import Path import yaml ROOT = Path(__file__).resolve().parent.parent def main() -> int: expected_precedence = ["risk_exit", "cash_floor", "anti_late_entry", "smart_money", "momentum"] files_to_check = [ ROOT / "spec" / "strategy" / "pre_distribution_early_warning_v4.yaml", ROOT / "spec" / "strategy" / "smart_money_liquidity_gate_v1.yaml", ROOT / "spec" / "09_decision_flow.yaml" ] conflict_without_precedence_count = 0 errors = [] # 1. Check spec files for conflict precedence configuration for fpath in files_to_check: if not fpath.exists(): errors.append(f"Spec file missing: {fpath.name}") conflict_without_precedence_count += 1 continue try: data = yaml.safe_load(fpath.read_text(encoding="utf-8")) or {} # Check meta or root level for conflict_precedence precedence = data.get("conflict_precedence") or (data.get("meta", {}) if isinstance(data.get("meta"), dict) else {}).get("conflict_precedence") if not precedence: errors.append(f"conflict_precedence not defined in {fpath.name}") conflict_without_precedence_count += 1 elif precedence != expected_precedence: errors.append(f"Invalid precedence in {fpath.name}: {precedence}. Expected: {expected_precedence}") conflict_without_precedence_count += 1 except Exception as e: errors.append(f"Failed to parse {fpath.name}: {e}") conflict_without_precedence_count += 1 # 2. Check gate_trace for conflict resolutions json_path = ROOT / "GatherTradingData.json" gate_trace_missing_count = 0 if json_path.exists(): try: raw_data = json.loads(json_path.read_text(encoding="utf-8")) hctx = raw_data.get("data", {}).get("_harness_context", {}) decisions = hctx.get("decisions_json", []) if isinstance(decisions, str): decisions = json.loads(decisions) # Verify if there is change from base to final, and check if explained for dec in decisions: if not isinstance(dec, dict): continue ticker = dec.get("ticker", "") base = dec.get("base_action", "") final = dec.get("final_action", "") if base and final and base != final: gate_trace = hctx.get("gate_trace_json", []) if isinstance(gate_trace, str): try: gate_trace = json.loads(gate_trace) except: gate_trace = [] trace_found = False for trace in gate_trace: if isinstance(trace, dict) and trace.get("ticker") == ticker: trace_found = True if not trace.get("explanation") and not trace.get("reason"): gate_trace_missing_count += 1 errors.append(f"Ticker {ticker} action changed from {base} to {final} but gate_trace explanation is missing") break if not trace_found: is_cash_block = (final == "WATCH_TIMING_SETUP" or final == "SELL_READY") and hctx.get("cash_floor_status") == "HARD_BLOCK" if not is_cash_block: gate_trace_missing_count += 1 errors.append(f"Ticker {ticker} action changed from {base} to {final} but no trace found in gate_trace_json") except Exception as e: errors.append(f"Failed to check trace in GatherTradingData.json: {e}") gate_passed = (conflict_without_precedence_count == 0) and (gate_trace_missing_count == 0) result = { "formula_id": "FACTOR_CONFLICT_PRECEDENCE_VALIDATOR_V1", "conflict_without_precedence_count": conflict_without_precedence_count, "gate_trace_missing_count": gate_trace_missing_count, "errors": errors, "gate": "PASS" if gate_passed else "FAIL" } # Write output to Temp out_dir = ROOT / "Temp" out_dir.mkdir(parents=True, exist_ok=True) out_path = out_dir / "factor_conflict_precedence_validation_v1.json" out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(result, ensure_ascii=True, indent=2)) return 0 if gate_passed else 1 if __name__ == "__main__": sys.exit(main())