feat(F-SCR-001): 검토 전 기준으로 채점 중인 것을 드러낸다 — draft 17종 중 7종 (#609 ①) - #617
Conversation
`status: confirmed` 는 「근거자료 검토를 마쳤다」는 뜻으로 써 왔고 `Rubric.is_draft` 가 그
판별자인데, **그 값을 읽는 코드가 채점 경로에 하나도 없다.**
선언 app/rubrics.py Rubric.is_draft
호출 tools/find_coverage_gaps.py 하나 — app/ 안에서는 0건
실물 17종 중 7종이 draft (변액 10종 중 7종). ELS 는 전부 confirmed
`#475` ⓐ 가 「승인 → draft 로 커밋 → 나중에 confirmed」 흐름인데 **그 첫 단계가 이미 운영
채점에 들어가 있다.** 그 필드는 지금 문서일 뿐이다.
## 채점을 바꾸지 않는다 — 드러내기만 한다
draft 를 `RubricNotFound` 로 빼면(갈래 ⓐ) 그 항목이 미측정이라 R-00 이 RED 로 막는데,
그러면 「승인했는데 안 돈다」가 된다 — `appliedOnDeploy` 와 다른 종류의 조용함이고 데모
채점도 바뀐다. 판정이 그 사실을 들고 나가는 쪽(ⓑ `Judgment.rubric_status`)이 맞다고 보고
그건 계약 변경이라 `#609` 에서 강희진에게 물었다. 이 커밋은 그 전에 **아무도 모르는
상태**만 없앤다.
- `rubrics.drafts_in_scoring()` — 검토 전인데 채점에 쓰이는 항목
- 기동 로그 한 줄. `_log_enforcement_gap` 에 **합치지 않았다** — 저쪽은 「강제 통로가 없다」
(#284)이고 여기는 「기준이 검토 전이다」(#609)라 다른 사실이다. 한 함수에 두면 한쪽이
조용해져도 다른 쪽 줄에 가려 안 보인다
- ❗**0종이 돼도 말한다.** 줄이 사라지면 「검사가 사라진 것」과 구별되지 않는다
(#284 가 링크 17/17 이 된 뒤에도 강제 범위를 계속 찍는 것과 같은 판단)
- 목록을 전수로 못박는다. 늘면 「검토 안 끝난 기준이 더 들어왔다」, 줄면 누가 검토를
마친 것 — 둘 다 사건이라 양방향으로 잡는다. ELS 가 draft 가 되는 것은 따로 잡는다
(데모가 ELS 로 돈다)
## ❗못 박기는 텍스트가 아니라 동작으로 잰다
처음에 「`is_draft` 호출 0건」을 파일 텍스트로 셌더니 **내가 방금 쓴 독스트링에 걸렸다.**
재야 하는 것은 호출 여부가 아니라 동작이라, 같은 루브릭을 draft/confirmed 로 두고
`build_prompt` 결과가 **글자까지 같은 것**을 단정한다. 고쳐지면 그 줄이 빨개진다.
`xfail` 을 쓰지 않는다 — `no_skip.py` 가 skip 을 실패로 바꾼다
(`test_misconception_library_sources.py` 의 선례).
변이 역검증 — 넷 다 잡힌다.
ⓐ drafts_in_scoring 이 빈 값을 낸다 2 failed
ⓑ 경고를 info 로 낮춘다 1 failed
ⓒ 0종일 때 조용해진다 1 failed
ⓓ 채점 프롬프트가 status 를 싣는다 1 failed ← 「고쳐지면 빨개진다」가 실제로 돈다
ai-service 1138 passed · skip 0 · eval 93
Refs #609 · #475
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gitIt-sehyeon
left a comment
There was a problem hiding this comment.
판단과 구조는 좋습니다 — 채점을 안 바꾸고 사실만 드러내는 것, _log_enforcement_gap 과 안 합친 것, 0종이 돼도 말하는 것 전부 동의합니다. 다만 숫자 하나가 틀렸고 그것이 코드에도 들어가 있습니다.
❗「변액 10종 중 7종」이 아니라 「변액 7종 전부」입니다
실측했습니다.
ELS 전체 10종 · draft 0종
VARIABLE_INSURANCE 전체 7종 · draft 7종 ❗전부다
─────────────────────
전체 17종 · draft 7종
ELS 10종과 변액 7종이 뒤섞인 것으로 보입니다. 그리고 같은 파일 안에서 두 문장이 서로 다릅니다.
tests/test_draft_rubrics_in_scoring.py:35 "변액 10종 중 7종이다" ❗
tests/test_draft_rubrics_in_scoring.py:62 "변액 7종이 draft 인 것과" ✅
62행이 맞습니다. 35행만 고치면 됩니다.
실제 사실이 더 셉니다
이 PR 의 논지가 "화면이 서기 전에 이 사실이 어딘가에는 있어야 한다" 인데, 「일부가 검토 전」과 「전부가 검토 전」은 화면에서 다른 이야기입니다.
10종 중 7종 변액 항목을 열면 일부에 표시가 붙는다 — 「이건 검토 전, 저건 아니다」
7종 전부 변액 상품 전체가 검토 전 기준으로 채점된다 — 표시가 아니라 **상태**다
#612 가 머지되면 화면에서 변액을 열었을 때 예외 없이 전부 draft 입니다. ⓑ(Judgment.rubric_status)를 논의할 때도 이 차이가 걸립니다 — 항목마다 다르면 배지가 맞고, 전부면 상품 단위 문면이 맞을 수 있습니다.
제가 확인한 것
draft 7종 목록 PR 본문과 같다 (VAR-* 일곱)
is_draft 호출 (app/ 채점 경로) 0건 — scoring.py 에 is_draft·.status 둘 다 없다
(rubrics.py:194 는 이 PR 이 낸 drafts_in_scoring 자신)
tests/test_draft_rubrics_in_scoring 5 passed
❗변이 ⓓ 는 제가 확정하지 못했습니다
"채점 프롬프트가 status 를 싣는다 → 1 failed" 를 재현하려고 build_prompt 에 draft 분기를 주입했는데, 제 주입이 세 번 다 불안정했습니다(셸을 지나며 \n 이 실제 개행이 되거나, cd 가 가로채여 백업이 안 만들어지거나). 마지막 시도에서 5 passed 가 나왔지만 그때 변이가 실제로 적용된 상태였는지 확신할 수 없어 결과로 적지 않겠습니다.
같은 자리에서 #613 이 "변이가 안 잡히면 변이가 걸렸는지 먼저 본다" 를 네 번째로 적었는데, 저는 그 확인을 못 하고 멈췄습니다. ⓓ 는 그쪽 보고를 그대로 받습니다 — 다른 셋(ⓐⓑⓒ)은 구조가 단순해서 의심할 자리가 없습니다.
좋았던 것
「호출 0건」을 텍스트가 아니라 동작으로 잰 것이 이 PR 의 핵심이라고 봅니다.
draft = rubrics.get("VAR-FEE-DEDUCTION")
confirmed = dataclasses.replace(draft, status="confirmed")
assert scoring.build_prompt(draft, *args) == scoring.build_prompt(confirmed, *args)"처음에 텍스트로 셌더니 제가 방금 쓴 독스트링에 걸렸다" — 이건 그물을 만들다 그물에 걸린 것이라 기록으로 남을 값이 있습니다. 그리고 「무엇을 늘렸나」와 「동작이 바뀌었나」가 다른 층이라는 것이 이 레포에서 반복해 나온 자리입니다.
_log_enforcement_gap 에 안 합친 판단도 맞습니다. "선언한 오해를 강제할 통로가 없다" 와 "기준이 아직 검토 전이다" 는 고칠 자리가 다르고, 한 줄에 섞으면 한쪽이 닫혀도 안 보입니다 — #605 에서 deniedByRole 이 두 행으로 갈린 것과 반대 방향의 같은 문제입니다.
xfail 을 안 쓴 이유와 선례를 적어 두신 것도 좋습니다. #613 과 여기서 같은 함정이 같은 날 두 번 나왔으니, 그 두 자리에 다 적혀 있으면 세 번째는 안 날 것입니다.
35행 숫자만 고쳐 주시면 승인하겠습니다.
정세현이 잡았다. ELS 10 · 변액 7 인데 둘을 뒤섞어 적었고, 실제는 **변액 7종 전부**가
draft 다.
ELS 10종 · draft 0종 데모가 도는 상품이다
변액 7종 · draft 7종 전부다
## 사실이 더 세다
「일부가 검토 전」과 「전부가 검토 전」은 화면에서 다른 이야기다. 항목마다 갈리면 배지가
맞고, **상품 전체가 그렇다면 상품 단위 문면**이 맞을 수 있다 — `#609` ① 의 ⓑ
(`Judgment.rubric_status`)를 정할 때 이 차이가 걸린다. `#612` 가 머지되면 화면에서 변액을
열었을 때 **예외 없이 전부** draft 다.
## 숫자를 주석에만 적어서 낡았다 — 그 문장을 검사로 만든다
`test_the_split_is_by_product_not_by_item` 이 상품별 구성을 못박는다. `_DRAFT_IN_SCORING`
집합만으로는 이 사실이 안 잡힌다 — **변액 루브릭이 confirmed 로 하나 늘어도 그 집합은
그대로**이고 「전부」라는 주석만 조용히 거짓이 된다.
역검증: `VAR-FEE-DEDUCTION` 을 confirmed 로 바꾸면 4 failed.
ai-service 1138 passed · skip 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gitIt-sehyeon
left a comment
There was a problem hiding this comment.
승인합니다. 숫자가 고쳐졌고, 제가 요청한 것보다 한 칸 더 가셨습니다 — 저는 "35행만 고쳐 달라" 였는데 그 문장이 낡는 구조 자체를 없애셨습니다.
assert drafts == var, "「변액 전부가 검토 전」이 더 이상 참이 아니다 …"주석이었으면 또 낡습니다. 검사로 만들면 양방향으로 잡히고(검토가 끝나도, 변액 밖에서 새로 draft 가 나도) 실패 문면이 "위 목록 주석과 #617·#609 의 문면을 같이 고친다" 로 고칠 자리 셋을 가리킵니다.
변이로 확인했습니다
VAR-FEE-DEDUCTION 하나를 confirmed 로 바꿨습니다.
기준선 5 passed
변액 하나를 confirmed 로 4 failed
test_the_split_is_by_product_not_by_item ← 새로 넣으신 것. 의도대로
test_every_draft_rubric_is_listed
test_scoring_cannot_tell_the_two_apart
test_startup_says_we_are_scoring_with_unreviewed_rubrics
넷이 같이 뭅니다. 목록·상품별 구성·동작·로그가 한 사실에 묶여 있어서, 어느 하나만 고치고 나머지를 두는 상태가 안 생깁니다.
ⓑ 에 대한 제 의견 — 둘 다 필요하다고 봅니다
#609 코멘트에 적으신 것에 동의하고, evidence/ 쪽에서 한 줄 보탭니다.
화면 상품 단위 문면이 맞을 수 있다 — 지금은 변액을 열면 예외 없이 전부다
레코드 항목 단위여야 한다 — 교부 문서와 감사가 그 단위로 읽는다
뒤쪽이 제 자리라 조금 더 적으면, 교부 문서는 나중에 조립됩니다. ReportService 가 발행 시점 기록을 재생해서 지면을 만드는데, 그때 "이 판정의 기준이 검토된 것이었나" 를 알 방법이 레코드 말고는 없습니다. 화면 문면은 그 시점에 사라지고 없습니다.
그래서 「전부가 draft 라 화면은 한 줄이면 된다」가 레코드까지 한 줄로 줄이는 근거가 되면 안 됩니다 — 상품 구성은 바뀌고(이 PR 의 검사가 그걸 잡습니다) 기록은 append-only 라 나중에 못 고칩니다. prompt_version·parser_version 이 산출물마다 붙어 다니는 것과 같은 이유입니다.
계약 변경은 @hd0rable 님 자리이니 #609 에서 정해지는 대로 따르겠습니다.
앞서 못 한 것 하나
지난 리뷰에서 변이 ⓓ 를 재현하려다 제 주입이 불안정해 확정 못 했다고 적었는데, 이번에는 변이가 한 번에 걸렸습니다(YAML 한 글자라 셸을 안 탑니다). ⓓ 자체를 다시 재지는 않았지만, test_scoring_cannot_tell_the_two_apart 가 이번 변이에서 실제로 빨개지는 것을 봤으니 그 단정이 살아 있다는 것은 확인됐습니다.
|
@hd0rable CI 초록이고 정세현 승인 받았습니다. 가드가 「아직 승인하지 않은 리뷰어가 있습니다 (hd0rable)」로 서 있습니다.
리뷰에서 정세현이 제 숫자를 잡아 고쳤습니다(
|
hd0rable
left a comment
There was a problem hiding this comment.
승인합니다. 사실을 직접 다시 셌고 정정판 숫자가 맞습니다.
ELS 10종 · confirmed 10 · draft 0
변액 7종 · confirmed 0 · draft 7 ← 전부
app/ 안에서 is_draft 를 읽는 코드: 0건 (tools/find_coverage_gaps.py:122 하나뿐)
ai-service 1138 passed · no_skip.py → skip 0
❗변이 ⓓ 를 제 쪽에서 확정했습니다 — 정세현 님이 못 확인하신 그 자리입니다
app/scoring.py:460
- item_name=rubric.name,
+ item_name=rubric.name + (" (검토 전)" if rubric.is_draft else ""),
→ 1 failed test_scoring_cannot_tell_the_two_apart
「고쳐지면 빨개진다」가 실제로 돕니다. 텍스트로 호출 0건을 세다 자기 독스트링에 걸렸다는 것을 그대로 적어 두신 것도 값이 있습니다 — 그 함정은 #368 에서도 났습니다.
두 테스트가 겹치지 않는 것도 재 봤습니다
_DRAFT_IN_SCORING 집합과 상품별 구성이 같은 것을 두 번 재는 게 아니냐가 유일하게 의심스러운 자리였습니다. 변액에 confirmed 루브릭 하나를 더해 봤습니다.
test_the_draft_set_is_exactly_what_we_measured PASS ← draft 집합은 그대로다
test_the_split_is_by_product_not_by_item FAIL 「변액 전부가 검토 전」이 깨졌다
독스트링에 적으신 그 논거가 실측으로 섭니다. 반대 방향(draft 가 하나 늘면)은 둘 다 빨개지므로 둘째가 첫째의 부분집합이 아닙니다.
제 쪽에서 하나 보태겠습니다 — 변액은 고를 수 있는 상품의 절반입니다
「전부가 검토 전」이 왜 센지에 한 줄 더 있습니다. 커밋된 상품 목록이 둘입니다.
doc-els-kiwoom-4181 A증권 제4181회 ELS confirmed 10/10
doc-var-samsung-b2601 B생명 변액연금보험 draft 7/7 ◀━━
S-02 에서 고를 수 있는 두 상품 중 하나를 고르면 그 세션의 이해항목 전부가 검토 전 기준으로 채점됩니다. 「17종 중 7종」보다 이쪽이 규모를 정확히 말합니다 — 상품 하나가 통째입니다.
#609 ① ⓑ 에 대한 제 입장 (계약 쪽)
필드를 항목마다 두는 것이 맞다고 봅니다. 지금 실물이 상품 단위로 갈려 있지만 그것이 우연이라서요 — 상품 단위 문면으로 계약을 짜면 변액 루브릭 하나가 confirmed 되는 순간 표현할 수 없는 상태가 되고, 그때 계약을 다시 고쳐야 합니다. 이 PR 의 test_the_split_is_by_product_not_by_item 이 정확히 그 순간을 잡아 주니, 계약은 넓게 두고 화면이 「전부면 상품 단위로 뭉쳐 보여 주는」 쪽이 순서가 낫습니다.
그리고 #612 를 다시 봤습니다 — 열람 중계는 status 를 이미 그대로 싣습니다.
// domain/Rubric.java:32
public record Rubric(String itemId, String productType, String name, String status, …)그래서 ⓑ 가 남은 이유는 "화면이 알 수 없다" 가 아니라 판정·교부 문서가 알 수 없다로 좁혀집니다. 공개 화면은 #612 가 머지되면 7종을 draft 로 표시할 재료를 이미 갖습니다. 그 구별을 #609 에 옮겨 적겠습니다.
합치지 않은 판단에 동의합니다
_log_enforcement_gap 과 한 함수로 묶으면 «강제 통로 없음» 과 «검토 전» 이 한 줄에 섞입니다. 한쪽이 0 이 되는 사건도 따로 와야 하는데, 0 이 돼도 말한다를 양쪽에 각각 둔 것이 그 이유와 맞습니다.
#609① 의 앞 절반입니다 — 채점을 안 바꾸고, 아무도 모르는 상태만 없앱니다. 뒤 절반(ⓑJudgment.rubric_status)은 계약 변경이라 그 이슈에서 물었습니다.무엇이 문제인가
status: confirmed는 "근거자료 검토를 마쳤다" 는 뜻으로 써 왔고Rubric.is_draft가 그 판별자입니다. 그런데 그 값을 읽는 코드가 채점 경로에 하나도 없습니다.#475ⓐ 가 「승인 → draft 로 커밋 → 나중에 confirmed」 흐름인데 그 첫 단계가 이미 운영 채점에 들어가 있습니다. 그 필드는 지금 문서일 뿐입니다.그리고
#612(루브릭 열람 중계)가 머지되면 화면이 이 17종을 보여 줍니다 — 변액을 열면 7종 전부가 draft 입니다. 그 화면이 서기 전에 이 사실이 어딘가에는 있어야 합니다.채점을 바꾸지 않습니다
무엇을 했나
rubrics.drafts_in_scoring()— 검토 전인데 채점에 쓰이는 항목❗
_log_enforcement_gap에 합치지 않았습니다저쪽은 "선언한 오해를 강제할 통로가 없다"(
#284)이고 여기는 "기준이 아직 검토 전이다"(#609)라 다른 사실입니다. 한 함수에 두면 한쪽이 조용해져도 다른 쪽 줄에 가려 안 보입니다. 막지 않는 판단은 저쪽과 같습니다 — draft 는 데이터 오류가 아니라 아직 검토 전이라는 사실이고, 기동을 막으면 그 사이 서비스가 안 뜹니다.❗0종이 돼도 말합니다
줄이 사라지면 「검사가 사라진 것」과 구별되지 않습니다.
#284가 링크 17/17 이 된 뒤에도 강제 범위를 계속 찍는 것과 같은 판단입니다.목록은 양방향으로 잡습니다
늘면 "검토 안 끝난 기준이 운영 채점에 더 들어왔다", 줄면 누가 검토를 마친 것 — 둘 다 사건입니다. ELS 가 draft 가 되는 것은 따로 잡습니다(문자열 집합은 어느 상품인지 말하지 않는데, 데모가 ELS 로 돕니다).
❗못 박기는 텍스트가 아니라 동작으로 쟀습니다
처음에 「
is_draft호출 0건」을 파일 텍스트로 셌더니 제가 방금 쓴 독스트링에 걸렸습니다. 재야 하는 것은 호출 여부가 아니라 동작이라, 같은 루브릭을draft/confirmed로 두고build_prompt결과가 글자까지 같은 것을 단정합니다.❗여기가 빨개지면 고쳐진 것입니다 — 그때 이 테스트를 지우고 그 동작을 재는 단정으로 바꿉니다.
xfail을 쓰지 않는 이유는no_skip.py이고 선례는test_misconception_library_sources.py입니다.변이 역검증
drafts_in_scoring이 빈 값을 낸다info로 낮춘다status를 싣는다검증
Refs #609 · #475
🤖 Generated with Claude Code
❗정정 (
#617리뷰, 정세현) —700cc8a초판이 "변액 10종 중 7종" 이라고 적었습니다. 틀렸습니다 — ELS 10 · 변액 7 인데 둘을 뒤섞었고, 실제는 변액 7종 전부입니다.
사실이 더 셉니다. 「일부가 검토 전」과 「전부가 검토 전」은 화면에서 다른 이야기이고,
#609① 의 ⓑ 설계에 그대로 걸립니다 — 항목마다 갈리면 배지, 상품 전체면 상품 단위 문면입니다.#609에도 옮겨 적었습니다.❗숫자를 주석에만 적어서 낡았습니다. 그래서 그 문장을 검사로 만들었습니다(
test_the_split_is_by_product_not_by_item) —_DRAFT_IN_SCORING집합만으로는 안 잡힙니다. 변액 루브릭이 confirmed 로 하나 늘어도 그 집합은 그대로이고 「전부」라는 주석만 조용히 거짓이 됩니다.역검증:
VAR-FEE-DEDUCTION을 confirmed 로 바꾸면 4 failed.변이 ⓓ 를 확정 못 하셨다고 적어 주신 것 — 그 판단이 맞습니다. "변이가 안 잡히면 변이가 걸렸는지 먼저 본다" 를 확인 못 했으면 결과로 안 적는다가 그 규칙의 뒷면입니다. 제 쪽 재현을 다시 붙입니다.