1. V4 evidence 확장 (pipeline_17b_extend_missing_sections.py): '결과물이 아니라 프로세스' 원칙 — pipeline_17 과 동일 평가 코드로 누락 3개 섹션(01-intro/05-1/05-2)만 평가해 v4_full32_result.yaml 병합 (기존 무접촉, blind/ANSWER_MAP 불변). 결과: 05-1 → F20 light_edit 0.77 (design-matched!), 01-intro/05-2 → all-reject (catalog gap 정직 노출 — F19 가 05 주제와 이름까지 일치하나 partial 없음 → #2 프로모션 최우선 근거) 2. renderable-aware provisional (IMP-30 u1 정밀화): rank-1 무조건 승격 → partial 존재 AND (비-reject OR verbatim builder 보유) 첫 후보 승격. reject+builder 미보유 renderable 의 mapper 네이티브 렌더는 원문 drop 위험 (F23 1-atom 손실 실측) — 원문 보존 > design 개선 우선순위. 3. 게이트 순서 버그 수정 (_apply_quality_gate_downgrades 추출): T28.5d popup 승격 후 재계산 경로에 quality gate 강등 3종(coverage/forbidden/ consistency) 미적용 → 텍스트 손실이 overall=PASS 로 통과 (mdx05 실측). 양 경로 공통 헬퍼로 통일 — mdx04 의 #16 시점 PASS 일부가 이 버그 덕이었음을 정직하게 정정 (현재 PARTIAL + frame mismatch 라벨, 텍스트는 완전). 최종 5-MDX: 전부 missing_atoms=0 (무손실) / 01·03 PASS / 02·04·05 PARTIAL(정직 frame-mismatch 라벨) / mdx01 readiness not_ready→needs_review 개선. 게이트: 1061 passed. SHA baseline 재캡처 (정당 변경). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 lines
4.6 KiB
Python
119 lines
4.6 KiB
Python
"""issue #17 — renderable-aware provisional 선택 테스트.
|
|
|
|
계약: IMP-30 u1 provisional 합성이 rank-1 을 무조건 승격하던 것을,
|
|
partial HTML 이 존재하는 첫 judgment(rank 순) 승격으로 변경 (IMP-95 u6
|
|
partial_exists 원칙). 전부 non-renderable 이면 기존대로 rank-1 (동작 보존).
|
|
|
|
실측 근거: mdx05 05-2 rank-1 F6(compensation_complaint_map, contract-only)
|
|
승격 → mapper FitError → verbatim 복구 → selected_frame_not_applied 라벨
|
|
→ PARTIAL_COVERAGE. renderable rank-6 F23 선택 시 정상 경로.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import src.phase_z2_pipeline as _pz
|
|
from src.phase_z2_pipeline import lookup_v4_match_with_fallback
|
|
|
|
|
|
def _j(rank, template_id, frame_id, label, conf=0.5):
|
|
return {
|
|
"v4_full_rank": rank,
|
|
"template_id": template_id,
|
|
"frame_id": frame_id,
|
|
"frame_number": rank,
|
|
"confidence": conf,
|
|
"label": label,
|
|
}
|
|
|
|
|
|
def _v4(judgments):
|
|
return {"mdx_sections": {"S1": {"judgments_full32": judgments}}}
|
|
|
|
|
|
def test_provisional_skips_contract_only_frames(monkeypatch):
|
|
"""rank1/2 는 partial 없음, rank3 존재(+verbatim builder) → rank3 승격."""
|
|
monkeypatch.setattr(
|
|
_pz, "_b4_partial_exists",
|
|
lambda tid: tid == "T_renderable",
|
|
)
|
|
monkeypatch.setattr(
|
|
_pz, "_VERBATIM_BUILDER_TEMPLATE_IDS", frozenset({"T_renderable"}),
|
|
)
|
|
v4 = _v4([
|
|
_j(1, "T_contract_only_a", "F1", "reject", 0.6),
|
|
_j(2, "T_contract_only_b", "F2", "reject", 0.5),
|
|
_j(3, "T_renderable", "F3", "reject", 0.4),
|
|
])
|
|
match, trace = lookup_v4_match_with_fallback(
|
|
v4, "S1", allow_provisional=True,
|
|
)
|
|
assert match is not None
|
|
assert match.template_id == "T_renderable"
|
|
assert match.provisional is True
|
|
assert trace["selection_path"] == "provisional_renderable_rank_3"
|
|
assert trace["selected_rank"] == 3
|
|
|
|
|
|
def test_provisional_rank1_when_renderable(monkeypatch):
|
|
monkeypatch.setattr(_pz, "_b4_partial_exists", lambda tid: True)
|
|
monkeypatch.setattr(
|
|
_pz, "_VERBATIM_BUILDER_TEMPLATE_IDS", frozenset({"T_a", "T_b"}),
|
|
)
|
|
v4 = _v4([
|
|
_j(1, "T_a", "F1", "reject", 0.6),
|
|
_j(2, "T_b", "F2", "reject", 0.5),
|
|
])
|
|
match, trace = lookup_v4_match_with_fallback(
|
|
v4, "S1", allow_provisional=True,
|
|
)
|
|
assert match.template_id == "T_a"
|
|
assert trace["selection_path"] == "provisional_rank_1"
|
|
assert trace["selected_rank"] == 1
|
|
|
|
|
|
def test_provisional_all_non_renderable_keeps_rank1(monkeypatch):
|
|
"""전부 partial 없음 → 기존 동작 보존 (rank-1, 정직 라벨은 하류 게이트)."""
|
|
monkeypatch.setattr(_pz, "_b4_partial_exists", lambda tid: False)
|
|
v4 = _v4([
|
|
_j(1, "T_a", "F1", "reject", 0.6),
|
|
_j(2, "T_b", "F2", "reject", 0.5),
|
|
])
|
|
match, trace = lookup_v4_match_with_fallback(
|
|
v4, "S1", allow_provisional=True,
|
|
)
|
|
assert match.template_id == "T_a"
|
|
assert trace["selection_path"] == "provisional_rank_1"
|
|
|
|
|
|
def test_provisional_reject_without_verbatim_builder_not_promoted(monkeypatch):
|
|
"""renderable 이어도 reject + verbatim builder 미보유면 승격 금지 —
|
|
mapper 네이티브 렌더의 원문 drop 위험 (mdx05 05-2 F23 1-atom 손실 실측).
|
|
조건 만족 후보 없음 → rank-1 유지 (verbatim 복구가 무손실 보장)."""
|
|
monkeypatch.setattr(_pz, "_b4_partial_exists", lambda tid: tid == "T_renderable")
|
|
monkeypatch.setattr(_pz, "_VERBATIM_BUILDER_TEMPLATE_IDS", frozenset())
|
|
v4 = _v4([
|
|
_j(1, "T_contract_only", "F1", "reject", 0.6),
|
|
_j(2, "T_renderable", "F2", "reject", 0.4),
|
|
])
|
|
match, trace = lookup_v4_match_with_fallback(
|
|
v4, "S1", allow_provisional=True,
|
|
)
|
|
assert match.template_id == "T_contract_only"
|
|
assert trace["selection_path"] == "provisional_rank_1"
|
|
|
|
|
|
def test_provisional_non_reject_renderable_promoted_without_builder(monkeypatch):
|
|
"""light_edit 등 비-reject 라벨은 verbatim builder 없어도 승격 가능
|
|
(mapper 가 정상 fit 예상 — V4 판정 자체가 사용 가능 평가)."""
|
|
monkeypatch.setattr(_pz, "_b4_partial_exists", lambda tid: tid == "T_light")
|
|
monkeypatch.setattr(_pz, "_VERBATIM_BUILDER_TEMPLATE_IDS", frozenset())
|
|
v4 = _v4([
|
|
_j(1, "T_contract_only", "F1", "reject", 0.6),
|
|
_j(2, "T_light", "F2", "light_edit", 0.5),
|
|
])
|
|
match, trace = lookup_v4_match_with_fallback(
|
|
v4, "S1", allow_provisional=True,
|
|
)
|
|
assert match.template_id == "T_light"
|
|
# IMP-39 label-priority sort 로 light_edit 이 정렬 1위 → rank_1 경로.
|
|
assert trace["selection_path"] == "provisional_rank_1"
|