同一张收据用了两次,只是文件名不同
一句话总结
理赔证明材料的检查,不是清点文件是否到齐,而是依据内容而不是名称来判定这个文件是否真的是那份材料。格式用魔数(magic bytes)判断,同一性用内容哈希判断,记录与实物之间的偏差则通过双向比对来发现。
为什么需要它
在保险理赔中,材料是赔付判断的依据。但受理柜台有多个、渠道有多个时,进入同一系统的文件状态就会五花八门。扫描仪生成的 PNG 被经办人加上 .pdf 后传上来,文件名里带着被保险人的姓名,同一张收据只改了名字就提交到两笔索赔中。审核人员只看界面上能打开的东西,所以这三种情况用肉眼都很难发现。
把检查交给人的眼睛,三件事会同时失守:材料没到齐却被当作到齐处理(赔付依据缺失),同一份材料两次被用作赔付依据(重复赔付),文件名中携带的个人信息扩散到清单、备份和日志中。这三件事一旦事后才发现,补救的代价都非常高。相反,在受理时点做自动检查则很便宜——所需要的只是一张规则表、文件开头的几个字节,以及哈希。
工作原理
检查清单作为数据,而不是代码。每种索赔类型所需的材料不同,这份清单会随产品和规定的变化而变动。把类型和材料种类放进一张有两列的表中,检查程序只需要读这张表,规定变了也不用改代码。像 SQLite 这样列类型比较宽松的存储,最好同时了解值的存储类别是什么(SQLite 数据类型)。
格式根据开头的字节来区分。扩展名是人加上的标签,没有任何保证。PDF 以 %PDF- 开头,这一点由 RFC 8118 的媒体类型注册条目明确规定;PNG 的前八个字节用十进制表示是 137 80 78 71 13 10 26 10,这由 RFC 2083 第 3.1 节定义。JPEG 以 FF D8 FF 开头,ZIP 则是 PK 后面跟着 03 04。Linux 的 file 命令所做的事,归根结底也是查看这张表。
这里有一点必须遵守。这些文件中混有 NUL 字节,所以不能使用 grep。GNU grep 会把混有 NUL 的输入视为二进制文件,只输出“Binary file matches”,或者悄悄地什么也找不到。以 NUL 开头的格式,越是如此,越会与真实文件明显不符。请用 od -c 查看,或者在 Python 中用 open(path, "rb").read(16) 读取后比较。
同一性依据内容哈希来判断。靠文件名绝对无法判断是不是同一份材料。用 hashlib 求出文件内容的 sha256,把值相同的归为一组,改名后重新提交的材料就会作为一组显现出来。把哈希写进供人阅读的清单时,通常用十六进制书写;需要把字节原样放入文本时,整理了 base16、base32、base64 各自适用场合的文档是 RFC 4648。处理路径的代码,如果使用 pathlib 而不是字符串运算,可以减少区分扩展名和名称时的失误。
记录与实物要双向比对。受理记录中有而文件不存在,与文件存在而记录中没有,是两种不同的事故。前者是赔付依据空缺,后者是留下了不知属于谁的个人信息。只看一个方向的检查程序,总会漏掉一半。
doc_rule(규칙표) ─┐
├─▶ 빠진 서류 ─┐
submission(기록) ─┤ ├─▶ 청구별 보완 요청 목록
├─▶ 고아 / 없는 파일 ┤
inbox(실물) ──────┴─▶ 형식 · 해시 · 이름 ┘
在现场相遇的样子
把期限的起算日弄错,是非常常见的情况。如果以受理日期为基准来计数,受理越晚的案件,期限就越长。起算日应当是事故日期,这条规则要用一行文字写下来,与检查程序放在一起。本实验以事故日期起 30 天作为期限,这不是实际的法定期限,而是本实验的假设。每个产品和每家公司都不同,所以在现场必须确认该公司的规定。
文件名中的个人信息也经常原样留存。内容做了去标识化处理,却不动文件名的情况很多,而清单界面、备份索引、错误日志,甚至报告的文件名,都会原样携带这个名字。为了避免检查报告本身再次传播同样的值,报告中只写笔数和规则,不写具体的值。
实际工作中真正重要的事
- 格式判定依据的是内容而不是名称。并且把判定结果留成表,以便日后可以重新统计。
- 同一份材料被重复使用,只能靠哈希发现。给人看的清单中要同时给出依据(与哪笔索赔归为一组)。
- 缺失的材料和迟交的材料处理方式不同。补充要求清单要按原因分开书写。
- 检查程序不修改收件箱。移动文件或更改名称,另有经过一致同意的流程。
下一项实验要做什么
你将亲自创建 Daon 财产保险(虚构)的 60 笔索赔和收件箱中的 189 个文件,并用规则表找出缺失的材料。编写不相信扩展名的格式判定程序,给出整个收件箱的判定表,用内容哈希把被重新使用的材料归为一组,并统计文件名中携带的个人信息。从事故日期起计算期限,把记录和实物双向比对之后,以各笔索赔的补充要求清单和报告收尾。