"""39개 침해 케이스 커버리지 갭 분석 (계획서 2단계 정밀도 97% 근거). 목적: 현재 표절 탐지 엔진이 자동 판별 가능한 케이스(detectable_internal=True) 중, 평가/학습 샘플이 확보된 케이스와 그렇지 않은 케이스를 구분해 컴북스에 요청할 데이터의 '케이스 단위 갭'을 산출한다. (정밀도 97% 달성은 케이스 커버리지가 전제) 상태 분류: - covered : 탐지 대상 + 평가 샘플 보유 - detectable_no_data : 탐지 대상이나 샘플 없음 → 데이터 요청 대상 - out_of_engine_scope: 탐지 모듈 범위 밖(운영/약관/유족 등) → 데이터 불필요 """ from __future__ import annotations from dataclasses import dataclass @dataclass class CaseCoverage: case_id: str subgroup: str actor: str detectable: bool sample_count: int status: str def analyze_coverage(cases: list, case_sample_counts: dict[str, int] | None = None) -> dict: """taxonomy 케이스 + (옵션) 케이스별 보유 샘플 수 → 커버리지 리포트. cases: Taxonomy.cases (case_id/subgroup/actor/detectable_internal 속성 보유) case_sample_counts: {case_id: 보유 샘플 수}. None 이면 모두 0(=데이터 미보유)으로 간주. """ counts = case_sample_counts or {} rows: list[CaseCoverage] = [] for c in cases: n = counts.get(c.case_id, 0) if not c.detectable_internal: status = "out_of_engine_scope" elif n > 0: status = "covered" else: status = "detectable_no_data" rows.append(CaseCoverage( case_id=c.case_id, subgroup=c.subgroup, actor=c.actor, detectable=c.detectable_internal, sample_count=n, status=status, )) detectable = [r for r in rows if r.detectable] covered = [r for r in detectable if r.status == "covered"] need_data = [r for r in detectable if r.status == "detectable_no_data"] return { "total_cases": len(rows), "detectable_cases": len(detectable), "covered_cases": len(covered), "need_data_cases": len(need_data), "coverage_ratio": round(len(covered) / len(detectable), 4) if detectable else 0.0, "rows": rows, "data_request_list": [ {"case_id": r.case_id, "subgroup": r.subgroup, "actor": r.actor} for r in need_data ], }