o2o-plagiarism-ai/scripts/analyze_case_coverage.py
hbyang bbdda66934 chore: prune docs to score-evidence set and drop sweep intermediates
docs 24 -> 12. 성적 수치 문서와 운영에 필요한 문서만 남긴다. 삭제분은 git
이력에 남아 있으므로 필요하면 되살린다.

남김: TEST_PLAN_2026_PHASE2, PERF_EVIDENCE_CAPTURE_2026, PRECISION_TEST_
PROCEDURE, SCOPE_AND_METRICS, AI_TRAINING_RESULT x2, CASE_MATCHING_KPI
(성적 수치) / IMPLEMENTATION_RUNBOOK(King 배포 절차), API_SPEC_BAIKAL(바이칼
연동), AI_DETECTION·PRECEDENT_LISTUP(코드가 참조), COMBOOKS 회신(미발송).

지우면 끊어질 내용은 버리지 않고 합쳤다.
- CASE_MATCHING_API.md -> API_SPEC_BAIKAL.md 7절
- PRECEDENT_SOURCE_GAP.md 의 출처 미확보 14건 표 -> COMBOOKS 회신 2절
남은 문서 6곳과 analyze_case_coverage.py 의 끊어진 링크도 함께 정리했다.

reports: 파라미터 스윕 중간본 4개(batch_*.jsonl, 42MB)를 삭제한다. 참조가 없고
xlsx 짝도 없는 탐색용 산출물이다. 날짜가 붙은 실측 배치(infringement_batch_*)는
xlsx 와 함께 증빙으로 유지한다.

진행 중인 요약(No.7) 작업 파일은 건드리지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 09:55:02 +09:00

77 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""39개 침해 케이스 커버리지 갭 분석 — 컴북스 데이터 요청 근거 산출.
데이터 없이 지금 실행 가능: taxonomy(분류체계)만으로 '탐지 대상이나 샘플이 없는
케이스' 목록을 뽑아 정밀도 97% 달성을 위해 요청할 데이터를 케이스 단위로 정리한다.
옵션으로 케이스별 보유 샘플 수 파일을 주면 실제 커버리지를 계산한다.
샘플 수 파일 형식 (JSON): {"A1": 12, "A2": 0, ...}
사용:
python -m scripts.analyze_case_coverage
python -m scripts.analyze_case_coverage --samples data/eval/case_counts.json
python -m scripts.analyze_case_coverage --md
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.core.config import get_settings # noqa: E402
from app.engine.case_coverage import analyze_coverage # noqa: E402
from app.engine.taxonomy import load_taxonomy # noqa: E402
def main() -> None:
ap = argparse.ArgumentParser(description="39 케이스 커버리지 갭 분석")
ap.add_argument("--samples", default=None, help="케이스별 보유 샘플 수 JSON")
ap.add_argument("--md", action="store_true", help="Markdown 표로 출력")
args = ap.parse_args()
tax = load_taxonomy(get_settings().taxonomy_path)
if tax is None:
print("taxonomy 로드 실패", file=sys.stderr)
sys.exit(1)
counts = None
if args.samples and Path(args.samples).exists():
counts = json.loads(Path(args.samples).read_text(encoding="utf-8"))
rep = analyze_coverage(tax.cases, counts)
if args.md:
print("# 39개 침해 케이스 커버리지 갭 분석\n")
print(f"- 전체 케이스: {rep['total_cases']}")
print(f"- 엔진 탐지 대상: {rep['detectable_cases']}")
print(f"- 평가 샘플 보유(covered): {rep['covered_cases']}")
print(f"- 탐지 대상이나 데이터 없음(요청 대상): {rep['need_data_cases']}")
print(f"- 커버리지: {rep['coverage_ratio']:.1%}\n")
print("| case_id | subgroup | actor | 탐지대상 | 샘플수 | 상태 |")
print("|---|---|---|---|---|---|")
for r in rep["rows"]:
print(f"| {r.case_id} | {r.subgroup} | {r.actor} | "
f"{'O' if r.detectable else '-'} | {r.sample_count} | {r.status} |")
return
print("\n=== 39개 침해 케이스 커버리지 ===")
print(f"전체 {rep['total_cases']} / 탐지대상 {rep['detectable_cases']} / "
f"covered {rep['covered_cases']} / 요청대상 {rep['need_data_cases']} / "
f"커버리지 {rep['coverage_ratio']:.1%}")
print("\n[데이터 요청 대상 — 탐지 가능하나 샘플 없는 케이스]")
if not rep["data_request_list"]:
print(" (없음 — 모든 탐지대상 케이스에 샘플 보유)")
for item in rep["data_request_list"]:
print(f" - {item['case_id']:<4} {item['subgroup']} [{item['actor']}]")
print("\n※ '탐지 대상이나 데이터 없음' 케이스가 정밀도 97% 달성을 위한 컴북스 요청 데이터 목록입니다.")
print(" out_of_engine_scope(유족/플랫폼/약관 등)는 탐지 모듈 범위 밖 → 데이터 불필요.")
if __name__ == "__main__":
main()