From dc4f53cfc0ec23c5af46d5717f1f84d81584186b Mon Sep 17 00:00:00 2001 From: YONGBEOM GWON Date: Tue, 25 Aug 2026 12:01:14 +0900 Subject: [PATCH] =?UTF-8?q?fix(render):=20=EC=B5=9C=EC=A2=85=20=ED=8C=90?= =?UTF-8?q?=EC=A0=95(HUMAN=5FREVIEW)=EC=9D=84=20=EB=B0=B0=EC=A7=80?= =?UTF-8?q?=EC=97=90=20=EB=B0=98=EC=98=81=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 재교정 한도를 소진한 REVISE 는 pipeline._final_verdict() 가 HUMAN_REVIEW 로 승격시킨다(spec 6.8 case d). 그런데 렌더러와 웹 배지는 승격 전 값인 result.fidelity.verdict 를 읽고 있었다 — 그래서 "고치지 못해서 멈춘" 바로 그 경우가 화면에서는 "수정 필요" / "Fidelity 재교정됨 🔁" 로 표시됐다. 금액이 여전히 누락된 출력에 성공 신호가 붙는 셈이다. 배지가 EasyReadResult.verdict(파이프라인 최종 판정)를 따르도록 고치고, 두 값이 갈라지는 경우를 렌더·웹 양쪽에서 회귀 테스트로 고정했다. 기존 테스트는 두 값을 함께 HUMAN_REVIEW 로 세팅해 이 분기를 밟지 않았다. --- tests/render/test_render_html.py | 17 +++++++++++++++++ tests/web/test_handler.py | 17 +++++++++++++++++ ttobak/render.py | 7 +++++-- ttobak/web/app.py | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tests/render/test_render_html.py b/tests/render/test_render_html.py index 895db59..fbf46a3 100644 --- a/tests/render/test_render_html.py +++ b/tests/render/test_render_html.py @@ -93,3 +93,20 @@ def test_render_html_easy_layout_font_size(): html = render_html(_build_result()) assert "font-size: 18px" in html assert "text-align: left" in html + + +# Bug regression (2026-08-25): pipeline._final_verdict() escalates a REVISE that +# survives max_revise to HUMAN_REVIEW (spec 6.8 case d), but render_html() read +# result.fidelity.verdict — the pre-escalation value — so the exact "we could not +# fix it, so we stopped" case rendered as "수정 필요" instead of "검수 필요". +# The badge MUST follow EasyReadResult.verdict (the pipeline's final verdict). +def test_render_html_badge_follows_final_verdict_not_last_fidelity_check(): + result = _build_result() + result.fidelity = FidelityReport(slots=[], verdict=Verdict.REVISE) + result.verdict = Verdict.HUMAN_REVIEW + + html = render_html(result) + + assert "badge-human_review" in html + assert "검수 필요" in html + assert "수정 필요" not in html diff --git a/tests/web/test_handler.py b/tests/web/test_handler.py index ce2cba6..6823b0b 100644 --- a/tests/web/test_handler.py +++ b/tests/web/test_handler.py @@ -78,3 +78,20 @@ def test_handler_output_pictogram_src_is_gradio_servable(): assert 'src="assets/pictograms/' not in html, "raw core path leaked into Gradio HTML unrewritten" assert "/gradio_api/file=" in html, "pictogram present but not rewritten to Gradio serving scheme" assert str(webapp._PICTOGRAMS_DIR) in html + + +# Bug regression (2026-08-25): when the revise loop exhausts max_revise the +# pipeline's final verdict is HUMAN_REVIEW, but the web badge read +# result.fidelity.verdict (still REVISE) and showed "Fidelity 재교정됨 🔁" — +# a success signal on output that still has the amount missing. +def test_handler_badge_says_human_review_when_revise_loop_exhausts(): + # Always drops the amount and the deadline -> REVISE every round -> exhausted. + dropping = FakeProvider(default="건강보험료를 기한 안에 내세요.") + + _, _, fid_badge = webapp.simplify_handler( + "건강보험료 1,295,400원을 2026년 7월 17일까지 납부하세요.", None, + next(iter(webapp.LEVEL_CHOICES)), dropping, + ) + + assert "검수" in fid_badge + assert "재교정" not in fid_badge diff --git a/ttobak/render.py b/ttobak/render.py index 2c4c67b..0770256 100644 --- a/ttobak/render.py +++ b/ttobak/render.py @@ -57,7 +57,10 @@ def render_html(result: EasyReadResult) -> str: disclaimer, the (non-validated) K-ER score + per-rule violations, the fidelity verdict badge, and pictograms as file-path references only. """ - badge_class, badge_label = _VERDICT_BADGE[result.fidelity.verdict] + # The badge MUST show the pipeline's FINAL verdict, not the last single + # fidelity check: a REVISE that survives max_revise is escalated to + # HUMAN_REVIEW (spec 6.8 case d) and the reader has to see that. + badge_class, badge_label = _VERDICT_BADGE[result.verdict] template = _env.get_template(_TEMPLATE_NAME) return template.render( disclaimer=_DISCLAIMER, @@ -70,7 +73,7 @@ def render_html(result: EasyReadResult) -> str: {"rule": v.rule, "span": v.span, "severity": v.severity.value, "suggestion": v.suggestion} for v in result.ker.violations ], - fidelity_verdict=result.fidelity.verdict.value, + fidelity_verdict=result.verdict.value, fidelity_badge_class=badge_class, fidelity_badge_label=badge_label, pictograms=[{"src": _pictogram_src(p.glyph_id), "caption": p.caption} for p in result.pictograms], diff --git a/ttobak/web/app.py b/ttobak/web/app.py index 6e4d3c1..ef6f4f1 100644 --- a/ttobak/web/app.py +++ b/ttobak/web/app.py @@ -193,7 +193,7 @@ def simplify_handler(text_input: str, file_obj, level_label: str, provider: LLMP doc = parse(source, mime) result = simplify(doc, level, provider) html = _serve_pictograms_via_gradio(render_html(result)) - return html, _ker_badge(result.ker), _fidelity_badge(result.fidelity.verdict) + return html, _ker_badge(result.ker), _fidelity_badge(result.verdict) except Exception as exc: # noqa: BLE001 — 데모 UI는 어떤 실패도 메시지로 표시 msg = _html.escape(str(exc)) or "변환 중 오류가 발생했습니다." return f'
변환 오류: {msg}
', "", ""