LabHub
배우기 러닝패스 코스

보험 도메인 심화 · 중복 청구 탐지 · 이론

같은 영수증인데 글자가 조금 다르면 시스템은 다른 청구로 본다

LabHub 에서 이어서 보기

한 줄 요약

중복 청구를 찾는 일은 같은 문자열을 찾는 일이 아니라, 정규화한 뒤에 같아지는 것을 찾고 그중 진짜 중복만 남기는 일이다. 어렵고 비싼 쪽은 찾는 것이 아니라 오탐을 줄이는 것이다.

왜 이게 필요했나

보험 청구 접수 창구는 여러 개다. 앱으로 사진을 올리고, 웹에서 다시 넣고, 창구에서 종이로 내고, 팩스로도 온다. 통신이 끊기면 앱이 같은 요청을 다시 보낸다. 그래서 같은 진료 한 건이 접수 시스템에 여러 줄로 남는 일은 예외가 아니라 일상이다.

여기에는 서로 다른 두 가지가 섞여 있다. 하나는 완전 중복이다. 청구번호까지 같은 것이 두 줄 들어와 있다. 이건 재전송이고, 청구번호로 묶으면 끝난다. 다른 하나는 사실상 중복이다. 청구번호는 다른데 같은 영수증이다. 앱에서 낸 뒤 처리가 늦는 것 같아 창구에서 한 번 더 냈다. 이쪽은 글자가 정확히 같지 않다. 앱은 이름을 "김서준" 으로 받고 창구 직원은 "김 서준" 으로 적는다. 영수증 번호를 앱은 하이픈까지 그대로 옮기고, 창구 시스템은 전각 문자로 저장한다.

두 번째를 놓치면 같은 돈이 두 번 나간다. 그런데 반대 방향의 사고가 더 조용하다. 같은 날 같은 병원에서 정말로 두 번 진료를 받은 사람의 청구를 중복으로 막으면, 고객은 이유도 모른 채 지급을 못 받고 콜센터에 전화한다. 탐지기를 만드는 일의 절반은 이 오탐을 줄이는 일이다.

어떻게 동작하나

순서는 늘 같다. 정규화 → 블로킹 → 유사도 → 임계값 → 허용 오차 → 오탐 규칙.

정규화. 표기 흔들림을 먼저 접는다. 유니코드 정규화 형식 가운데 NFKC 는 호환 분해 뒤 정규 결합을 하는 형식이고([UAX #15](https://www.unicode.org/reports/tr15/)), 전각 영숫자처럼 모양만 다른 호환 문자를 보통 형태로 접는다. 실습 이미지에서 실제로 재 보면 unicodedata.normalize("NFKC", "RC-2026")"RC-2026" 을 돌려준다. 여기에 공백 제거와 하이픈 제거를 더하면 창구마다 다른 표기가 한 값으로 모인다. [unicodedata 모듈](https://docs.python.org/3/library/unicodedata.html)과 [유니코드 다루기 안내](https://docs.python.org/3/howto/unicode.html)가 이 형식들을 설명한다.

블로킹. 25만 건을 서로 다 견주면 쌍이 300억 개가 된다. 그래서 같은 값을 공유하는 것끼리만 묶어 그 안에서만 견준다. 이 실습에서는 정규화한 병원명과 진료일을 블록 키로 쓴다. 블로킹은 공짜가 아니다. 블록 키가 틀리면 그 쌍은 애초에 후보가 되지 못한다. 병원 이름 표기가 흔들려 블록이 갈라지는 일이 자주 있어서, 블록 키에도 정규화한 값을 쓴다.

유사도. [difflib 의 SequenceMatcher](https://docs.python.org/3/library/difflib.html)를 쓴다. ratio() 는 두 열의 전체 원소 수를 T, 일치 원소 수를 M 이라 할 때 2.0*M / T 다. 같으면 1.0, 겹치는 것이 없으면 0.0 이다. 함정이 하나 있다. 두 번째 열이 200개 이상이면 자동 junk 휴리스틱이 켜져서 1% 를 넘게 차지하는 원소를 junk 로 본다. 이름과 영수증 번호는 짧아서 상관없지만, 긴 텍스트를 비교할 때는 autojunk=False 를 생각해야 한다. 그리고 이 휴리스틱은 비대칭이라 A 와 B 를 바꾸면 결과가 달라질 수 있다.

임계값과 허용 오차. 항목별 유사도를 가중합해 점수를 내고 두 개의 문턱으로 자른다. 높은 쪽 위는 중복, 가운데는 사람이 보는 구간, 아래는 별개다. 여기에 금액과 날짜의 허용 오차를 곱한다. 점수가 1.0 이어도 금액이 4천 원 벌어져 있으면 그것은 같은 영수증이 아닐 수 있다. 자동으로 막지 말고 사람에게 보낸다.

오탐 규칙. 같은 날 같은 병원에서 연달아 받은 영수증은 번호가 연번이다. 문자열 유사도로 보면 열두 자리 중 한 글자만 다르니 0.93 이 나오고, 이름과 병원까지 같으니 합산 점수가 문턱을 넘는다. 이건 중복이 아니라 이웃한 서류다. 번호의 마지막 숫자 덩어리만 1 에서 5 만큼 차이 나면 중복 후보에서 뺀다.

청구 25만 ──▶ 정규화 ──▶ 블록 ──▶ 블록 안의 쌍 ──▶ 점수 ──▶ 허용 오차 ──▶ 오탐 규칙                                                         │            │          │                                                    duplicate      review     distinct

현장에서 만나는 모습

가장 흔한 실수는 청구번호로만 묶는 것이다. 이러면 재전송은 잡히고 사실상 중복은 하나도 안 잡힌다. 그리고 그 사실이 화면에 드러나지 않는다. "중복 0건" 이라는 보고서가 매일 나오고, 지급된 뒤 감사에서 걸린다.

두 번째는 점수만 내고 근거를 안 남기는 것이다. 심사 담당자는 "0.94" 를 받아도 무엇을 확인해야 할지 모른다. 어느 항목이 같아서 점수가 올라갔는지를 목록으로 남기면, 담당자는 그 항목만 눈으로 보고 5초 만에 끝낸다. 판정 결과는 자동화의 출력이 아니라 사람의 입력이다.

세 번째는 임계값을 데이터 없이 정하는 것이다. 0.9 가 좋아 보인다는 이유로 정하면, 하루에 몇 건이 사람에게 넘어가는지 모른 채 운영을 시작한다. 조사 인력이 하루 30건인데 200건이 넘어오면 그 목록은 며칠 만에 무시된다. 임계값은 규칙이 아니라 인력과의 계약이다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

다음 실습이 쓰는 가중치(0.45·0.35·0.20)와 문턱(0.92·0.80), 허용 오차(100원·0일)는 이 실습의 가정이다. 실제로는 과거 판정 이력과 조사 인력의 하루 처리량으로 정하고, 규칙판에 판 번호를 붙여 바꿀 때마다 기록한다.

접수 시스템의 하루치 청구 표를 직접 만들고, 완전 중복부터 시작해 정규화, 블로킹, 유사도, 허용 오차, 연번 오탐 제거까지 단계별로 쌓는다. 채점기는 매번 다른 환자와 병원, 영수증 번호로 자기 청구 표를 만들어 여러분의 탐지기를 실행하고 판정을 대조한다. 마지막에는 자기 표로 판정을 내고 심사 담당자가 그대로 볼 수 있는 검토 목록과 보고서를 낸다.