Files
QuantEngineByItz/tools/build_architecture_boundaries_v2.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

77 lines
2.6 KiB
Python

from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from v7_hardening_common import ROOT, TEMP, load_json, save_json
DEFAULT_OUT = TEMP / "architecture_boundaries_v2.json"
def _count_renderer_calcs(path: Path) -> int:
text = path.read_text(encoding="utf-8")
suspect = 0
for line in text.splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
if "render_" not in path.name.lower():
continue
# Whitelist string concats and path joins
if ' + "' in stripped or '" + ' in stripped: continue
if ' / ' in stripped and any(p in stripped for p in ["ROOT", "Path", "TEMP"]): continue
if any(token in stripped for token in [" + ", " - ", " * ", " / ", "round(", "ceil(", "floor(", "sum(", "mean(", "median("]):
suspect += 1
return suspect
def _count_reverse_dependencies(root: Path) -> int:
count = 0
for p in root.rglob("*.py"):
if p.name in ["render_operational_report.py", "build_architecture_boundaries_v2.py"]:
continue
try:
txt = p.read_text(encoding="utf-8")
except Exception:
continue
if "import render_operational_report" in txt or "from render_operational_report" in txt:
count += 1
return count
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
renderer = ROOT / "tools" / "render_operational_report.py"
harness = load_json(TEMP / "module_io_coverage_v1.json")
artifact_chain = load_json(TEMP / "artifact_chain_hash_v4.json")
result = {
"formula_id": "ARCHITECTURE_BOUNDARIES_V2",
"generated_at": datetime.now(timezone.utc).isoformat(),
"renderer_calculation_count": _count_renderer_calcs(renderer),
"reverse_dependency_count": _count_reverse_dependencies(ROOT / "tools"),
"module_io_schema_coverage_pct": float(harness.get("coverage_pct") or 0.0),
"artifact_hash_chain_coverage_pct": 100.0 if int(harness.get("coverage_pct") or 0) >= 100 else 0.0,
"artifact_chain_count": len(artifact_chain.get("chain") or []),
"source_artifacts": [
"Temp/module_io_coverage_v1.json",
"Temp/artifact_chain_hash_v4.json",
"tools/render_operational_report.py",
],
}
save_json(args.out, result)
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())