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

136 lines
8.1 KiB
Python

from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_TRUTH = ROOT / "Temp" / "operational_truth_score_v1.json"
DEFAULT_DECISION = ROOT / "Temp" / "final_execution_decision_v1.json"
DEFAULT_MATRIX = ROOT / "Temp" / "execution_readiness_matrix_v1.json"
DEFAULT_DQ = ROOT / "Temp" / "data_quality_reconciliation_v1.json"
DEFAULT_SCR = ROOT / "Temp" / "smart_cash_recovery_v5.json"
DEFAULT_HARDENING = ROOT / "Temp" / "strategy_hardening_harness_v2.json"
DEFAULT_OUT = ROOT / "Temp" / "pass_100_criteria_v1.json"
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def _as_float(value: Any, default: float = 0.0) -> float:
try:
if value is None or value == "":
return default
return float(value)
except Exception:
return default
def _criterion(criterion_id: str, actual: Any, target: str, passed: bool, source_json: str, formula_id: str, remediation: str) -> dict[str, Any]:
return {
"criterion_id": criterion_id,
"actual": actual,
"target": target,
"passed": bool(passed),
"source_json": source_json,
"formula_id": formula_id,
"remediation": "NONE" if passed else remediation,
}
def main() -> int:
ap = argparse.ArgumentParser(description="Build PASS_100_CRITERIA_V1.")
ap.add_argument("--truth", default=str(DEFAULT_TRUTH))
ap.add_argument("--decision", default=str(DEFAULT_DECISION))
ap.add_argument("--matrix", default=str(DEFAULT_MATRIX))
ap.add_argument("--dq", default=str(DEFAULT_DQ))
ap.add_argument("--scr", default=str(DEFAULT_SCR))
ap.add_argument("--hardening", default=str(DEFAULT_HARDENING))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
def _rp(path_str: str) -> Path:
path = Path(path_str)
return path if path.is_absolute() else ROOT / path
truth = _load(_rp(args.truth))
decision = _load(_rp(args.decision))
matrix = _load(_rp(args.matrix))
dq = _load(_rp(args.dq))
scr = _load(_rp(args.scr))
hardening = _load(_rp(args.hardening))
domain_scores = hardening.get("domain_scores") if isinstance(hardening.get("domain_scores"), dict) else {}
meta_scores = hardening.get("meta_scores") if isinstance(hardening.get("meta_scores"), dict) else {}
basis = truth.get("metric_basis") if isinstance(truth.get("metric_basis"), dict) else {}
decision_basis = decision.get("decision_basis") if isinstance(decision.get("decision_basis"), dict) else {}
schema_score = _as_float(dq.get("schema_presence_score", basis.get("schema_presence_score")))
formula_coverage = _as_float(domain_scores.get("formula_runtime_coverage_pct", basis.get("formula_runtime_coverage_pct", 100.0)))
cap_basis = _as_float(dq.get("confidence_cap_basis_score", basis.get("confidence_cap_basis_score")))
truth_score = _as_float(truth.get("score_0_100"))
execution_score = _as_float(truth.get("execution_truth_score"))
performance_score = _as_float(truth.get("performance_readiness_score"))
scr_damage = _as_float(scr.get("value_damage_pct_avg"))
hts_count = int(_as_float(decision.get("hts_order_count")))
export_status = str(decision_basis.get("export_status") or basis.get("export_status") or "UNKNOWN")
export_allowed = decision_basis.get("export_allowed")
readiness_gate = str(meta_scores.get("readiness_gate") or decision_basis.get("readiness_gate") or "MISSING")
matrix_gate = str(matrix.get("gate") or "MISSING")
matrix_min = _as_float(matrix.get("min_axis_score"))
criteria = [
_criterion("SCHEMA_PRESENCE_100", schema_score, "== 100", schema_score == 100.0, "data_quality_reconciliation_v1.json", "DATA_QUALITY_RECONCILIATION_V1", "restore missing required JSON collections/fields"),
_criterion("FORMULA_RUNTIME_COVERAGE_100", formula_coverage, "== 100", formula_coverage == 100.0, "strategy_hardening_harness_v2.json", "STRATEGY_HARDENING_HARNESS_V2", "map every YAML formula to runtime code and validator"),
_criterion("CONFIDENCE_CAP_BASIS_GE_90", cap_basis, ">= 90", cap_basis >= 90.0, "data_quality_reconciliation_v1.json", "DATA_QUALITY_RECONCILIATION_V1", "fill actual investment-quality evidence; do not use schema score as substitute"),
_criterion("OPERATIONAL_TRUTH_SCORE_100", truth_score, "== 100 and gate PASS_100", truth_score == 100.0 and str(truth.get("gate") or "") == "PASS_100", "operational_truth_score_v1.json", "OPERATIONAL_TRUTH_SCORE_V1", "remove all truth blockers and rerun truth score"),
_criterion("EXECUTION_TRUTH_SCORE_100", execution_score, "== 100", execution_score == 100.0, "operational_truth_score_v1.json", "OPERATIONAL_TRUTH_SCORE_V1", "resolve export/order validation blockers"),
_criterion("PERFORMANCE_READINESS_GE_90", performance_score, ">= 90 and readiness_gate PERFORMANCE_READY", performance_score >= 90.0 and readiness_gate == "PERFORMANCE_READY", "operational_truth_score_v1.json", "OPERATIONAL_TRUTH_SCORE_V1", "accumulate operating T+20 samples or complete performance replay audit"),
_criterion("SMART_CASH_VALUE_DAMAGE_LE_10", scr_damage, "<= 10", scr_damage <= 10.0, "smart_cash_recovery_v5.json", "SMART_CASH_RECOVERY_V5", "rebuild rebound-preserving sell plan with value damage <= 10%"),
_criterion("SMART_CASH_EXECUTION_ALLOWED", scr.get("execution_allowed"), "is true and status PASS", bool(scr.get("execution_allowed")) and str(scr.get("status") or "") == "PASS", "smart_cash_recovery_v5.json", "SMART_CASH_RECOVERY_V5", "clear smart cash recovery hard blocks"),
_criterion("EXPORT_GATE_READY", {"status": export_status, "allowed": export_allowed}, "EXPORT_READY and allowed true", export_status == "EXPORT_READY" and export_allowed is True, "final_execution_decision_v1.json", "FINAL_EXECUTION_DECISION_V1", "rerun GAS export until export_gate_json.hts_entry_allowed=true"),
_criterion("FINAL_EXECUTION_HTS_READY", decision.get("global_execution_gate"), "HTS_READY", str(decision.get("global_execution_gate") or "") == "HTS_READY", "final_execution_decision_v1.json", "FINAL_EXECUTION_DECISION_V1", "remove truth/export/readiness blockers before HTS order generation"),
_criterion("HTS_ORDER_COUNT_GT_0", hts_count, "> 0", hts_count > 0, "final_execution_decision_v1.json", "FINAL_EXECUTION_DECISION_V1", "order_blueprint_json must contain PASS validation rows"),
_criterion("EXECUTION_READINESS_MATRIX_PASS_100", {"gate": matrix_gate, "min_axis_score": matrix_min}, "gate PASS_100 and min_axis_score 100", matrix_gate == "PASS_100" and matrix_min == 100.0, "execution_readiness_matrix_v1.json", "EXECUTION_READINESS_MATRIX_V1", "raise every readiness axis to 100 or keep EXPLAIN_ONLY"),
]
failed = [row for row in criteria if not row["passed"]]
score = round((len(criteria) - len(failed)) / len(criteria) * 100.0, 2) if criteria else 0.0
pass_allowed = len(failed) == 0
gate = "PASS_100" if pass_allowed else "BLOCK_EXECUTION"
result = {
"formula_id": "PASS_100_CRITERIA_V1",
"gate": gate,
"pass_100_allowed": pass_allowed,
"score_0_100": score,
"passed_count": len(criteria) - len(failed),
"failed_count": len(failed),
"failed_criteria": [str(row["criterion_id"]) for row in failed],
"criteria": criteria,
"targets": {
"completion_definition": "PASS_100 only when every criterion passed",
"hts_execution_definition": "HTS execution remains forbidden unless FINAL_EXECUTION_HTS_READY and HTS_ORDER_COUNT_GT_0 pass",
"llm_role": "copy criteria and render ledgers only; no ad-hoc numeric overrides",
},
}
out_path = _rp(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())