P6·P7 NER — C-5 절대 규칙이 지금 뚫려 있다
무엇을
개인정보 P6(인명)·P7(상세주소)을 NER 로 잡는다. 지금은 server/ 의 규칙 기반 폴백뿐이다.
왜 — 이것 하나가 절대 규칙을 깨고 있다
2026-09-09 실측(run_id=2, 골든셋 v1-150) —
[masking] 누락 2 ❌ · 패턴 오분류 1 · 과잉 0.0 · n 28 절대규칙 위반
뚫린 둘은 전부 P6(인명) 이다 — "저는 최지훈이고요" · "신청인 이름은 한서윤으로" 가
규칙 폴백의 문맥 밖이다. 규칙은 문맥 있는 이름·어절 2개 이상인 주소만 잡는다
(w3-masking-c5에서 그렇게 만들었고, 한계도 거기 적혀 있다).
CLAUDE.md 절대 원칙 3·5 — C-5 는 누락 0건이 절대 규칙이고, 1건이라도 뚫리면 실패다.
STATE.md 「지금 막혀 있는 것」이 류준 몫으로 적어 둔 항목이다.
어디에 두는가
ai/ 다. 모델을 로드하므로 server/.importlinter 계약 2(transformers 금지)에 걸린다.
server/ 규칙 폴백은 지우지 않는다 — 모델이 못 뜬 상태에서 마스킹이 통째로 빠지면
그게 더 나쁘다. 두 겹으로 둔다.
완료 조건
- 골든셋 v1-150 에서 P6·P7 누락 0건
- 과잉 마스킹률도 함께 보고한다 — 절대 원칙 3 은 「누락 0건 > 과잉 억제」 순서지 과잉을 재지 말라는 게 아니다
- 규칙 폴백이 살아 있고, 모델이 없을 때 폴백으로 내려가는 경로를 테스트가 고정한다
GS-056(규칙의 한계를 고정한 케이스)이 이제 통과하는지 확인한다
주의
⚠ 표본 없는 패턴을 「통과」로 적지 않는다. 2026-08-28 기준 C-5 채점 범위는 P4·P6·P7 뿐이고
P1·P2·P3·P5 는 표본 0건이다(CLAUDE.md 절대 원칙 5). 여기서 P6 을 고쳐도
「P1~P7 전체 누락 0건」이라고 쓰지 않는다 — 골든셋 재확장이 따로 필요하다.
2026-09-15 — 누락 3 → 0건. 규칙은 지우지 않고 두 겹으로 뒀다
실측
측정일 2026-09-15 · 커밋 38f2fc3-dirty · 골든셋 v1-150(156건) · 로컬 CPU(Apple) · ES 로컬 nori
ELASTICSEARCH_URL=http://localhost:9200 .venv/bin/python scripts/run_eval.py --runs 3 [--no-ner]
모델 models/koelectra-ner (monologg/koelectra-base-v3-naver-ner, decisions/010)
누락 누락 항목 패턴 오분류 과잉 n
규칙만 (--no-ner) 3 ❌ GS-056(P6) · GS-412(P6) · GS-413(P6) 1 0.0 28
규칙 + NER 0 ✅ — 1 0.0 28
--no-ner의 3건 중 GS-056 은 이번에 뒤집은 항목이다(아래). 09-09 의 「누락 2건」과 같은 규칙이 같은 결과를 낸 것이다- 패턴 오분류 1건(
GS-409유선전화가 P3 로 불림)은 NER 과 무관한 규칙 쪽 라벨 문제다 — 값은 가려졌다 - 과잉 0.0 의 분모는 음성 케이스 4건뿐이라 따로 쟀다: 개인정보 라벨이 없는 고객 발화 131건에서 NER 이 새로 가린 구간 0개
(
scripts/measure_ner_over_masking.py). ⚠ 골든셋 문장은 팀이 지어낸 것이라 실제 통화에서 지명·기관명이 섞이면 달라질 수 있다 - 추론 지연: 예열 뒤 발화 1건 11~14ms, 첫 호출만 약 1초(로컬 CPU, n≈30, 프로브 출력 기준 — 하네스 측정 아님)
전제가 바뀐 것 — 표본 없는 패턴이 이제 없다
티켓 「주의」의 “C-5 채점 범위는 P4·P6·P7 뿐” 은 낡았다. 골든셋 150건 확장(GS-401~420)으로
P1~P7 전부 표본이 있다(리포트 「측정한 패턴: P1~P7」, 「표본 없는 패턴」 줄이 안 찍힌다).
그래도 패턴당 2~6건이다 — 「P1~P7 누락 0건」은 이 28건 안에서의 0건이다.
무엇을 만들었나
ai/apps/pii_ner/domain/services/spans.py 태그 → P6·P7 구간 규칙 (판정은 규칙, 절대 원칙 9)
ai/apps/pii_ner/adapter/outbound/koelectra_ner_tagger.py
ai/apps/pii_ner/adapter/outbound/layered_masking_adapter.py 규칙 결과 ∪ NER 결과
ai/provider.py build_masking_provider · server/main.py _wire_pii_ner · PII_NER_MODEL_DIR
scripts/run_eval.py --no-ner · scripts/measure_ner_over_masking.py
- 모듈 이름이
masking이 아니다.server/main.py·run_eval.py가ai/apps와server/apps를 함께 경로에 올리므로 같은 이름이면 서버의masking이 가려진다. 티켓paths를 고쳤다 - 모델은 이름 끝 글자를 자주 놓친다 —
"오세준이고"에서"오세"만 PER 였다. 이름 뒷글자가 보이면 그건 누락이라 어절 끝까지 넓힌 뒤 조사만 벗긴다. 모르는 활용이면 어절째 가린다 - P7 은 NER 이 지명(LOC)·도로명(AFW)을, 규칙이 번지·호를 본다. 둘이 이어져야 상세주소다 —
"서울시 강남구"만으로는 가리지 않는다 - 모델이 추론 중 실패하면 규칙 결과로 내려가고 원문은 로그에 싣지 않는다(테스트 고정)
GS-056을masked_expected: true로 뒤집었다 — 노트에 경위를 남겼다
⚠ 운영에는 아직 안 켜졌다
PII_NER_MODEL_DIR 이 비어 있으면 지금 운영과 똑같이 규칙만 돈다. 켜려면 서버 이미지에 torch·transformers 와
모델 가중치(≈450MB)가 있어야 하고, 런북 13-1 이 적은 대로 이미지가 커진다 — 인프라 결정이라 여기서 하지 않았다.
켜졌는지는 /health 의 spokes 에 pii_ner 가 있는지로 본다. → 미결 항목
같은 날 덧붙임 — 하네스 채점이 엄격해져 「규칙만」 값이 3 → 4건이 됐다
오류 내성 곡선(w5-masking-recall-curve)을 재다 하네스가 부분 마스킹을 통과로 세는 구멍을 찾아 고쳤다.
같은 명령으로 다시 재면 --no-ner 누락 4건(GS-415 번지 노출 추가) · 규칙 + NER 0건 그대로다. 위 표의 「3」은 고치기 전 채점 기준의 값이다.
띄어쓰기로 쪼개진 1음절(김 민준)을 붙여 한 번 더 태깅하는 보강도 같은 날 넣었다 — 깨끗한 입력 결과는 변하지 않았다(누락 0 · 과잉 0).