C-5 과잉 마스킹 — 「테스트 요청 (정성윤, 09-22)」에서 일반 단어 「요청」이 같이 가려진다

정성윤이 09-22 저녁 최종 QA(류준 페르소나 4 + 검증 웨이브)·운영 확인 결과를 보고 만든 티켓이다. 담당은 «제안»이다 — 본인이 확인하고 맞으면 착수, 아니면 옮긴다.

무엇을

09-22 운영 블랙리스트 요청 사유 「운영 점검용 테스트 요청 (정성윤, 09-22) — …」 가 저장 뒤 「운영 점검용 테스트 ** **, ***) — …」 가 됐다. 이름·날짜는 의도대로인데 「요청」이 이름으로 잡혔다. 「~요청 (이름」 꼴이 이름 규칙(괄호 앞 어절)에 걸린 것으로 보인다 — 가설, 확인 안 했다.

왜

재현율 우선이라 실패는 아니지만, 관리자 화면의 사유 문장이 깨진다. 원인을 알아야 비이름 목록에 넣을지 규칙을 좁힐지 정할 수 있다.

완료 조건

근거: 정성윤 13번 기록.

2026-09-22 — 원인·수정 (류준, 에이전트)

가설(「~요청 (이름」 이 규칙의 괄호 앞 어절 갈래에 걸렸다)은 틀렸다 — 규칙이 아니라 NER 겹이다. 로컬 재현: 같은 문장을 규칙(mask_text)만 돌리면 아무것도 가리지 않는다. 규칙+NER(LayeredMaskingAdapter + models/koelectra-ner)으로 돌리면 운영과 글자까지 같게 "운영 점검용 테스트 ** ****, 09-22) — …" 가 나온다. 모델 태그: 요청=PER · (=PER · 정성·윤=PER. ai/apps/pii_ner/domain/services/spans.py person_spans 가 PER 구간을 검사 없이 P6 로 넘겼다 — 「요청」 이 이름이 됐고, 「(」 도 이름에 붙어 괄호까지 가려졌다(**** 가 4글자인 이유). 뒤에 이름이 없으면 모델은 「요청」 을 태깅하지 않는다("민원 처리 요청 (지난주 접수 건)" 태그 없음). 같은 문장을 "테스트 요청 (정성윤, …)" 으로 줄이면 이번엔 「테스트」 를 PER 로 줬다. ⚠ 날짜 「09-22」 가 ***** 가 된 것은 로컬에서 재현되지 않았다 — 사유의 생략된 뒷부분(「—」 뒤)에 P5 문맥어(인증·승인·코드 등)가 있었다면 4자리 숫자가 P5 로 가려진다(가설, 확인 안 함).

수정(spans.py) — ① PER 구간 가장자리의 괄호·문장부호를 뗀다 ② _NOT_PERSON_WORDS = {"요청", "테스트"} — 조사를 벗긴 이름 구간이 통째로 그 말일 때만 뺀다("요청희" 는 그대로 이름). 실제로 걸린 두 단어만 넣었다. 결과: "운영 점검용 테스트 요청 (***, 09-22) — …". 재현 중 규칙 쪽 누락도 하나 나와 같이 고쳤다 — 호칭 규칙이 「(」 뒤·「)」 앞을 경계로 안 봐서 "(한가람 고객님)" 의 이름을 못 잡았다(name_detector.py).

골든셋 GS-430(순수 음성 — 「요청 (」 인데 개인정보 없음) · GS-431("요청 (한가람 고객님) 건 확인 부탁해요" — 이름 양성 + 「요청」 음성, raw_span 이 있어 하네스가 「요청」 이 읽히는지 본다). 과잉 마스킹률(음성 6건): 규칙+NER 1/6 = 0.167 → 0/6 = 0.0, 규칙만 0 → 0. 누락은 0 그대로 (표 전체는 w6-c5-name-tail-leak 에 같은 표로). ⚠ 순수 음성(GS-430)은 옛 코드에서도 0 이었다 — 이 과잉은 뒤에 이름이 있어야 나서 GS-431 이 잰다.

남은 것: 장민석 사후 리뷰(server/apps/masking 쪽 호칭 경계 변경).