Files
C.E.L_Slide_test2/tests/test_phase_z2_issue17_provisional_renderable.py
KyeongminandClaude Opus 4.8 9b79bf7538 feat(#17): generic fallback 탈출 — V4 evidence 확장 + renderable-aware provisional + 게이트 순서 버그 수정
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>
2026-07-07 13:51:00 +09:00

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"