o2o-plagiarism-ai/app/engine/case_coverage.py
hbyang 0472ae1f8a 2단계 고도화 선행 작업: 군집화·요약·평가환경·데이터 스펙 (데이터 수령 전)
컴북스 데이터 수령 전 가능한 작업을 선행 구현. 데이터 도착 즉시
학습·검증에 착수할 수 있도록 알고리즘·평가 하니스·문서를 준비.

알고리즘/파이프라인:
- engine/clustering.py: 군집화 기반 부분 표절(요소 교체) 판별,
  detect 응답에 partial_signal 필드 노출 (계획서 2단계 표절검출 고도화)
- engine/summarizer.py + POST /v1/summary: TextRank 추출적 요약 +
  통합(LLM) 골격 (과제2 ② 스토리 요약/분석)
- engine/preference.py + scripts/build_preference_dataset.py:
  Human Feedback 선호학습(DPO) 데이터 파이프라인 골격

평가 하니스 (정답셋 도착 즉시 측정):
- engine/rouge.py + scripts/eval_rouge.py: 요약 ROUGE (지표 No.7)
- engine/metadata_eval.py + scripts/eval_metadata_f1.py: 메타 추출 F1
  (지표 No.3, KLUE NER 호환)
- engine/case_coverage.py + scripts/analyze_case_coverage.py:
  39 케이스 갭 분석 → 컴북스 데이터 요청 목록 (정밀도 97% 근거)

문서:
- docs/DATA_REQUEST_SPEC.md: 요청 데이터 스펙 + 요약 정답셋/HF 라벨 가이드
- docs/INTEGRATION_INTERFACE.md: 2단계 통합 인터페이스
- docs/SW_COPYRIGHT_REGISTRATION.md: SW 저작권 등록 준비
- docs/PHASE2_PROGRESS.md, docs/CASE_COVERAGE.md

테스트 31 → 67건 전부 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:16:07 +09:00

65 lines
2.4 KiB
Python

"""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
],
}