| name | data-sensibility |
| tier | meta |
| description | Build intuition about document data before writing extraction logic. Use before designing any extraction schema or regex pattern, when onboarding a new document type, or when extraction accuracy is unexpectedly low and you suspect a data assumption is wrong. Covers systematic observation of raw documents, spot-checking extracted results, distribution analysis, and recognizing suspicious patterns. If you are about to write code that touches document data and you have not read at least five documents end-to-end, stop and use this skill first. |
数据体感培养
数据体感不是一个精确的技术术语,而是一种能力:你对数据质量的直觉判断。看数据和读数据是两件不同的事。看数据是扫一眼字段名、确认有值、继续写代码。读数据是理解每个值是怎么来的、为什么是这个格式、什么时候会出问题。
文档核查中最昂贵的错误不是逻辑写错了——那种错误测试能抓住。最昂贵的错误是对数据的假设不成立。你以为日期格式是 YYYY-MM-DD,因为前三份文档都是这样。第四份文档用了 DD/MM/YYYY,所有提取结果都悄无声息地错了。你以为贷款金额在"贷款要素"表格的第三行,直到遇到一家银行把金额写在合同首段的叙述文字中。
数据体感是"先看后写"的纪律。在写第一行提取代码之前,先真正理解你的数据。
前置观察:先读文档再写代码
拿到一类新文档时,打开 3-5 份完整文档从头读到尾。不是浏览——是阅读。打开原始解析文本,从第一页开始,逐页往下。
读什么类型的文档
以金融文档核查为例,常见的文档类型包括:
- 贷款合同:关注金额表述方式(大写/小写)、利率表述(年化/月利率/基点)、签署日期位置、担保条款结构。
- 增值税发票:关注发票代码和号码格式、金额和税额的位置、购买方/销售方信息的布局、备注栏内容。
- 银行流水:关注日期格式、摘要字段的长度和内容、余额计算方式(实时余额 vs 日终余额)、分页和续页的衔接。
- 征信报告:关注查询记录格式、贷款记录的展示方式、逾期信息的编码、特殊交易类型。
- 房产评估报告:关注估价结论的位置和表述、面积单位、评估方法描述、有效期表述。
读的时候注意什么
一边读一边记录让你意外的地方:
- 你以为金额在表格里,结果它在一段叙述文字中间。
- 同一份合同里大写金额写的"伍仟万元整",小写写的"50,000,000.00元"——它们一致吗?
- 文档有两个签字页,不是一个。
- 章节标题的编号方式前后不一致(第一章用"一",第二章用"2")。
- 某些字段有时候有,有时候没有——不是固定出现的。
对每种新文档类型都做这件事。当文档来源发生变化时,再做一次。30 分钟的阅读能节省 3 小时的调试时间——那些调试时间会花在修复基于错误假设构建的提取逻辑上。
系统观察清单
读完文档后,显式回答以下问题。写下来,不要只在脑子里过一遍:
什么是一致的?
所有文档中稳定不变的元素:页头结构、字段位置、术语用法、日期格式。这些是你的锚点,提取逻辑应该围绕它们来设计。
例如:所有贷款合同都在第一页左上角有合同编号,格式为"XX银贷字〔YYYY〕第NNNN号"。这个就可以写正则。
什么在变化?
不同文档之间有差异的元素:表格布局、章节顺序、字段是否存在、格式约定。这些是你的风险点,每种变体都需要一个测试用例。
例如:贷款金额有时候在"贷款要素"表格中,有时候在合同正文第一段。两种位置都需要覆盖。
什么是令人意外的?
你没有预料到的任何情况:
- 一个应该总是存在的字段偶尔缺失。
- 同一个值在不同文档中用不同单位表示(万元 vs 元)。
- 某些模板中存在一个章节,其他模板中没有。
- 增值税发票的备注栏包含了关键的合同信息。
文档子类型?
同一类文档中是否存在不同的模板、不同的出具机构、不同的时间段版本?
- 工商银行的贷款合同和招商银行的贷款合同可能完全不同。
- 2020 年之前的征信报告和之后的格式不一样。
- 同一家银行的个人贷款合同和企业贷款合同是两套模板。
尽早识别子类型——它们通常需要独立的提取路径。
章节长度?
实际测量。不要猜。
for section_name, section_text in sections.items():
token_count = len(section_text) // 2
print(f"{section_name}: ~{token_count} tokens")
一个平均 200 tokens 的章节对任何模型都没问题。一个偶尔膨胀到 8000 tokens 的章节会打爆你的上下文窗口预算。提前知道,提前规划。
编码问题?
- 全角 vs 半角:
12.5% vs 12.5%。人眼看一样,正则匹配不一样。
- Unicode 标准化:
〔 vs ( vs (。同一个括号可能有三种写法。
- OCR 伪影:扫描件转文本后,
0(零)和 O(字母),1(一)和 l(L),壹 和 ㈠。
- 空格和换行:PDF 解析后的文本可能在奇怪的位置插入换行符,把"资本充足率"拆成"资本充足\n率"。
这些问题导致静默的提取失败——文本看起来对了,但正则匹配不到。
抽检协议
每次提取运行后,随机挑选 10 个字段手动核验。不挑简单的——要覆盖置信度频谱:3 个高置信度、4 个中置信度、3 个低置信度。
核验字段示例
以贷款合同为例,典型的抽检字段:借款人姓名(与合同首页一致吗?)、贷款金额(大写和小写一致吗?单位对吗?)、合同编号(完整提取了吗?)、签署日期(取的是签署日期还是打印日期?)、利率(年化还是月利率?LPR加点还是固定?)、担保方式(抵押/质押/保证/信用——提取对了吗?)。
对每个字段检查四个维度
- 值正确? 提取的值与文档原文一致吗?
- 来源位置正确? 提取逻辑从正确的位置找到了这个值,还是从文档其他地方抓了一个相似的值?
- 标准化正确? 原文写的"人民币伍仟万元整",输出的 50000000 对不对?
- 类型正确? 百分比有没有变成小数?日期有没有解析成错误的世纪?
判定规则
如果 10 个字段中有超过 1 个错误,停下来。不要继续处理这批数据。调查失败原因,识别模式,修复提取逻辑,然后重新运行。
10% 的抽检错误率意味着真实错误率远高于 10%——因为你还没有发现那些看起来正确但实际不对的错误。
分布可视化
提取完成后,对关键字段做分布分析。不需要复杂的可视化工具,基本的 Python 就够了。
一段代码覆盖三个维度
from collections import Counter
for field in ["contract_id", "borrower_name"]:
lengths = [len(str(r[field])) for r in results if r.get(field)]
print(f"{field} 长度分布: {Counter(lengths).most_common(5)}")
guarantee_types = [r["guarantee_type"] for r in results if r.get("guarantee_type")]
for value, count in Counter(guarantee_types).most_common(10):
print(f" {value}: {count} 次")
total = len(results)
for field in ["borrower_name", "loan_amount", "contract_id", "sign_date"]:
missing = sum(1 for r in results if not r.get(field))
rate = missing / total * 100
print(f" {field}: 缺失 {rate:.1f}%{' ⚠' if rate > 10 else ''}")
即使是最简单的 Counter(values).most_common(20) 也能告诉你很多。不要跳过这一步。
异味模式
有些提取值单独看是合理的,但放在一起看就有问题:
大写金额与小写金额不一致
文档 A: 大写"伍仟万元整" → 50,000,000 小写"50,000,000.00" → 50,000,000 ✓ 一致
文档 B: 大写"壹仟万元整" → 10,000,000 小写"1,000,000.00" → 1,000,000 ✗ 差10倍
大小写金额不一致在真实金融文档中是严重问题。如果你的提取结果中频繁出现不一致,可能是大写金额转换函数有 bug,也可能文档本身有错——但无论如何都需要调查。
签章日期集中在月末
所有合同的签署日期都是每月 28、29、30、31 号。可能是真实的业务模式(月末集中签约),但也可能是你提取的不是签署日期,而是合同生效日期或月末报送日期。核验几份原文确认。
统一社会信用代码格式异常
标准的统一社会信用代码是 18 位,格式为:登记管理部门代码(1位) + 机构类别代码(1位) + 登记管理机关行政区划码(6位) + 主体标识码(9位) + 校验码(1位)。如果提取结果中出现 15 位(旧版组织机构代码)或长度不等的代码,提取逻辑可能匹配了错误的字段。
所有合同金额都是整万
5,000,000.00
10,000,000.00
3,000,000.00
20,000,000.00
银行贷款确实倾向于整万或整百万,但如果每一份都是精确的整万,没有任何零头,值得抽查几份——你可能在提取授信额度而不是实际放款金额。
恰好处于监管阈值的值
资本充足率恰好 8.00%。贷款集中度恰好 10.00%。拨备覆盖率恰好 150.00%。金融机构确实会管理到阈值附近,但如果多份文档的同一指标恰好等于监管阈值,你可能提取的是阈值定义本身("根据规定,资本充足率不得低于8%"),而不是机构的实际指标值。
完美一致
每一份文档都通过了每一条规则。100% 合规率。真实数据一定有例外。如果你的系统报告全部合规,你的系统大概率是错的。
案例:通过分布分析发现系统性错误
某次银行流水核查,提取"交易摘要"字段。初始结果看起来正常——每条流水都有摘要,字段不为空。
做分布分析时发现问题:
Counter(len(s) for s in summaries).most_common(5)
"转账"、"消费"、"工资"——每个值单独看都是合理的中文词汇。但真实银行流水的摘要通常更丰富:"支付宝消费-星巴克"、"跨行转账-张三"、"工资-XX公司"。
原因:PDF 表格解析时摘要列宽不足,长文本被截断。只有前两个中文字符被归入"摘要"列,后续内容被分配到了下一列。不做分布分析,这个错误不会被发现。修复:调整列宽参数。
中间输出物化
把每个处理阶段的结果保存到磁盘:
- 原始解析文本(来自文档解析阶段)。
- 树切分章节(来自文档树处理阶段)。
- 提取的实体(来自实体提取阶段)。
- 判定结果(来自合规判定阶段)。
将中间结果写为 JSON 文件,放在 debug/ 目录中,用文档 ID 和阶段命名:
debug/
DOC001_01_raw_text.json
DOC001_02_tree_sections.json
DOC001_03_extracted_entities.json
DOC001_04_judgments.json
磁盘很便宜。没有中间结果的调试是猜谜游戏。
当出问题时,你可以独立检查每个阶段:章节切分错了?看 _02。提取对了但判定错了?对比 _03 和 _04。原始文本就是乱码?看 _01。没有中间结果,你只能盯着最终输出猜测。保留至少当前迭代的中间结果。
当语料装不进你的脑子时
要预先规划的一个根本约束:你有有限的上下文窗口。连着读几十份样本文档,会在你读完之前就把早期的观察挤出工作记忆,留给你"我看过整套语料"的印象但其实没有归纳能力。
把语料当成统计学家眼中的总体来对待:抽样、汇总、不要试图把整个总体塞进脑子里。几种在实践中有效的做法:
- 把文件系统当记忆用。一边扫一边写
notes/data_observations.md (或者按规则维度写 notes/<rule_id>_observations.md)。记录字段命名变体、格式怪癖、缺失章节的模式、出人意料的取值。下一次会话打开笔记重读,而不是重新扫一遍文档。
- 每条规则一个 notepad / memory.md。为每条规则维护一份简短的
memory.md,记录"我在样本集里就这条规则看到了什么" —— 哪些文档会触发它、出现了哪些取值、有哪些边缘情形。增量更新,而不是每次看这条规则都从头推导一遍。
- 派 subagent 去扫样本。语料规模较大时,让一个 subagent(通过
agent_tool)去扫某个目录,回来给你一份汇总统计或一份简短的 markdown 报告。subagent 把完整的文件读取留在自己的上下文里,你只收到摘要。这个工具用得正是时候 —— 否则你会为了一个观察消耗大量上下文去读几十份文件。
- 统计 / 元视图优先于逐份阅读。与其去读 20 份收入证明,不如对它们跑一遍正则统计格式变体。与其打开每份年报,不如先列文件名按签发方 / 年份分组。先建好元视图,再深入有代表性的几份。
原则:目标是足够多的样本来刻画分布,而不是足够多的样本来背诵语料。前者装得进脑子也装得进笔记。后者装不下。
集成
数据体感的观察结果应直接输入下游技能:
entity-extraction:数据格式、字段变体、编码问题直接决定数据模式设计和正则模式。大写金额的变体告诉你转换函数需要处理哪些写法。
skill-authoring:边缘案例和已知变体成为规则技能中的测试用例。某家银行的合同模板与众不同?这应该成为技能中的一个明确分支。
confidence-system:抽检结果和分布分析校准初始置信度预期。某类字段抽检准确率只有 70%,先验值就不应该设到 0.90。
evolution-loop:演进循环揭示预期之外的失败时,回来重新做数据体感观察。数据可能已经变化。
数据体感不是一次性活动。每一批新数据、每一个新的文档来源、每一次格式变更,都是重新观察的契机。不要因为"上次看过了"就跳过——上次看的和这次可能不一样。