第 13 周营收变成 0 — 把接收契约写进代码
目标
构建工具 schema.py:从每周收到的文件中提取 schema 并固化成指纹,分类列的新增、删除、重命名和类型变更,并按兼容规则自动判定。最后,通过值的分布抓出名称和类型都不变而只有含义变了的列。
为什么重要
别人提供的文件,其 schema 会在不通知的情况下变化。发送方只是整理了自己系统的字段,而接收方一直把它当作契约在使用。我们无法控制修改的一方,所以能做的只有让管道先发现“已经变了”这个事实。 发现之后更重要。因为多出一个不认识的列就停止加载,就没有人再相信这个警告;而必需列消失了却照样放行,数字就会悄悄出错。所以要把哪些会被破坏、哪些不会被破坏写成规则。默认值是两条:不认识的列放行,消失的必需列中止。 还有一样东西,schema 检查永远看不到:金额单位从韩元变成千韩元时,列名和类型都没变。检查全部通过,而销售额只剩下千分之一。这样的变化只有从值的分布才能看出来,而分布不是证据,而是线索。 评分器不会相信你的文字。它在临时目录里摆好评分器生成的每周文件,真实运行你的工具,核对分类和判定。商号和金额每次运行都会变。
步骤
- 创建并运行 /root/drift/gen_weeks.py,在 /root/drift/weeks 下生成从
w01.csv到w07.csv的文件。30 条订单始终相同,只有 schema 在变化。 - 在 /root/drift/schema.py 中实现
fingerprint,让它给出列名、类型、值的样本和指纹,并把w01.csv的指纹保存到 /root/drift/baseline.json。 - 增加
diff <기준 JSON> <파일>(占位符依次为基准 JSON、文件),让它分类列的新增和删除。 - 让
diff找出值的样本大量重合的一对,并分类为重命名。被识别为重命名的列,要从新增和删除的列表中去掉。 - 让
diff分类同一列的类型变更,同时包括名称变了、类型也变了的情况。 - 在 /root/drift/contract.json 中声明必需列、可选列和规则,并让
gate <기준 JSON> <파일>(占位符依次为基准 JSON、文件)判定 pass、warn、stop。 - 增加
watch <기준 CSV> <파일>(占位符依次为基准 CSV、文件),让它测量数值列中位数的变化,并把单位发生变化的周次写入 /root/drift/meaning.json。 - 一次性判定七个周次,生成 /root/drift/drift_report.json 和 /root/drift/drift_report.md。
参考
- 运行契约:
python3 /root/drift/schema.py fingerprint <파일>·diff <기준 JSON> <파일>·gate <기준 JSON> <파일>·watch <기준 CSV> <파일>(占位符依次为文件、基准 JSON、文件、基准 JSON、文件、基准 CSV、文件)。成功时退出码为 0,文件不存在时为 3,命令或参数个数有误时为 2。 fingerprint的响应:{"rows": 정수, "columns": [{"name": 이름, "type": 타입, "sample": [값...]}], "digest": 12자 16진수}(占位符依次为整数、名称、类型、值、12 位十六进制数)。样本是该列中不同的值排序后取前 20 个。- 指纹(digest)是把
[[이름, 타입], ...](占位符依次为名称、类型)生成不带空格的 JSON,求出 sha256,取前 12 个字符。样本不放进指纹:即使数据变了,只要 schema 相同,指纹就必须相同。 - 类型的尺子是本实验的假设。去掉空值后剩下的全都是整数的样子,为
int;包含小数的,为float;全部是YYYY-MM-DD的,为date;其余为str;没有任何值时为empty。 diff的响应:{"added": [이름...], "removed": [이름...], "renamed": [[옛이름, 새이름]...], "retyped": [[이름, 옛타입, 새타입]...]}(占位符依次为名称、名称、旧名称、新名称、名称、旧类型、新类型)。- 重命名的判断标准是:消失的列与新出现的列的值样本的杰卡德相似度在 0.8 以上。这个阈值也是本实验的假设。
gate的响应:{"verdict": "pass"|"warn"|"stop", "reasons": [문자열...]}(占位符为字符串)。reasons 要排序后输出。必需列和可选列的清单从/root/drift/contract.json中读取。- 判定规则:不认识的列放行;消失的可选列为 warn;消失的必需列为 stop;重命名为 warn;从
int变宽为float的为 warn;其他类型变更,必需列为 stop,可选列为 warn。 watch的响应:{"columns": {이름: {"median_before": 수, "median_after": 수, "ratio": 수, "flag": true|false}}}(占位符依次为名称、数值、数值、数值)。中位数保留到小数点后第三位,比例保留到第四位,四舍五入。比例在 3 以上或在三分之一以下时,flag 为 true。- 官方文档:python csv · python hashlib · python statistics · python json
- 常见错误:把值的样本也放进指纹,导致每次数据变化指纹都不同;只看名称来判断重命名;遇到不认识的列就停止;把分布变化当作证据。
生成七周的接收文件
创建并运行 /root/drift/gen_weeks.py,在 /root/drift/weeks 下生成从 w01.csv 到 w07.csv 的文件。30 条订单始终相同,只有列在每个周次发生变化。
先把基准周次的行做成字典列表,每个周次只调整列的列表和值,再重新写出即可。值必须始终相同,之后才能通过值的样本找出重命名。只有单位变化的那个周次才修改值。
把 schema 固化成指纹
在 /root/drift/schema.py 中实现 fingerprint <파일>(占位符为文件),让它给出 rows、columns、digest,并把 w01.csv 的指纹保存到 /root/drift/baseline.json。
为每一列收集值并推断类型,把不同的值排序后取前 20 个作为样本。指纹只把名称和类型连起来做哈希:把样本也放进去,每次数据变化指纹都不同,就没有用了。
区分新增的列和消失的列
增加 diff <기준 JSON> <파일>(占位符依次为基准 JSON、文件),让它给出 added、removed、renamed、retyped 四个列表。这一步只填写新增和删除也可以。
从基准指纹和新文件的指纹中取出列名集合,求差集,就得到新增和删除。列表要排序后输出:顺序如果每次运行都不同,就无法比较。
找出只改了名称的列
让 diff 在消失的列与新出现的列的值样本的杰卡德相似度达到 0.8 以上时,分类为重命名。被识别为重命名的列,要从 added 和 removed 中去掉。
只看名称,是消失了一个、新增了一个;看值的话,它们是同一列。对每个消失的列,求它与各新出现的列的样本集合的交集大小除以并集大小,选出最高的一对,只在超过阈值时才视为重命名。
找出类型变了的列
让 diff 把同名列的类型变更,以 [이름, 옛타입, 새타입](占位符依次为名称、旧类型、新类型)的形式放入 retyped。名称变了、类型也变了的情况,使用新名称放入。
类型是推断出的值,所以数据只要稍有不同就可能变化。因此要同时放入旧类型和新类型,以便区分整数改成了小数写法,和数字变成了字符串。下一步会把这两种情况当作不同的等级来处理。
把兼容规则写死在代码里
在 /root/drift/contract.json 中声明 required、optional、rules,并让 gate <기준 JSON> <파일>(占位符依次为基准 JSON、文件)给出 {"verdict": …, "reasons": [...]}。不认识的列放行,消失的必需列为 stop。
必需列的清单是由业务决定的。没有 order_id、shop_id、qty、amount_krw、ordered_at,就无法生成统计;而没有 shop_name 和 weight,统计照样能生成。reasons 要排序后输出,并在每条原因里写上是哪一列引起的。
抓住 schema 看不到的变化
增加 watch <기준 CSV> <파일>(占位符依次为基准 CSV、文件),让它测量两个文件中都有的数值列的中位数和比例。并把单位发生变化的周次,以 column、median_before、median_after、ratio、flag、schema_verdict 写入 /root/drift/meaning.json。
这种变化中,列名和类型都没有变,所以前面步骤的判定全部通过,只有值的分布会下沉。比例在 3 以上或在三分之一以下,就要标注需要人来看:这不是证据,而是线索。在 schema_verdict 中,把同一周次的 gate 判定也写上,以留下“schema 检查看不到这一点”的事实。
用一份报告说明七周的情况
把七个周次与基准周次核对,在 /root/drift/drift_report.json 中写入 baseline、weeks、pass、warn、stop,并在 /root/drift/drift_report.md 中分 ## 무엇이 바뀌었나 ## 무엇이 깨지고 무엇이 안 깨지나 ## 자동으로 잡히지 않는 것 ## 보내는 쪽과 맞출 것 四节书写(韩文标题,依次意为“什么变了”“什么会被破坏、什么不会被破坏”“无法自动抓到的内容”“需要与发送方对齐的内容”)。
weeks 是以文件名为键的对象,其中包含 verdict、added、removed、renamed、retyped。把基准周次自己也放进去,判定会得到 pass,便于核对。报告里要用数字写明出现 stop 的周次及其原因。