Files
QuantEngineByItz/tools/validate_decision_graph_precedence_v1.py
kjh2064 af1236202d WBS-7.3: GAS→Python 마이그레이션 5개 항목 완료 (F14, F02-F06)
- F14: late_chase_risk_score 검증
  * GAS가 유일한 생산처 (Python canonical 없음)
  * migration_action: KEEP_IN_GAS로 정정, status: DONE

- F02/F03/F04/F06: priceBasis 로직 포팅
  * formulas/price_basis_v1.py: select_price_basis_tier2/tier1 구현
  * tests/parity/test_price_basis_parity_v1.py: 8 parity 테스트 (모두 PASS)
  * GAS Number.isFinite() 의미론 정확히 재현 (math.isfinite 사용)
  * 모든 테스트 112/112 PASS

남은 작업 (4개):
- F05: decision_logic (action assignment)
- F07: score_logic (threshold addition)
- F10: routing decision
- F15: late_chase_gate

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-22 22:45:00 +09:00

105 lines
3.2 KiB
Python

#!/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:
graph_path = ROOT / "spec" / "routing" / "decision_graph.yaml"
if not graph_path.exists():
print(f"Decision graph spec missing: {graph_path}")
return 1
try:
graph_data = yaml.safe_load(graph_path.read_text(encoding="utf-8")) or {}
except Exception as e:
print(f"Failed to parse decision graph: {e}")
return 1
nodes = graph_data.get("nodes", [])
edges = graph_data.get("edges", [])
# Build adjacency list
adj = {}
for node in nodes:
nid = node.get("id")
adj[nid] = []
for edge in edges:
if len(edge) == 2:
u, v = edge[0], edge[1]
if u in adj and v in adj:
adj[u].append(v)
else:
# If nodes are not declared, dynamically add them
if u not in adj:
adj[u] = []
if v not in adj:
adj[v] = []
adj[u].append(v)
errors = []
# Check topological sort order
in_degree = {n: 0 for n in adj}
for u in adj:
for v in adj[u]:
in_degree[v] += 1
# Find nodes with 0 in-degree
queue = [n for n in adj if in_degree[n] == 0]
topo_order = []
while queue:
curr = queue.pop(0)
topo_order.append(curr)
for v in adj.get(curr, []):
in_degree[v] -= 1
if in_degree[v] == 0:
queue.append(v)
# If topological sort is not successful (has cycle), fail
if len(topo_order) != len(adj):
errors.append("Decision graph contains a cycle")
gate_passed = False
else:
anti_chase_idx = -1
if "anti_chase" in topo_order:
anti_chase_idx = topo_order.index("anti_chase")
else:
errors.append("anti_chase node not found in graph")
target_nodes = ["regime", "sector_beta", "style", "sizing", "execution"]
if anti_chase_idx != -1:
for t in target_nodes:
if t in topo_order:
t_idx = topo_order.index(t)
if anti_chase_idx >= t_idx:
errors.append(f"anti_chase (index {anti_chase_idx}) does not precede {t} (index {t_idx})")
else:
# Missing target node is a failure
errors.append(f"Target node {t} not found in topological order")
gate_passed = len(errors) == 0
result = {
"formula_id": "DECISION_GRAPH_PRECEDENCE_VALIDATOR_V1",
"topo_order": topo_order,
"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 / "decision_graph_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())