#!/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())