客户、订单、活跃 - 三套系统数的是三样东西
目标
用对照表把客户所说的“客户”“活跃”“订单”在三个系统中各自指什么固定下来,并用数据测量同名集合的大小和重叠。亲眼看到改变标识符规范化会使答案整个不同,再把“同名不同集合”和“异名同集合”区分开,写成报告提交。
为什么重要
开会时如果问“活跃客户有多少”,三个人会给出三个数字,而且三个可能都对。销售统计的是状态字母为 A 的行,计费统计的是可以计费的账户,运营统计的是最近登录过的人。这三个定义在各自的业务中都是对的,错的是相信一个词已经涵盖了三个定义的那一方。 这种错位不会表现为错误。查询成功,报告照常打印。等到暴露时,已经用这个数字做出了决定。所以第一周就要把术语-标识符对照表做成文件,并让汇总工具读取这个文件。 要比较集合,先得确定什么才算“同一个”。RFC 5321 第 2.4 节规定邮件地址的本地部分要区分大小写处理,而域名不区分。按规范来看,没有一个人是重叠的,而全部转为小写后几乎全部重叠:同样的数据,得出了两个答案。 评分器不会相信你写下的文字。它在每次以不同的表名和值生成的数据库上真实运行你的汇总程序,把集合大小和重叠与它自己计算的值进行核对。
步骤
- 创建并运行 /root/glossary/gen_systems.py,生成 /root/glossary/systems.db。其中包含销售(crm)、计费(bill)、运营(ops)三个系统的六张表。
- 在 /root/glossary/terms.json 中写入术语-标识符对照表。三个术语(客户、活跃、订单)乘以三个系统,共九行,每行写入 system、table、id_column 和 predicate。
- 创建 /root/glossary/census.py,让它为对照表的每一行测量集合大小,并写出 /root/glossary/census.json。
- 增加
--overlap <경로>(占位符为路径),让它写出同一术语内两两比较系统的结果。给出重叠的数量和只存在于一侧的数量。 - 增加
--normalize none|strict|loose。strict 去掉空格并只把域名转为小写,loose 去掉空格并全部转为小写。 - 用 loose 测得的值生成 /root/glossary/conflicts.json,为每个术语写入最不一致的系统对及其杰卡德系数。
- 在 /root/glossary/naming.json 中区分写入同名不同集合(杰卡德系数 0.1 以下)和异名同集合(客户术语的列对中杰卡德系数 0.5 以上)。
- 在 /root/glossary/glossary_report.md 中分四节报告。
参考
- 运行契约:
python3 /root/glossary/census.py --db <DB> --terms <대조표> --out <집계 JSON> [--overlap <겹침 JSON>] [--normalize none|strict|loose](占位符依次为对照表、汇总 JSON、重叠 JSON)把一行摘要输出到标准输出,并以退出码 0 结束。无法读取输入时为 3。 - 对照表:
{"entries": [{"term": …, "system": …, "table": …, "id_column": …, "predicate": "SQL 조건", "note": …}]}(占位符为 SQL 条件)。predicate 是直接接在WHERE之后的条件字符串,没有条件时为1=1。 - 汇总 JSON:
{"normalize": …, "counts": [{"term", "system", "table", "rows", "distinct_ids"}]}。counts 按 term、system 升序排列。rows 是符合条件的行数,distinct_ids 是规范化后标识符的不同取值数。 - 重叠 JSON:
{"normalize": …, "pairs": [{"term", "left", "right", "both", "left_only", "right_only"}]}。在同一术语内,按系统名称升序两两配对,left 排在 right 之前。 - 规范化:none 是保持原值,strict 是
공백 제거 + 마지막 @ 뒤만 소문자(韩文,意为“去除空白 + 只把最后一个 @ 之后的部分转为小写”),loose 是공백 제거 + 전부 소문자(韩文,意为“去除空白 + 全部转为小写”)。 - conflicts.json:
{"normalize": "loose", "terms": [{"term", "sizes": {시스템: 크기}, "min_pair": [시스템, 시스템], "min_jaccard": 소수점 셋째 자리, "conflict": true|false}]}(占位符依次为系统、大小、两个系统、精确到小数点后第三位的数值)。杰卡德系数是交集大小除以并集大小的值,并集为空时视为 1.0。min_jaccard 小于 1.0 时 conflict 为真。 - naming.json:
{"same_name_different_thing": [{"term", "min_pair", "jaccard"}], "different_name_same_thing": [{"a", "b", "jaccard"}]}。a 和 b 的形式为시스템.표.칼럼(占位符依次为系统、表、列),a 排在 b 之前。 - 常见错误:对照表里漏写条件(“活跃”的差别不在表,而在条件);每次统计使用不同的规范化规则;统计重叠前没有去重。
- 杰卡德阈值 0.1 和 0.5,以及四舍五入到小数点后第三位,是本实验的假设。它们不是标准规定的值,而是要写进报告后再使用的值。
- 参考文档:RFC 5321 第 2.4 节讲的是邮件地址的大小写规则,RFC 4949是处理同一个词被赋予多种含义的问题时术语表的范本,SQLite SELECT 文档说明了 INTERSECT 和 EXCEPT。
掌握三个系统
创建并运行 /root/glossary/gen_systems.py,生成 /root/glossary/systems.db。其中包含 crm_customer、crm_order、bill_account、bill_invoice、ops_user、ops_workorder 六张表。
在现场,会从三个系统各拿到一份导出数据。这里把这三份放进同一个文件。表名前面的 crm、bill、ops 表明它来自哪个系统。创建完成后,先用 sqlite3 查看表的列表和每张表的几行数据。
建立术语-标识符对照表
在 /root/glossary/terms.json 中写入九行。术语有客户、活跃、订单三个,系统有 crm、bill、ops 三个。每行写入 term、system、table、id_column 和 predicate;“活跃”与同一系统的“客户”使用同一张表,但用条件收窄范围。“订单”使用与“客户”不同的表。
缺了条件,对照表只有一半的用处。因为“活跃”的差别不在表,而在条件。每个系统用什么来区分“活跃”,打开表就能看到:状态字母、是否可以计费、最后登录日期。没有条件的行,把 predicate 设为 1=1。
测量同名集合的大小
创建 /root/glossary/census.py,让它为对照表的每一行测量符合条件的行数(rows)和标识符的不同取值数(distinct_ids),并写入 /root/glossary/census.json。counts 按 term、system 升序排列。
predicate 是直接接在 WHERE 之后的条件字符串。之所以分别统计行数和标识符的不同取值数,是因为一个人可能有多行。这一步不对值做任何处理,直接原样比较;规范化在第 5 步再加上。
两两比较系统
增加 --overlap <경로>(占位符为路径),让它写出同一术语内两两比较系统的结果。both 是两侧都有的标识符数量,left_only 和 right_only 是只存在于一侧的数量。这一步同样原样比较值。
对每个术语,把系统名称按升序排列后两两配对。有三个系统就是三对。如果原样比较值,重叠几乎会是 0,而这个结果本身就是下一步的起点:亲眼确认为什么是 0。
什么才算相同
增加 --normalize none|strict|loose。strict 去掉空格,只把最后一个 @ 之后的部分转为小写;loose 去掉空格,全部转为小写。把所用的值写入汇总 JSON 和重叠 JSON 的 normalize 字段。
RFC 5321 第 2.4 节规定邮件地址的本地部分要区分大小写处理,而域名不区分。strict 遵循这条规范,loose 则是这家客户的系统实际的做法。请亲眼看看同样的数据会得出两个答案。
同名却指向不同集合
使用 loose 测得的值,生成 /root/glossary/conflicts.json。为每个术语写入各系统的集合大小(sizes)、杰卡德系数最低的系统对(min_pair)、该系数的值(min_jaccard,保留到小数点后第三位)以及是否 conflict。
杰卡德系数是交集大小除以并集大小的值。并集为空时视为 1.0。最低的系统对有多个时,选择系统名称按升序排在前面的那一对。min_jaccard 小于 1.0 时 conflict 为真:哪怕只有一点差别,使用同一个名称也是危险的。
异名却指向同一个集合
在 /root/glossary/naming.json 中写入两个列表。same_name_different_thing 是 min_jaccard 在 0.1 以下的术语,different_name_same_thing 是客户术语的三个列对中杰卡德系数在 0.5 以上的列对。列用 시스템.표.칼럼(占位符依次为系统、表、列)的形式书写,a 排在 b 之前。
“异名同集合”比“同名不同集合”更常见,也更危险。名称不同,所以没有人去对照,也就没有人知道同一个人在三个系统里出现了三次。杰卡德系数与第 6 步使用的是同一个定义,规范化也同样是 loose。
写明要达成什么共识
在 /root/glossary/glossary_report.md 中分 ## 세 시스템이 같은 말을 쓴다 ## 용어-식별자 대조표 ## 같은 이름 다른 집합 ## 합의할 것 四节书写(韩文标题,依次意为“三个系统使用同一个词”“术语-标识符对照表”“同名不同集合”“需要达成的共识”)。三个术语的名称和各系统的集合大小都必须出现,所用的规范化规则也要写明。
报告不要手写,而是从 census、conflicts、naming 三个文件生成。这样对照表变化时,报告也会一起变化。最后一节要写的不是“统一”的提议,而是“每份报告里写明用了哪个定义”这一共识。