컴북스 데이터 수령 전 가능한 작업을 선행 구현. 데이터 도착 즉시 학습·검증에 착수할 수 있도록 알고리즘·평가 하니스·문서를 준비. 알고리즘/파이프라인: - 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>
65 lines
2.4 KiB
Python
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
|
|
],
|
|
}
|