LabHub
배우기 러닝패스 코스

Insurance Domain Deep Dive

Finding the Receipt That Was Claimed Twice

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

접수된 청구에서 완전 중복과 사실상 중복을 갈라내고, 같은 날 같은 병원의 다른 진료라는 오탐을 뺀 판정을 근거와 함께 내는 탐지기 dedup.py 를 만든다.

왜 중요한가

같은 진료 한 건이 앱·웹·창구로 여러 번 들어오는 일은 예외가 아니라 일상이다. 청구번호까지 같은 재전송은 묶으면 끝나지만, 청구번호가 다르고 표기만 흔들린 사실상 중복은 정규화해야 드러난다. 놓치면 같은 돈이 두 번 나간다. 반대 방향의 사고가 더 조용하다. 같은 날 같은 병원에서 정말로 두 번 진료받은 사람의 청구를 중복으로 막으면, 고객은 이유도 모른 채 지급을 못 받는다. 탐지기를 만드는 일의 절반은 이 오탐을 줄이는 일이고, 그래서 판정에는 반드시 근거가 붙는다. 채점기는 여러분의 판정을 믿지 않는다. 임시 청구 표를 차려 놓고 여러분의 dedup.py 를 실행해 정규화 결과·블록·점수·판정을 채점기가 계산한 값과 대조한다. 환자와 병원, 영수증 번호, 금액은 실행마다 바뀐다.

단계

  1. /root/dupclaim/gen_claims.py 를 만들어 실행해 /root/dupclaim/claims.db 에 하루치 접수분을 만든다.
  2. 청구번호가 같은 재전송을 /root/dupclaim/exact_dup.csv 로 뽑는다.
  3. /root/dupclaim/dedup.py 가 환자·병원·영수증을 정규화하고 블록 키를 계산하게 한다.
  4. dedup.py 가 블록을 만들어 견줄 쌍을 줄이게 한다.
  5. dedup.py 가 블록 안의 모든 쌍에 유사도와 합산 점수, 점수 등급을 매기게 한다.
  6. dedup.py 에 금액과 날짜의 허용 오차를 넣는다.
  7. dedup.py 가 연번 영수증을 오탐으로 빼고 최종 판정과 근거를 내게 한다.
  8. 자기 청구 표로 판정해 /root/dupclaim/dedup_result.json, /root/dupclaim/review.csv, /root/dupclaim/dup_report.md 를 남긴다.

참고

하루치 접수분 만들기

/root/dupclaim/gen_claims.py 를 만들어 실행해 /root/dupclaim/claims.db 를 만드세요. claim 표는 261행이고 서로 다른 청구번호는 250개입니다. 청구번호가 같은 재전송 10건(그중 한 건은 세 번 들어왔습니다), 정규화하면 환자·병원·영수증이 완전히 같아지지만 원문 표기는 다른 쌍 12쌍, 같은 블록 안에서 영수증이 연번인 쌍 6쌍을 심고, channel 은 app·web·counter 세 가지를 씁니다.

표기 흔들림은 공백 넣기, 하이픈을 전각으로 바꾸기, 숫자를 전각으로 쓰기, 소문자로 쓰기 네 가지면 넉넉합니다. 연번 쌍은 같은 환자·병원·진료일에 영수증 번호 끝자리만 1 올리면 됩니다. 병원과 진료일 가짓수를 좁게 잡아야 블록에 둘 이상이 모입니다.

청구번호가 같은 재전송부터 묶기

/root/dupclaim/exact_dup.csv 에 같은 청구번호가 두 번 이상 들어온 건을 머리글 claim_no,copies,first_received,last_received 로 뽑으세요. 청구번호마다 한 줄입니다.

1단계에서 만든 /root/dupclaim/claims.db 를 읽습니다. GROUP BY 와 HAVING COUNT(*) > 1 이면 끝납니다. first_received 와 last_received 는 그 청구번호로 들어온 접수 시각의 최솟값과 최댓값이고, 재전송 횟수가 늘 2 인 것은 아닙니다. 이건 사실상 중복이 아니라 재전송이라 중복 건수로 세지 않습니다.

표기 흔들림을 정규화로 접기

/root/dupclaim/dedup.py 가 재전송을 청구번호마다 한 줄로 접고, 각 청구의 patient_n hospital_n receipt_n block 을 계산해 결과 JSON 의 normalized 에 넣게 하세요.

unicodedata.normalize("NFKC", s) 가 전각 영숫자와 전각 하이픈을 보통 문자로 접습니다. 그다음 공백을 지우고, 영수증은 영숫자만 남겨 대문자로 맞춥니다. 접을 때는 접수 시각이 가장 이른 줄을 남깁니다.

블록으로 견줄 쌍 줄이기

/root/dupclaim/dedup.pyblocks 를 내게 하세요. 키는 블록 키이고 값은 그 블록에 속한 청구번호를 오름차순으로 정렬한 배열이며, 구성원이 둘 이상인 블록만 넣습니다.

블록 키에 정규화 전 병원명을 쓰면 표기가 흔들린 짝이 서로 다른 블록으로 갈라져 애초에 후보가 되지 못합니다. 25만 건을 전부 견주면 쌍이 300억 개라, 블로킹은 성능이 아니라 실현 가능성의 문제입니다.

유사도와 합산 점수 매기기

/root/dupclaim/dedup.py 가 블록 안의 모든 쌍에 name_sim receipt_sim hospital_same score score_verdict 를 매겨 pairs 에 넣게 하세요.

difflib.SequenceMatcher(None, x, y).ratio() 는 두 열의 전체 원소 수 T 와 일치 원소 수 M 에 대해 2.0*M/T 입니다. 소수 넷째 자리에서 반올림하세요. 쌍은 청구번호 오름차순으로 a 가 b 보다 앞입니다.

금액과 날짜의 허용 오차

/root/dupclaim/dedup.py 가 각 쌍에 amount_gap day_gap in_tolerance 를 붙이게 하세요. in_tolerance 는 금액 차가 100 이하이고 진료일 차가 0 일 때만 참입니다.

점수가 1.0 이어도 금액이 수천 원 벌어져 있으면 같은 영수증이 아닐 수 있습니다. 진료일은 datetime.date.fromisoformat 으로 읽어 빼면 일수가 나옵니다. 접수일이 아니라 진료일을 봅니다.

연번 영수증이라는 오탐 빼기

/root/dupclaim/dedup.py 가 각 쌍에 serial_neighbor 와 최종 verdict, 그리고 reasons 를 붙이게 하세요. 연번 영수증은 점수가 높아도 distinct 입니다.

열두 자리 번호에서 한 글자만 다르면 유사도가 0.93 을 넘습니다. 이름과 병원까지 같으니 합산 점수가 문턱을 넘지만, 이건 같은 서류가 아니라 이웃한 서류입니다. 마지막 숫자 덩어리만 떼어 비교하세요.

심사 담당자가 그대로 볼 수 있게 내기

자기 청구 표로 판정해 /root/dupclaim/dedup_result.json 을 남기고, 판정이 distinct 가 아닌 쌍을 /root/dupclaim/review.csv 에 머리글 a,b,verdict,score,amount_gap,day_gap,reasons 로 (reasons 는 세로막대로 이어서) 적으세요. /root/dupclaim/dup_report.md 에는 ## 판정 요약 ## 완전 중복과 사실상 중복 ## 사람이 봐야 하는 건 ## 오탐으로 뺀 것 ## 정규화 규칙 다섯 절을 쓰고, 요약에는 duplicate·review·distinct·serial_neighbor 의 쌍 수를 표로 적습니다.

앞 단계에서 만든 /root/dupclaim/dedup.py 에 /root/dupclaim/claims.db 를 물리면 됩니다. 검토 목록은 담당자가 스프레드시트로 여는 파일입니다. 점수만 있으면 무엇을 확인할지 모르니 근거를 같이 넣습니다. 오탐 절에는 실제로 뺀 쌍의 청구번호를 적어야 다음 사람이 규칙을 검증할 수 있습니다.