o2o-plagiarism-ai/tests/test_preference.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

60 lines
2.0 KiB
Python

"""HF 선호학습 데이터 파이프라인 단위테스트 (계획서 2단계 고도화)."""
from __future__ import annotations
from app.engine.preference import (
PreferenceCandidate,
build_candidate,
to_dpo_record,
to_dpo_dataset,
validate_labeled,
dataset_stats,
)
def test_build_candidate_is_pending():
c = build_candidate("p1", "원문", "글A", "글B", suggested_rejected="b")
assert c.label_status == "pending"
assert "원문" in c.prompt
assert to_dpo_record(c) is None # 미라벨은 학습 레코드 없음
def test_labeled_becomes_dpo_record():
c = build_candidate("p1", "원문", "정상글", "표절글", suggested_rejected="b")
c.label_status = "labeled"
c.chosen = "정상글"
c.rejected = "표절글"
rec = to_dpo_record(c)
assert rec is not None
assert rec["chosen"] == "정상글" and rec["rejected"] == "표절글"
def test_validate_catches_same_chosen_rejected():
c = PreferenceCandidate("p1", "p", "a", "b", label_status="labeled", chosen="x", rejected="x")
errors = validate_labeled(c)
assert any("chosen == rejected" in e for e in errors)
def test_validate_catches_missing():
c = PreferenceCandidate("p1", "p", "a", "b", label_status="labeled", chosen="x", rejected=None)
assert any("rejected 누락" in e for e in validate_labeled(c))
def test_dataset_stats():
pending = build_candidate("p1", "o", "a", "b")
labeled = build_candidate("p2", "o", "a", "b")
labeled.label_status = "labeled"
labeled.chosen, labeled.rejected = "a", "b"
stats = dataset_stats([pending, labeled])
assert stats["total"] == 2
assert stats["labeled"] == 1
assert stats["pending"] == 1
assert stats["trainable"] == 1
assert stats["errors"] == []
def test_to_dpo_dataset_filters_pending():
cs = [build_candidate(f"p{i}", "o", "a", "b") for i in range(3)]
cs[0].label_status = "labeled"
cs[0].chosen, cs[0].rejected = "a", "b"
assert len(to_dpo_dataset(cs)) == 1