Files
QuantEngineByItz/tools/build_yaml_code_coverage_v1.py
T
kjh2064 ee3e799de1 feat: 리밸런싱 엔진 V1 + GAS 버그 수정 (2026-06-13)
주요 변경:
- tools/build_rebalance_engine_v1.py: REBALANCE_ENGINE_V1 신규
  * account_snapshot 직접 합산(_build_snap_position_map) → 소수주 분리 행 병합
  * 레짐 소스 macro.REGIME_PRELIM 최우선 (GAS 와 동일)
- src/gas_adapter_parts/gdf_06_rebalance.gs: runRebalanceSheet_() 신규
  * Logger.log / getSpreadsheet_() 로 run_all 연동 수정
- src/gas_adapter_parts/gdc_01_fetch_fundamentals.gs
  * _mergePositionRecord_(): 소수주 중복 행 합산 신규
  * parseInt → parseFloat (qty, availQty)
- src/gas_adapter_parts/gdf_01_price_metrics.gs
  * 미보유 종목 SELL_READY → WATCH_EXIT_SIGNAL
- spec/41_release_dag.yaml: build_rebalance_sheet 노드 추가 (step_count 63)
- spec/51_formula_lifecycle_registry.yaml: REBALANCE_ENGINE_V1 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:20:14 +09:00

176 lines
6.5 KiB
Python

"""build_yaml_code_coverage_v1.py — YAML_TO_CODE_COVERAGE_V1
spec/13_formula_registry.yaml 의 active=true formula_id를 authoritative denominator로 삼고
tools/*.py / *.gs 구현 여부를 매핑해 yaml-to-code 커버리지 보고서를 산출한다.
산출물: Temp/yaml_code_coverage_v1.json
- yaml_formula_count: spec에 등록된 공식 수
- implemented_count: 코드에서 확인된 공식 수
- golden_test_count: tests/*.yaml / spec/formula_golden_cases_v2.yaml에 테스트가 있는 공식 수
- unimplemented_rules: 코드 미구현 공식 목록
- orphan_code_rules: 코드에는 있으나 spec에 없는 식별자 (샘플)
- coverage_ratio: implemented / total
- golden_coverage_ratio: golden_test / total
"""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUT = ROOT / "Temp" / "yaml_code_coverage_v1.json"
SPEC_DIR = ROOT / "spec"
TOOLS_DIR = ROOT / "tools"
FORMULA_YAML_FILES = [SPEC_DIR / "13_formula_registry.yaml"]
GOLDEN_YAML_FILES = [
SPEC_DIR / "formula_golden_cases_v2.yaml",
SPEC_DIR / "formula_golden_cases_v3.yaml",
SPEC_DIR / "formula_golden_cases_v4.yaml",
ROOT / "tests" / "strategy_tests.yaml",
]
GS_FILES = list(ROOT.glob("*.gs"))
PY_FILES = list(TOOLS_DIR.glob("*.py"))
ALL_CODE_FILES = GS_FILES + PY_FILES
def _load_yaml(path: Path) -> Any:
if not path.exists():
return {}
try:
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
except Exception:
return {}
def _extract_formula_ids(registry: Any) -> list[str]:
fr = (registry.get("formula_registry") or {}) if isinstance(registry, dict) else {}
return list((fr.get("formulas") or {}).keys())
def _read_code_text() -> str:
parts = []
for f in ALL_CODE_FILES:
try:
parts.append(f.read_text(encoding="utf-8"))
except Exception:
pass
return "\n".join(parts)
def _read_golden_text() -> str:
parts = []
for f in GOLDEN_YAML_FILES:
if f.exists():
try:
parts.append(f.read_text(encoding="utf-8"))
except Exception:
pass
return "\n".join(parts)
def _find_orphan_formula_ids(code_text: str, spec_ids: set[str], max_sample: int = 20) -> list[str]:
"""코드에 정의된 FORMULA_ID 패턴 중 spec에 없는 것 (샘플)."""
candidates = set(re.findall(r"\bFORMULA_ID\s*=\s*[\"']([A-Z0-9_]+)[\"']", code_text))
# also pick up python_tool formula_id strings
candidates |= set(re.findall(r'"formula_id"\s*:\s*"([A-Z0-9_]+)"', code_text))
orphans = sorted(candidates - spec_ids)
return orphans[:max_sample]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
out_path = Path(args.out) if Path(args.out).is_absolute() else ROOT / args.out
# 1) spec 공식 수집
all_spec_ids: list[str] = []
spec_sources: dict[str, str] = {}
for yf in FORMULA_YAML_FILES:
reg = _load_yaml(yf)
ids = _extract_formula_ids(reg)
for fid in ids:
# also read python_tool field for implementation source
formula_def = (reg.get("formula_registry") or {}).get("formulas", {}).get(fid, {})
if not bool(formula_def.get("active", True)):
continue
py_tool = str(formula_def.get("python_tool") or "")
gas_impl = str(formula_def.get("gas_function") or formula_def.get("gas_name") or "")
source = py_tool or gas_impl or "unknown"
all_spec_ids.append(fid)
spec_sources[fid] = source
all_spec_ids = list(dict.fromkeys(all_spec_ids)) # dedup preserving order
spec_id_set = set(all_spec_ids)
# 2) 코드에서 구현 확인
code_text = _read_code_text()
golden_text = _read_golden_text()
runtime_gas_text = "\n".join(
p.read_text(encoding="utf-8", errors="ignore")
for p in sorted(ROOT.glob("gas_*.gs"))
if p.exists()
)
rows: list[dict[str, Any]] = []
for fid in all_spec_ids:
in_code = bool(re.search(re.escape(fid), code_text))
in_golden = bool(re.search(re.escape(fid), golden_text))
declared_source = spec_sources.get(fid, "")
# Check if declared python_tool file actually exists
source_exists: bool | str = "N/A"
if declared_source and declared_source.startswith("tools/"):
source_path = ROOT / declared_source
source_exists = source_path.exists()
rows.append({
"formula_id": fid,
"in_code": in_code,
"in_golden_test": in_golden,
"declared_source": declared_source,
"source_file_exists": source_exists,
})
implemented = [r for r in rows if r["in_code"]]
unimplemented = [r for r in rows if not r["in_code"]]
golden_covered = [r for r in rows if r["in_golden_test"]]
missing_source_file = [r for r in rows if r["source_file_exists"] is False]
orphan_ids = _find_orphan_formula_ids(runtime_gas_text, spec_id_set)
result = {
"formula_id": "YAML_TO_CODE_COVERAGE_V1",
"yaml_formula_count": len(all_spec_ids),
"implemented_count": len(implemented),
"unimplemented_count": len(unimplemented),
"golden_test_count": len(golden_covered),
"missing_source_file_count": len(missing_source_file),
"orphan_code_formula_count": len(orphan_ids),
"coverage_ratio": round(len(implemented) / len(all_spec_ids), 4) if all_spec_ids else 0.0,
"golden_coverage_ratio": round(len(golden_covered) / len(all_spec_ids), 4) if all_spec_ids else 0.0,
"gate": "PASS" if not unimplemented else "WARN",
"unimplemented_rules": [r["formula_id"] for r in unimplemented],
"missing_source_file_rules": [r["formula_id"] for r in missing_source_file],
"orphan_code_formulas": orphan_ids,
"golden_uncovered_rules": [r["formula_id"] for r in rows if not r["in_golden_test"]],
"rows": rows,
}
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(
f"[YAML_TO_CODE_COVERAGE_V1] total={len(all_spec_ids)} "
f"implemented={len(implemented)} ({result['coverage_ratio']*100:.1f}%) "
f"golden={len(golden_covered)} ({result['golden_coverage_ratio']*100:.1f}%) "
f"unimplemented={len(unimplemented)} orphan={len(orphan_ids)} -> {out_path}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())