Files
QuantEngineByItz/tools/validate_completion_criteria_v1.py
T
kjh2064 27730704ae
Snapshot Admin Web Validation / validate-snapshot-admin-smoke (push) Has been cancelled
Snapshot Admin Web Validation / validate-snapshot-admin-full (push) Has been cancelled
Quant Engine CI/CD Pipeline / validate-core (pull_request) Has been cancelled
Quant Engine CI/CD Pipeline / validate-ui-and-storage (pull_request) Has been cancelled
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (pull_request) Has been cancelled
test(validation): 토큰 위생 및 플랫폼 통합 검증 체계 고도화
2026-06-24 18:06:05 +09:00

152 lines
5.6 KiB
Python

"""validate_completion_criteria_v1.py — COMPLETION_CRITERIA_VALIDATOR_V1
spec/30_completion_criteria_contract.yaml의 16개 기준을 build_completion_gap_v1.py
산출물(Temp/completion_gap_v1.json)로부터 프로그램적으로 검증한다.
기본 모드: 기준 파일 존재·schema·계산 정확성 검증 (FAIL 기준도 허용).
--require-pass N: N개 이상 PASS 필요.
--strict: 모든 기준 PASS 필요 (이상적 목표 — 현재 달성 불가).
종료코드: 0=검증 통과, 1=검증 실패.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_GAP = ROOT / "Temp" / "completion_gap_v1.json"
SPEC30 = ROOT / "spec" / "30_completion_criteria_contract.yaml"
def _load(path: Path) -> dict:
try:
return json.loads(path.read_text(encoding="utf-8"))
except Exception as e:
print(f"FAIL: cannot load {path}: {e}")
sys.exit(1)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--gap", default=str(DEFAULT_GAP))
ap.add_argument("--require-pass", type=int, default=0,
help="Required minimum PASS count (0=no requirement)")
ap.add_argument("--strict", action="store_true",
help="All 16 criteria must PASS (ideal goal)")
args = ap.parse_args()
gap_path = Path(args.gap) if Path(args.gap).is_absolute() else ROOT / args.gap
if not gap_path.exists():
print(f"FAIL: {gap_path} not found - run build-completion-gap-v1 first")
return 1
d = _load(gap_path)
failures: list[str] = []
# 1) schema 검증
required = ["formula_id", "total_criteria", "passed_count", "failed_count",
"pass_rate_pct", "criteria", "priority_roadmap"]
for f in required:
if f not in d:
failures.append(f"missing field: {f}")
if "workflow_disciplines" not in d:
failures.append("missing field: workflow_disciplines")
if failures:
for f in failures:
print("FAIL:", f)
return 1
total = d["total_criteria"]
passed = d["passed_count"]
pass_rate = d["pass_rate_pct"]
criteria = d["criteria"]
# 2) 16개 기준 완전성 확인
expected_criteria_count = 16
if total != expected_criteria_count:
failures.append(
f"total_criteria={total} != {expected_criteria_count} "
f"— spec/30에 {expected_criteria_count}개 기준 정의됨"
)
# 3) 계산 정확성 검증
actual_passed = sum(1 for c in criteria if c.get("status") == "PASS")
actual_failed = sum(1 for c in criteria if c.get("status") == "FAIL")
if actual_passed != passed:
failures.append(f"passed_count mismatch: declared={passed} actual={actual_passed}")
expected_rate = round(actual_passed / total * 100, 1) if total else 0
if abs(expected_rate - pass_rate) > 0.2:
failures.append(f"pass_rate_pct mismatch: declared={pass_rate} expected={expected_rate}")
# 4) 각 기준에 필수 필드 존재 확인
req_crit_fields = ["id", "target", "current", "status", "fix", "effort"]
for c in criteria:
for f in req_crit_fields:
if f not in c:
failures.append(f"criterion {c.get('id','?')} missing field: {f}")
# 5) decision_source 필드 확인 (엔진 결정론 기준)
reproducibility = next(
(c for c in criteria if c.get("id") == "decision_reproducibility_score"), {}
)
if reproducibility.get("status") != "PASS":
failures.append("CRITICAL: decision_reproducibility_score != PASS — 결정론 미달")
llm_field = next(
(c for c in criteria if c.get("id") == "llm_generated_decision_field_count"), {}
)
if str(llm_field.get("current")) != "0" and llm_field.get("current") != 0:
failures.append("CRITICAL: llm_generated_decision_field_count != 0 — LLM 판단 개입")
workflow = d.get("workflow_disciplines") if isinstance(d.get("workflow_disciplines"), dict) else {}
required_order = workflow.get("required_preimplementation_order") if isinstance(workflow.get("required_preimplementation_order"), list) else []
expected_order = [
"로드맵/현황 확인",
"WBS 작성",
"목표 설정",
"성공판단 데이터 정의",
"구현",
"사후 검증",
"증빙 기록",
]
if required_order != expected_order:
failures.append(f"workflow_disciplines.required_preimplementation_order mismatch: {required_order}")
for key in ("completion_gate_rule", "small_change_rule", "scope_change_rule", "evidence_rule"):
if not str(workflow.get(key) or "").strip():
failures.append(f"workflow_disciplines missing or empty: {key}")
if failures:
for f in failures:
print("FAIL:", f)
print(f"COMPLETION_CRITERIA_VALIDATOR_V1: FAIL ({len(failures)} issue(s))")
return 1
# 6) --require-pass 검사
if args.require_pass > 0 and passed < args.require_pass:
print(
f"REQUIRE_PASS_FAIL: {passed}/{total} < required {args.require_pass} "
f"({pass_rate}%)"
)
return 1
# 7) --strict 검사
if args.strict and passed < total:
fail_ids = [c["id"] for c in criteria if c.get("status") == "FAIL"]
print(f"STRICT_FAIL: {total-passed}/{total} criteria still FAIL: {fail_ids}")
return 1
print(
f"COMPLETION_CRITERIA_VALIDATOR_V1: OK | "
f"{passed}/{total} PASS ({pass_rate}%) | "
f"deterministic=PASS | llm_fields=0"
)
return 0
if __name__ == "__main__":
sys.exit(main())