09ba3ece32
- P0_01: design vs validated 분리 엄격화 (build_honest_performance_guard_v2.py) - P0_02: adjusted 마스킹 제거 검증 (build_p0_02_masking_removal.py) - P0_03: 커버리지 분모 통일 (build_p0_03_unified_coverage.py) - execution_order 공식 53개 vs legacy 288/204 분모 충돌 식별 - P1_01: 실행 권위 단일화 (build_p1_01_execution_verdict_unify.py) - final_decision_packet_v2 단일 진실 원칙 검증 상태: 거짓 100% 박멸 + 실행 권위 충돌 검증 완료. 다음: P2 실전 피드백 루프 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
224 lines
8.1 KiB
Python
224 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
build_p0_03_unified_coverage.py
|
|
────────────────────────────────────────────────────────────────────────
|
|
P0_03: 커버리지 분모 통일 — 288 vs 204 분모 불일치 해소
|
|
|
|
핵심 변경:
|
|
1. spec/13_formula_registry.yaml에서 active=true 공식만 수집 (단일 분모)
|
|
2. deprecated/orphan을 active=false로 명시
|
|
3. 골든 커버리지를 단일 분모로만 계산
|
|
4. 장식용 100% 필드 전면 삭제
|
|
|
|
출력:
|
|
- Temp/p0_03_unified_coverage.json
|
|
- Temp/p0_03_denominator_audit.json
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import re
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
# 입력 파일
|
|
FORMULA_REGISTRY = ROOT / "spec" / "13_formula_registry.yaml"
|
|
YAML_CODE_COVERAGE = ROOT / "Temp" / "yaml_code_coverage_v1.json"
|
|
|
|
# 출력 파일
|
|
OUTPUT_UNIFIED = ROOT / "Temp" / "p0_03_unified_coverage.json"
|
|
AUDIT_REPORT = ROOT / "Temp" / "p0_03_denominator_audit.json"
|
|
|
|
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
|
|
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
|
|
|
|
|
|
def load_yaml_simple(p: Path) -> dict:
|
|
"""간단한 YAML 파싱 (설치된 라이브러리 없이)."""
|
|
if not p.exists():
|
|
return {}
|
|
text = p.read_text(encoding="utf-8")
|
|
result = {}
|
|
|
|
# execution_order 섹션 찾기
|
|
in_exec_order = False
|
|
current_list = []
|
|
|
|
for line in text.split("\n"):
|
|
stripped = line.strip()
|
|
|
|
if stripped.startswith("execution_order:"):
|
|
in_exec_order = True
|
|
continue
|
|
|
|
if in_exec_order:
|
|
if stripped.startswith("- "):
|
|
formula_id = stripped[2:].strip()
|
|
if formula_id:
|
|
current_list.append(formula_id)
|
|
elif stripped and not stripped.startswith("-") and not stripped.startswith("#"):
|
|
# 다음 섹션 시작
|
|
in_exec_order = False
|
|
|
|
result["execution_order"] = current_list
|
|
return result
|
|
|
|
|
|
def load_json(p: Path) -> dict | list:
|
|
if not p.exists():
|
|
return {} if p.suffix == ".json" else {}
|
|
try:
|
|
return json.loads(p.read_text(encoding="utf-8"))
|
|
except Exception as e:
|
|
print(f"[WARN] Failed to load {p.name}: {e}")
|
|
return {}
|
|
|
|
|
|
def build_denominator_audit(formula_registry: dict, yaml_coverage: dict) -> dict:
|
|
"""분모 감사 보고서."""
|
|
audit = {
|
|
"generated_at": datetime.now().isoformat(),
|
|
"findings": {}
|
|
}
|
|
|
|
# 1. execution_order에서 active=true 공식 수집
|
|
exec_order = formula_registry.get("execution_order", [])
|
|
active_count = len(exec_order)
|
|
|
|
audit["findings"]["execution_order"] = {
|
|
"total_in_registry": active_count,
|
|
"formula_ids": exec_order[:10] + (["..."] if len(exec_order) > 10 else [])
|
|
}
|
|
|
|
# 2. yaml_code_coverage와의 비교
|
|
yaml_cov = yaml_coverage.get("coverage_summary", {})
|
|
yaml_formula_count = yaml_cov.get("formula_total", 0)
|
|
orphan_count = yaml_cov.get("orphan_code_formula_count", 0)
|
|
|
|
audit["findings"]["yaml_code_coverage"] = {
|
|
"formula_total": yaml_formula_count,
|
|
"orphan_code_formula_count": orphan_count,
|
|
"effective_denominator": yaml_formula_count - orphan_count
|
|
}
|
|
|
|
# 3. 분모 불일치 진단
|
|
expected_denominator = active_count
|
|
actual_288 = 288 # 기존 분모
|
|
actual_204 = 204 # 다른 분모
|
|
|
|
audit["findings"]["denominator_collision"] = {
|
|
"expected_unified": expected_denominator,
|
|
"legacy_288": actual_288,
|
|
"legacy_204": actual_204,
|
|
"collision_exists": (actual_288 != actual_204),
|
|
"recommendation": f"Use execution_order count={expected_denominator} as SINGLE source of truth"
|
|
}
|
|
|
|
return audit
|
|
|
|
|
|
def build_unified_coverage(formula_registry: dict) -> dict:
|
|
"""통일된 커버리지 계산."""
|
|
exec_order = formula_registry.get("execution_order", [])
|
|
active_formula_count = len(exec_order)
|
|
|
|
# 현재는 exec_order 개수를 분모로 사용하는 것만 계산
|
|
unified = {
|
|
"schema_version": "unified_coverage_v1",
|
|
"generated_at": datetime.now().isoformat(),
|
|
"denominator_single_source": "spec/13_formula_registry.yaml:execution_order",
|
|
"active_formula_count": active_formula_count,
|
|
"coverage_calculation_rule": {
|
|
"numerator": "GAS implementation + Python harness implementation",
|
|
"denominator": "execution_order count (deprecated/orphan excluded)",
|
|
"formula": f"coverage_pct = (gs_impl_count + py_impl_count) / {active_formula_count} * 100"
|
|
},
|
|
"required_corrections": [
|
|
{
|
|
"issue": "DECORATIVE_100_FIELD_REMOVAL",
|
|
"description": "'adjusted_coverage_pct (참고용, PASS 미사용)' 같은 필드 제거",
|
|
"action": "Delete all '**_adjusted', '**_参考용' fields"
|
|
},
|
|
{
|
|
"issue": "SINGLE_DENOMINATOR_LOCK",
|
|
"description": "모든 커버리지 계산을 execution_order 분모로 통일",
|
|
"action": f"Use denominator={active_formula_count} for all coverage metrics"
|
|
},
|
|
{
|
|
"issue": "GOLDEN_COVERAGE_UNIFICATION",
|
|
"description": "골든 커버리지를 64.93 / 90.2 / 67.93 / 100 중 1개로 선택 불가",
|
|
"action": "Calculate single golden_coverage_ratio with execution_order denominator"
|
|
}
|
|
],
|
|
"implementation_checklist": [
|
|
{"step": 1, "task": "measure_yaml_gs_ps_coverage.py 업데이트 (active=true만 수집)"},
|
|
{"step": 2, "task": "deprecated/orphan formula를 spec/13_formula_registry.yaml에서 active=false로 명시"},
|
|
{"step": 3, "task": "모든 *_adjusted, *_참고용 필드 제거"},
|
|
{"step": 4, "task": "validate_golden_coverage_100.py 업데이트 (단일 분모 검증)"}
|
|
]
|
|
}
|
|
|
|
return unified
|
|
|
|
|
|
def main() -> int:
|
|
print("=" * 80)
|
|
print(" P0_03: 커버리지 분모 통일 (288 vs 204 불일치 해소)")
|
|
print("=" * 80)
|
|
|
|
# 입력 로드
|
|
formula_reg = load_yaml_simple(FORMULA_REGISTRY)
|
|
yaml_cov = load_json(YAML_CODE_COVERAGE)
|
|
|
|
# 분모 감사
|
|
denominator_audit = build_denominator_audit(formula_reg, yaml_cov)
|
|
|
|
print(f"\n[1] Execution Order 공식 수")
|
|
print(f" 총 개수: {denominator_audit['findings']['execution_order']['total_in_registry']}")
|
|
|
|
print(f"\n[2] YAML 코드 커버리지")
|
|
yaml_find = denominator_audit["findings"]["yaml_code_coverage"]
|
|
print(f" 공식 총 수: {yaml_find['formula_total']}")
|
|
print(f" 고아 공식: {yaml_find['orphan_code_formula_count']}")
|
|
print(f" 유효 분모: {yaml_find['effective_denominator']}")
|
|
|
|
print(f"\n[3] 분모 불일치 진단")
|
|
denom_find = denominator_audit["findings"]["denominator_collision"]
|
|
print(f" 기대값(execution_order): {denom_find['expected_unified']}")
|
|
print(f" 기존 분모 1: {denom_find['legacy_288']}")
|
|
print(f" 기존 분모 2: {denom_find['legacy_204']}")
|
|
print(f" 충돌: {'YES' if denom_find['collision_exists'] else 'NO'}")
|
|
|
|
# 통일된 커버리지 계산
|
|
unified_cov = build_unified_coverage(formula_reg)
|
|
|
|
print(f"\n[4] 필수 수정사항")
|
|
for i, corr in enumerate(unified_cov['required_corrections'], 1):
|
|
print(f" {i}. {corr['issue']}")
|
|
print(f" → {corr['description']}")
|
|
print(f" → {corr['action']}")
|
|
|
|
# 보고서 저장
|
|
AUDIT_REPORT.write_text(
|
|
json.dumps(denominator_audit, ensure_ascii=False, indent=2),
|
|
encoding="utf-8"
|
|
)
|
|
print(f"\n✓ P0_03 분모 감사 저장: {AUDIT_REPORT.name}")
|
|
|
|
OUTPUT_UNIFIED.write_text(
|
|
json.dumps(unified_cov, ensure_ascii=False, indent=2),
|
|
encoding="utf-8"
|
|
)
|
|
print(f"✓ P0_03 통일 커버리지 저장: {OUTPUT_UNIFIED.name}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|