找出用同一张收据重复申请的理赔
目标
编写检测器 dedup.py:从已受理的索赔中区分完全重复和实质重复,剔除“同一天同一家医院的不同诊疗”这类误报,并给出附带依据的判定。
为什么重要
同一次就诊通过 App、网页、柜台多次进入,不是例外,而是常态。连索赔编号都相同的重传,合并就完事,但索赔编号不同、只是写法不一致的实质重复,必须经过规范化才会显现。漏掉的话,同一笔钱就会被赔付两次。 反方向的事故更不容易被察觉。如果把同一天在同一家医院真的看了两次病的人的索赔当作重复拦下,客户就会在不知道原因的情况下领不到赔付。构建检测器的工作有一半就是减少这种误报,所以判定必须附带依据。 评分器不会相信你写的判定。它会搭建临时索赔表,运行你的 dedup.py,把规范化结果、块、分数和判定与评分器计算的值进行核对。患者、医院、收据编号和金额每次运行都会变化。
步骤
- 创建并运行
/root/dupclaim/gen_claims.py,在/root/dupclaim/claims.db中生成一天的受理数据。 - 把索赔编号相同的重传提取到
/root/dupclaim/exact_dup.csv。 - 让
/root/dupclaim/dedup.py对患者、医院、收据进行规范化,并计算块键。 - 让 dedup.py 生成块,减少需要比较的配对。
- 让 dedup.py 为块内的所有配对计算相似度、合计分数,并给出分数等级。
- 在 dedup.py 中加入金额和日期的容差。
- 让 dedup.py 把连号收据作为误报剔除,并输出最终判定和依据。
- 用自己的索赔表做出判定,留下
/root/dupclaim/dedup_result.json、/root/dupclaim/review.csv、/root/dupclaim/dup_report.md。
参考
- 表
claim有八列:claim_nopatienthospitalreceipt_noservice_date(YYYY-MM-DD)amount(整数)channelreceived_at(RFC 3339 本地时间)。 - 运行约定:
python3 /root/dupclaim/dedup.py --db <claims.db> --out <결과.json> - 重传要按每个索赔编号合并为一行。保留受理时间最早的那一行。
- 规范化:
patient_n和hospital_n是 NFKC 规范化后去除所有空格。receipt_n是 NFKC 之后只保留字母和数字并转为大写。块键是hospital_n和service_date用竖线连接而成。 blocks中只放成员有两个以上的块,值是按索赔编号升序排列的数组。- 配对只在块内生成,按索赔编号升序,
a排在b之前。 - 分数:
name_sim和receipt_sim取difflib.SequenceMatcher(None, x, y).ratio(),在小数点后第四位四舍五入。score=0.45 * receipt_sim + 0.35 * name_sim + 0.20 * (병원이 같으면 1.0, 아니면 0.0),同样在第四位四舍五入。 score_verdict:0.92 以上为 duplicate,0.80 以上为 review,更低为 distinct。- 容差:
amount_gap是金额差的绝对值,day_gap是就诊日期相差的天数。仅当amount_gap不超过 100 且day_gap为 0 时,in_tolerance才为真。 serial_neighbor:当两个receipt_n互不相同、长度相同,去掉最后一段数字后的前半部分相同、该段数字的长度也相同,并且数字之差在 1 到 5 之间时为真。- 最终
verdict:若serial_neighbor为真,则为 distinct。否则,当score_verdict为 duplicate 时,若in_tolerance为真则为 duplicate,否则为 review。其余情况保持score_verdict的值不变。 reasons按以下顺序放入:receipt_exact(receipt_sim 为 1.0)或receipt_similar(0.8 以上)、name_exact(name_sim 为 1.0)、hospital_same、amount_gap(大于 0)、day_gap(大于 0)、serial_neighbor。- 自行测试:
python3 /root/dupclaim/dedup.py --db /root/dupclaim/claims.db --out /tmp/r.json - 常见错误:只按索赔编号合并而漏掉实质重复,块键使用规范化之前的值,只给分数而不留依据。
创建一天的受理数据
创建并运行 /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.py 输出 blocks。键是块键,值是属于该块的索赔编号按升序排列的数组,只放入成员有两个以上的块。
如果块键使用规范化之前的医院名称,写法不一致的一对记录就会被分到不同的块,一开始就进不了候选。25 万笔记录全部比较会产生 300 亿个配对,所以分块不是性能问题,而是可行性问题。
计算相似度和合计分数
让 /root/dupclaim/dedup.py 为块内所有配对计算 name_sim receipt_sim hospital_same score score_verdict,并放入 pairs。
对于两个序列的元素总数 T 和匹配元素数 M,difflib.SequenceMatcher(None, x, y).ratio() 等于 2.0*M/T。请在小数点后第四位四舍五入。配对按索赔编号升序,a 排在 b 之前。
金额和日期的容差
让 /root/dupclaim/dedup.py 给每个配对加上 amount_gap day_gap in_tolerance。仅当金额差不超过 100 且就诊日期差为 0 时,in_tolerance 才为真。
即使分数是 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 即可。审核清单是经办人用电子表格打开的文件。只有分数的话,不知道该确认什么,所以要同时放入依据。误报一节要写出实际剔除的配对的索赔编号,下一个人才能验证规则。