主题: 第2部分. 为什么银行数据需要规范
难度等级: 中级
预计学习时间: 3-4小时
前置要求: 对 SQL 有基本了解(SELECT、JOIN、GROUP BY、聚合函数)
了解数据集市(data marts)和分层架构(staging、intermediate、marts)的概念
初步了解 dbt(模型、not_null/unique/accepted_values 测试、dbt build 命令)
对个人数据(PII)和银行分析原理有总体了解
学习目标: 解释为什么在银行领域,数据错误比语法错误更危险,以及这与 Web 应用程序中的错误有何不同。
列出银行数据的五类风险,并为每一类制定可验证的规则(而非口号)。
区分 SDD Data 中的五个规范层(dbt 测试、ODCS、ODPS、模型规范、审查员报告),并解释每一层的具体检查内容。
将「糟糕的」数据集市规范转换为「良好的」规范,增加粒度、PII 策略、新鲜度 SLA、必需事实和负责人。
为 mart 模型制定验收标准,以便可以根据正式理由拒绝 agent 或开发人员的工作。
概述: 银行数据是一个「大致正确」代价尤其昂贵的领域:客户或支付数据集市中的错误可能看起来像带有绿色测试的常规表,但对报告、风险管理或客户服务却可能带有错误的含义。正因如此,银行背景便于学习 SDD Data(Specification-Driven Development for Data)——一种每个数据产品承诺都必须被明确记录、由具体规则限制并由证据证实的方法。本章分析了五类风险(PII、支付、同意、信贷组合、报告),解释了为什么仅有 dbt 测试是不够的,并引入了多层规范模型:测试捕获形式和部分事实,ODCS 记录技术合约,ODPS 描述数据产品和消费者,模型规范描述粒度和验收事实,而审查员报告将变更与承诺联系起来。在 customer_360 数据集市的具体示例中,展示了「自信但空洞」的糟糕规范与「狭窄但可验证」的良好规范之间的差异。本章的主要实践技能是养成问「如何证明这一点?」的习惯,并能够将任何关于数据的声明转化为命令、SQL 查询、审查员的手动步骤或人工确认。
关键概念: Sdd data: Specification-Driven Development for Data — 一种开发数据产品的方法,从明确的规范开始:产品承诺什么,谁需要它,它有哪些限制(粒度、PII、新鲜度),以及如何验证这些承诺。在 SDD Data 中,agent 或开发人员不应该猜测「正确」的含义——他得到可以根据此拒绝其工作的标准。
五类银行数据风险: 在教学银行中确定了五类风险:(1) PII 和直接标识符,(2) 支付和风险信号,(3) 数据访问同意,(4) 信贷组合和逾期,(5) 报告和计算可重现性。每个类别不仅需要 SQL 解决方案,还需要可验证的规则——「不披露个人数据」的口号必须转化为 mart 层中禁止列的具体列表以及审查员的检查。
Grain 粒度: 数据集市行的详细程度,例如「每个 customer_id 一行」。这是数据产品的承诺,不能从 dbt 测试中自动推导出来——它需要记录在规范中。粒度变化会不知不觉地破坏所有下游报告,因此粒度要么被记录,要么经负责人明确确认后更改。
Pii 和直接标识符: Personally Identifiable Information — 可以直接或间接识别客户的字段(email、phone、passport_number、ИНН 等)。SDD Data 中的 PII 策略通过具体禁令表达:例如,「models/marts/ 不包含 pii_email、phone、passport_number 列」,并在发布前由审查员检查,而不是通过关于「遵守要求」的笼统说法。
Freshness 数据新鲜度: 数据集市的时效性 SLA,例如「数据不超过 24 小时」。Freshness 必须作为可测量的承诺记录在规范中,否则消费者无法理解是否信任该数据集市。在 dbt 中通过 source freshness 支持,但策略本身仍然存在于规范中。
合约漂移: 数据集市的结构或语义对消费者来说不知不觉发生变化的情况:添加 product_code、更改 risk_flag 公式、出现新的 NULL 级别。只有将当前版本与已记录的规范(ODCS、ODPS、模型规范)进行比较,才能捕获合约漂移。
SDD Data 中的规范层: 数据产品的多层防御:(1) dbt 测试 — 形式和部分事实,(2) ODCS — 技术合约(字段、类型、负责人),(3) ODPS — 数据产品及其消费者的描述,(4) 模型规范 — 粒度和验收事实,(5) 审查员报告 — 将变更与承诺联系起来。每一层捕获其他层看不到的内容。
Odcs 和 odps: ODCS(Open Data Contract Standard)— 数据技术合约标准:字段列表、类型、负责人、约束。ODPS(Open Data Product Standard)— 数据产品描述标准:谁是消费者,产品解决什么问题,它有哪些 SLA 和验收标准。两者共同提供了合约的技术和产品方面。
验收事实 acceptance facts: 关于数据集市的具体可验证声明,必须满足:「customer_id unique/not_null」、「total_balance_rub not_null」、「risk_event_count_7d 存在」、「mart 层中不出现禁止的直接 PII 列表」。这是规范和执行之间的桥梁——它们可以自动化在测试中或手动检查。
绿色 dbt build vs 可用数据: 成功运行 dbt build 仅意味着模型图执行了并且声明的测试通过了。它不能证明选择了正确的粒度,不能证明数据集市中没有混合客户和账户,不能证明教学 risk_flag 不能被当作实战评分输出。这是工具的边界,不是它的缺点。
可验证规则 vs 口号: 本章的主要技能:任何关于数据的声明都必须转化为命令、SQL 查询、审查员的手动步骤或人工确认。「考虑风险」的口号对 agent 毫无用处;而「mart_customer_360 不包含禁止 PII 列列表中的列,在发布前由模式审查员检查」这样的规则是有用的。
数据产品负责人: 接受数据集市合约变更的个人或角色:他们是否同意新字段,是否愿意承担粒度变化的后果,在 freshness 告警触发时应通知谁。没有负责人,规范「悬在空中」,没有人阻止漂移。
练习题: 名称: 将糟糕的规范转化为可验证的规范
问题: 给定一个糟糕的数据集市规范:「为分析师制作客户数据集市。添加有关账户和操作的有用字段。检查数据质量。」将其重写为良好规范的格式(按 customer_360 的示例):指定数据产品名称、消费者、数据集市名称、粒度、PII 策略、新鲜度、必需事实。解释每个添加的条款覆盖了哪些风险。
解决方案: 步骤 1. 数据产品名称和消费者。将「为分析师」替换为具体角色,例如「客户组合分析师和信贷风险部门」。这设定了谁有权要求更改。 步骤 2. 数据集市名称。将「客户数据集市」替换为 mart_customer_360 并指定层(models/marts/)。 步骤 3. 粒度。添加「每个 customer_id 一行」——否则无法理解什么应被视为重复。 步骤 4. PII。添加「数据集市不包含直接标识符:email、phone、passport_number、ИНН」。这封闭了个人数据泄露的风险。 步骤 5. 新鲜度。添加「不超过 24 小时」——否则报告可能依赖于过时的快照。 步骤 6. 必需事实。列出:customer_id unique/not_null、total_balance_rub not_null、accounts_count >= 0、禁止 PII 列的检查通过。这就是转化为可验证声明的「数据质量」。 步骤 7. 负责人。添加「负责人:客户分析团队」——谁接受漂移。 每一条款覆盖五类风险之一,并将口号转化为可以根据此拒绝工作的标准。
难度: beginner
名称: 为三种风险制定可验证规则
问题: 拿您 dbt 项目中的任何 mart(或假设的 mart_customer_transactions)。为其写出三种风险:(1) grain,(2) PII,(3) 合约漂移。为每种风险制定规则而非口号。如果规则无法验证——将其重写为命令、SQL 或审查员的手动步骤。
解决方案: mart_customer_transactions 的示例: (1) Grain。口号:「每行一个交易」。可验证规则:「SELECT COUNT() FROM (SELECT transaction_id FROM mart_customer_transactions GROUP BY transaction_id HAVING COUNT() > 1) 应返回 0;审查员检查 transaction_id 是否包含在模型的键列表中并标记为 unique」。 (2) PII。口号:「安全」。可验证规则:「grep -E 'pii_email|phone|passport_number|card_pan' models/marts/mart_customer_transactions.sql 应返回空结果;审查员在发布前运行 list_pii_columns.sql 脚本并确认列表为空」。 (3) 合约漂移。口号:「合约稳定」。可验证规则:「当 models/marts/ 中的 schema.yml 更改时,agent 必须生成 diff_spec.md 报告,列出添加/删除的列并放入 PR;产品负责人(客户组合分析师)在合并前留下 approve 评论」。
难度: intermediate
名称: 分析:绿色 dbt build 尚不能证明正确性
问题: 在 mart_customer_360 数据集市中,所有 dbt 测试都是绿色的:customer_id unique/not_null、total_balance_rub not_null、risk_flag 的 accepted_values。但是 source_system 字段意外进入了数据集市,部分行现在按 (customer_id, source_system) 聚合,即实际粒度变为「每个 (customer_id, source_system) 一行」,尽管规范承诺「每个 customer_id 一行」。哪些测试会漏掉此问题,为什么?需要在规范中添加什么以提前捕获此类漂移?
解决方案: 步骤 1. 为什么 dbt 测试没有生效。unique/not_null 测试检查单独的列,而不是组合。customer_id 上的 unique 测试即使在表中有两个具有相同 customer_id 和不同 source_system 的行时也会通过——如果天真地按此字段分组会看到唯一性(实际上会失败,但问题在于粒度比预期的更宽)。risk_flag 上的 accepted_values 测试与粒度完全无关。因此,dbt 测试看到形式但看不到 grain 语义。 步骤 2. 规范中应包含什么。明确的声明:「粒度 = 每个 customer_id 一行」、「source_system 不属于粒度,不应出现在最终数据集市中」、「允许的列列表在 ODCS 中固定」。 步骤 3. 这包括哪些检查。自定义 dbt 测试,计算 COUNT(DISTINCT customer_id) 并与 COUNT(*) 比较:如果不相等,测试失败。审查员的手动步骤:在合并前将数据集市的列列表与 ODCS 合约核对。当 diff_spec.md 中出现 source_system 列时向负责人发出警报:需要产品负责人的批准。
难度: intermediate
名称: 在哪里记录 PII 策略
问题: 团队在争论记录客户数据集市 PII 策略的最佳位置:(a) 团队聊天中,(b) 模型开头 SQL 注释中,(c) 规范中(ODCS/ODPS/模型规范)。选择一种并基于可验证规则的想法进行论证。另外:建议如何将所选选项转换为自动检查。
解决方案: 步骤 1. 选项选择。正确答案是 (c) 规范。聊天 (a) 是约定一周后就会忘记并且对团队新成员(包括 agent)不可见的地方。SQL 注释 (b) 更接近,但注释是没有负责人和自动检查的文档:它可能被意外删除,没有人会注意到。规范 (c) 将策略记录为义务:它有负责人、版本,可以与实际架构进行比较。 步骤 2. 自动化。(1) 在 ODCS 中列出允许和禁止的列。(2) 发布前运行 list_pii_columns.py 脚本,该脚本读取 models/marts/ 中的 schema.yml 并与禁止列表进行比较——如果找到匹配,则阻止发布。(3) 在审查员报告(review_report.md)中记录:「已核对禁止 PII 列表,无匹配项」——这将手动步骤转化为可重现的检查。 步骤 3. 与本章原则的联系。任何策略都必须转化为命令、SQL、审查员的手动步骤或人工确认。聊天不提供这四个中的任何一个。
难度: intermediate
名称: 章节复习问题
问题: 详细回答来源中的三个复习问题:(1) 为什么绿色 dbt build 不能证明数据产品的正确性?(2) 哪种缺陷更危险——SQL 语法错误还是粒度的不明显变化?(3) 记录 PII 策略的最佳位置是哪里——聊天、SQL 注释还是规范?
解决方案: (1) dbt build 仅证明模型图无错误地执行并且声明的测试通过。它不检查粒度是否符合承诺,数据集市是否出现多余的列,实体(例如客户和账户)是否未发生混合,教学风险标志是否未被当作实战评分输出。这是工具的边界:测试捕获形式和部分事实,但不捕获产品语义。 (2) 不明显的粒度变化更危险。SQL 语法错误破坏管道——立即可见,它阻止发布。粒度变化不破坏任何东西:测试是绿色的,报告生成了,但数字变得虚假,这种虚假传播到所有下游数据集市和定期报告。正因如此,粒度在规范中单独记录,作为负责人的义务。 (3) 在规范中。聊天没有版本、负责人和自动检查。SQL 注释更好,但也容易丢失且没有负责人。规范(ODCS/ODPS/模型规范)是有负责人、版本和能够在发布前自动将数据集市架构与合约核对的义务。
难度: beginner
案例研究: 名称: 案例 1. 教学银行 customer_360 数据集市中的 grain 漂移
场景: 教学银行的客户组合分析师要求数据团队在 mart_customer_360 数据集市中添加 source_system 字段,以便跟踪客户来自哪个系统(移动银行、营业厅、合作伙伴计划)。收到任务的 agent 将列添加到 SELECT 中,更新了 schema.yml,运行了 dbt build——所有测试都是绿色的。数据集市的规范之前是以一般方式编写的,没有提及 source_system 或允许的列列表。
挑战: 一个月后,信贷风险部门开始抱怨他们的客户群报告有重复数据:同一个客户被计算两次,因为他现在同时来自两个系统。分析师花了一天时间才理解数据集市的粒度实际上从「每个 customer_id 一行」变为「每个 (customer_id, source_system) 一行」,尽管没有人同意这一点。没有语法错误,dbt 测试仍然是绿色的,但报告已经崩溃了。
解决方案: 数据团队进行了三项操作。(1) 通过从数据集市临时删除 source_system 并将其保留在中间层 models/intermediate/ 中来恢复粒度。(2) 澄清了 mart_customer_360 的规范:明确记录「grain = 每个 customer_id 一行」、「允许的列列表在 ODCS 中固定」、「任何列的添加需要 diff_spec.md 报告和产品负责人的批准」。(3) 添加了自定义 dbt 测试,将 COUNT(DISTINCT customer_id) 和 COUNT(*) 与数据集市进行比较:如果有差异,测试失败。(4) 在合并前引入了强制的手动审查步骤:将数据集市的 schema.yml 与 ODCS 合约核对并将链接附加到 PR。
结果: 数据集市在一个发布内返回到承诺的粒度。自定义测试和审查员的手动步骤将使此类漂移原则上不可能发生,因为在没有负责人明确决定的情况下添加 source_system 将在代码审查阶段被阻止。团队将这一事件评估为「有益的打击」:它表明「绿色 dbt build」不等于「可以使用产品」,并加速了项目所有数据集市的多层规范的引入。
经验教训: 语法正确且测试为绿色的数据集市可能在语义上被破坏——形式不等于意义。
Grain 是不能从测试中推导出来的承诺;它需要在规范中明确记录并通过单独的 COUNT(DISTINCT) 测试封闭。
在 SELECT 中添加「无害」列可以不知不觉地改变 grain 并破坏所有下游报告。
「未经负责人不得更改合约」的策略必须表达为强制性的审查员步骤,而不是聊天中的短语。
相关概念: Grain_粒度
合约漂移
SDD_Data_中的规范层
绿色_dbt_build_vs_可用数据
验收事实_acceptance_facts
名称: 案例 2. 通过「安全」操作数据集市泄露 PII
场景: 数据团队构建了 mart_customer_transactions——用于反欺诈分析师的客户操作数据集市。根据规范,数据集市应仅包含聚合(7 天内操作金额、操作数量、平均金额)并且不包含直接标识符。agent 生成了模型,「为了调试」添加了 phone 列和 card_pan 字段到前 100 行。not_null 的 dbt 测试通过,产品负责人粗略查看了 PR 并合并了。
挑战: 两周后发现,该数据集市定期用于仪表板,相关部门的分析师可以访问该仪表板,他们无权处理客户的个人数据。phone 和 card_pan 列反欺诈形式上不需要,但进入了导出并进入了 ad-hoc 报告。法律部门要求解释。
解决方案: 团队进行了紧急审计:从数据集市中删除了 PII 列,完全删除了它们(mart 层中不需要)。然后引入了四个障碍。(1) 在 ODCS 中明确列出了禁止的列:pii_email、phone、passport_number、card_pan、ИНН。(2) 创建了 pre-commit 脚本 list_pii_columns.py,该脚本按禁止列表对 models/marts/ 进行 grep 并在匹配时阻止提交。(3) 在 mart_customer_transactions 规范中添加了条目「PII:数据集中没有直接标识符;发布前审查员的检查是强制性的」。(4) 引入了审查员报告中的强制步骤:「已核对禁止 PII 列表,无匹配项,日期,签名」,该报告放入 PR。
结果: 数据泄露已关闭,流程已加强。在接下来的一个月中,pre-commit 脚本在其他模型上触发了两次,并防止了「为了调试」添加 phone 和 email 的尝试。反欺诈分析师指出工作质量没有受到影响——他们确实只需要聚合。法律部门在没有索赔的情况下结束了事件,因为团队能够表明 PII 策略现在以可验证规则而非口号的形式表达。
经验教训: 「添加用于调试」是 PII 进入生产数据集市的典型路径;调试应存在于 staging/intermediate 层,而不是 marts。
「不披露个人数据」的口号如果未转化为具体的禁止列列表和自动检查,则无用。
PII 策略需要在规范中记录,而不是在 SQL 注释或聊天中——否则它没有负责人,也没有自动执行的可能性。
多层防御(ODCS + pre-commit + 审查员的手动步骤 + 报告)捕获单个障碍遗漏的内容。
相关概念: PII_和直接标识符
可验证规则_vs_口号
ODCS_和_ODPS
SDD_Data_中的规范层
名称: 案例 3. 教学数据集市中 risk_flag 的有争议解释
场景: 在 mart_customer_360 数据集市中出现了 risk_flag 列,其值为 {low, medium, high}。在源中,它形成为教学信号「客户参加了培训反欺诈活动」。数据集市的规范将 risk_flag 描述为「聚合风险指标」,没有澄清这是教学信号而非实战评分。{low, medium, high} 上的 accepted_values 的 dbt 测试是绿色的。
挑战: 一个季度后,信贷风险部门开始使用 mart_customer_360 作为预评分来源。他们将 risk_flag 作为现成的特征并将其纳入限额计算模型。在季度会议上发现,数据集中的「high」实际上并不意味着「高风险」,而是「客户参与了训练样本」——即信号在意义上是相反的。错误没有导致实际损失(这是教学银行),但过程是危险的。
解决方案: 团队进行了分析。(1) 将列重命名为 training_risk_flag,以便名称本身说明数据的性质。(2) 更新了 ODCS 和规范:添加了「语义和适用性限制」部分,明确指出 training_risk_flag 是属于训练样本的标签,而不是评分输出,禁止在实战模型中使用它。(3) 在 schema.yml 中的列描述中添加了警告,该警告出现在 dbt 生成的文档中。(4) 引入了强制步骤:如果有人在 mart 中添加带有 risk 或 score 一词的列,审查员必须在 PR 中附上产品负责人对字段性质的确认。
结果: 信贷风险部门停止使用 mart_customer_360 作为评分特征来源,并切换到单独的战时风险评分数据集市。语义混乱已消除。团队引入了「任何具有敏感名称(risk、score、limit、status)的字段需要在规范中有明确的语义描述」的实践,从而降低了未来类似错误的可能性。
经验教训: 列名和 accepted_values 测试不能防止错误语义——在一个上下文中「high」意味着一件事,在另一个上下文中意味着另一件事。
dbt 测试捕获形式但不捕获解释:语义是规范的责任。
在 SDD Data 中,重要的不仅是「有哪些字段」,还有「不能如何使用字段」——这也是合约的一部分。
危险的列名(risk、score、limit)需要单独的审查过程——否则很容易将它们当作现成的产品接受。
相关概念: 合约漂移
验收事实_acceptance_facts
可验证规则_vs_口号
ODCS_和_ODPS
学习建议: 在手边保留一个真实的(或虚拟的)mart 来自您的 dbt 项目:当您同时重写自己的数据集市规范而不是抽象的 customer_360 时,本章更容易理解。
养成规则:在任何规范段落之后问自己「我将如何验证?」。如果没有答案——将该段落重写为命令、SQL、审查员的手动步骤或人工确认。
不要在长度层面上比较「糟糕」和「良好」规范,而是在可验证性层面上:良好规范看起来更窄,因为其中几乎没有猜测的余地。
练习将口号(「确保质量」、「考虑风险」)转化为可验证规则——这是本章的主要技能,也是生产中错误的主要来源。
记住「绿色 dbt build」是起点而不是终点。在此之后问:「粒度正确吗?没有多余的列吗?我正确理解了 risk_flag 的语义吗?」。
使用「五类风险」清单作为习惯:对于任何新的数据集市,问 PII、支付、同意、信贷组合和报告可重现性是否已被考虑。
不要在 AGENTS.md 中写「遵守银行要求」:对 agent 来说这是一个空短语。最好列出具体的禁止、允许的输入模型、合约字段和检查。
创建一个单独的文件「数据风险笔记」,分为 PII / Grain / Freshness / 合约漂移 / 人工确认部分——本章直接要求这样的工件,它确实在日常工作中有所帮助。
学习「逆向」编写规范:首先想象审查员或 agent 如何破坏数据集市(添加多余的列、更改 grain、将教学标志用作实战),然后在规范中封闭这些路径。
区分「表已构建」和「可以使用数据」:前者是技术事实,后者是产品义务。在银行领域中,它们比看起来更频繁地分离。
附加资源: Open data contract standard (odcs): AIDA Data Council 的数据技术合约标准。描述字段、类型、负责人、约束。在 SDD Data 中用作技术合约层。
Open data product standard (odps): AIDA Data Council 的数据产品描述标准。描述消费者、任务、SLA、验收标准。在 SDD Data 中用作产品合约层。
Dbt documentation: tests: dbt 官方文档中关于测试的部分(通用测试 not_null、unique、accepted_values、relationships;通过 singular SQL 的自定义测试)。关于 dbt 测试具体捕获内容的信息来源。
Dbt documentation: sources 和 freshness: dbt 文档中关于源和 source freshness 配置的部分。有助于在规范中封闭数据新鲜度层。
Dbt labs 博客: 《why data tests are not enough》: dbt Labs 关于自动数据测试边界的系列材料。支持本章关于多层防御的思想。
GDPR 和 152-ФЗ(俄语来源): 在欧盟和俄罗斯处理个人数据的监管框架。作为理解为什么银行中的 PII 策略应以具体规则而非口号形式表达的背景非常有用。
《fundamentals of data engineering》一书(joe reis, matt housley): 关于数据工程的现代教科书,详细介绍了数据合约、lineage、质量和可重现性——本章思想的概念基础。
Datafold 博客 和 data contracts 资源: 关于 data contracts 和 data diffing 的材料。对于理解如何自动捕获数据集市版本之间的合约漂移非常有用。
摘要: 银行数据的主要挑战不是 SQL 的复杂性,而是错误经常看起来正常:表已构建,测试是绿色的,但意义已经破坏。因此,在银行领域,规范纪律尤其重要:任何关于数据的声明都必须转化为命令、SQL、审查员的手动步骤或人工确认。本章介绍了五类风险(PII、支付、同意、信贷组合、报告)、五层防御(dbt 测试、ODCS、ODPS、模型规范、审查员报告)以及关键技能——区分「表已构建」和「可以使用数据」。本章的实践成果——一个填充的「数据风险笔记」文件,包含针对 PII、grain、freshness、合约漂移和人工确认的具体可验证规则。狭窄的、可验证的规范始终优于宽泛的口号:正是狭窄性使工作可被拒绝,因此可管理。