财务报表提取
将报告PDF转化为经过校验的事实:financial_fact(文档, 期间, 标签, 范围, 值, 单位, 审计状态)。
在科伦坡证券交易所(CSE)两年多的文件档案中构建并验证(2707份报告,72万个数据点,99.4%完全加载)。
其方法使其高效:自动解析器处理大部分报告,算术校验决定哪些数据可信,
分诊步骤明确指出每个剩余报告失败的原因,然后由子代理批量从页面图像中读取这些报告。
每次更改仅通过分阶段加载和审计才能到达已发布的数据库。
一切依赖的唯一规则
只有当数据列的算术校验通过时(资产 = 权益 + 负债;税前利润 - 税额 = 利润;归属母公司股东 + 少数股东权益 = 利润;现金流各分节之和等于净变动;期初 + 变动 = 期末),该数据才会被加载。 不会进行任何调整以使校验通过。校验仅允许印刷舍入误差(最后一位印刷数字的单位数:两项时为2,三项时为3,超过三项时为5),因此总数与其组成部分相差1-2个单位可视为通过,更大差异则为读取或印刷错误。 这就是解析器、OCR引擎、视觉转录和人工操作都能向同一数据库输入数据的原因:由校验(而非来源)决定。当校验失败时,要么是读取错误(重新读取),要么是印刷错误(记录原因,忽略该数据)。
实践中重要的推论:
- 标签保持原样印刷,仅通过确切别名(
label_alias)映射到标准行项目,从不使用模糊匹配。印刷错误的标签会使语句无法加载,而不是静默地产生错误数据。 - 没有标签的合计行可能获得带方括号的标签(如
[Total assets]),因其位置定义了它。 - "UNCHECKED"(无法校验)不等于"FAIL"。通常表明检查器尚不认识的某种布局 - 一次性修复检查器,之后所有具有该布局的报告都能加载(参见
references/checks.md)。
文件位置
scripts/pipeline/- 流水线:parse_report.py(PDF -> JSON表格),segment.py,columns.py,tokens.py,checks.py,ocr_words.py(OCR作为伪文本层),load_facts.py(JSON -> DB,仅通过校验的列),vision_tables.py(图像转录 -> JSON表格),extract_all.py,normalize.py。 映射、数据形状以及如何适配其他市场:references/pipeline.md。scripts/triage.py,scripts/make_batches.py,scripts/render.py,scripts/stage_publish.py- 以下步骤的辅助工具。assets/schema.sql(数据库),assets/dictionary.sql(252个标准行项目 + 471个精确标签别名),assets/worker_brief.md(转录工作者简报),assets/examples/(转录文件示例)。- 项目目录:数据库、PDF和输出文件存放的位置。用户在
CLAUDE.md/AGENTS.md中指定(例如 "Financials project: ~/stocks/cse_financials");如果未指定则询问。布局:db/<name>.db,reports/<SYMBOL>/<doc>.pdf,解析器输出extracted_final/,已发布源extracted_final_v/, 转录reviewed/vision/,笔记docs/。如果项目有自己的pipeline/,则将其视为当前代码(此技能的副本可能更旧)。
始终使用 -I(隔离模式)运行Python,除非需要OCR则设置 CSE_OCR=0(CSE_ 前缀是历史遗留)。
报告的PDF:select local_path from report_document where document_id=?(相对于项目目录),然后运行
python -I scripts/render.py <pdf> <pages> <out_dir> --text 转储文本层并渲染页面。
工作流
1. 确定范围并下载
选择期间范围;从报告标题中获取期间结束日期(交易所备案日期不可靠)。填充
report_document 行(每行一份报告:公司,类别 ANNUAL/INTERIM,标题,期间结束,来源URL),然后
并行且支持断点续传地下载。交易所特定部分(API,URL,特殊规则)属于配套技能 -
cse-financials 用于科伦坡。将每个年份块作为这些步骤的独立运行。在新市场或新年份之前,请阅读
references/traps.md:它列出了最耗时的数据陷阱。
2. 使用同一解析器版本解析所有内容
python -I scripts/pipeline/extract_all.py <db> <out_dir> --workers N 并使用 CSE_OCR=0(约2700份报告
需9分钟),然后对扫描件进行单独的OCR处理(如可用则使用GPU;参见陷阱)。解析器更改后,重新解析所有
报告并逐报告进行比较 - 部分重新解析会使数据库不可重现。
3. 加载并检查状态
load_facts.py(通过 stage_publish.py,步骤6)为每份报告指定状态:EXTRACTED(利润表和资产负债表已加载)、PARTIAL 或 FAILED。预计仅解析器就能处理大约80%的报告为 EXTRACTED。
4. 分诊剩余报告 - 先于查看任何页面
python -I scripts/triage.py <db> <extracted_dir> triage.json --pdf-hints
python -I scripts/make_batches.py triage.json batches/ --target=12
分诊给出每个缺失语句的原因(not_found,UNCHECKED,FAIL,no_columns,no_months,
unit_unproven,pass_refused...)以及查看位置。阅读统计:具有相同原因的大簇(例如100个UNCHECKED)
通常意味着检查器缺口或加载器规则 - 修复代码比转录数百页更有效。同时检查 FAILED 的报告是否实际上是报表:
通知、股东名单和勘误表很常见,只需记录原因。
5. 从图像转录,分批进行
每个工作者获取一个批次文件和 assets/worker_brief.md(填写路径)。它找到报表页面,
读取(先文本层,然后通过图像确定布局和任何可疑内容),写入 reviewed/vision/<doc>.txt,在其文档上运行
vision_tables.py,直到每个数据列都通过PASS,然后写入 bNN.result.json。工作者需要能够读取图像的模型。
格式和规则:references/vision-format.md。并行运行工作者、使用限制、恢复和审查结果:references/orchestration.md。
没有子代理时,按照相同简报一个接一个地处理批次。
批处理运行时,随时阅读到达的每个结果摘要:
- 重复出现的 "UNCHECKED because the checker does not recognise X" -> 扩展
checks.py(references/checks.md), 用vision_tables.py重新测试受影响的文件,将规则追加到简报中以便后续批次知晓。已写入的表格随后在不重新转录的情况下通过。 - 判断性决定(审计状态、重述列、异常期间长度、提供的标签)-> 检查是否符合简报;集中纠正(例如对COLS行使用sed) 而不是重新运行工作者。
6. 暂存、审计、发布
python -I scripts/stage_publish.py --project <dir> # 仅暂存和审计
python -I scripts/stage_publish.py --project <dir> --publish # 读取审计后
如果审计显示报告离开EXTRACTED状态、报告变为空、完整性/外键错误、出现新的单位异常值,或者在没有转录文件的报告中 丢失/更改了数据(意味着代码修改接触了无人重新读取的数据),则拒绝发布。非视觉报告的增加是正常的(已解决的冲突 释放了另一报告的列),但需要逐一检查。分阶段发布(先发布完成的批次)以便工作能承受中断;然后运行项目的测试(如果有)。
7. 关闭所有报表
目标是:每个缺失的报表要么已加载,要么有明确说明的原因。构建每个报表的结果表
(已加载 / 未加载 + 来自 MISSING 行和结果文件的原因)。对于每个未加载的报表,检查
相同数据是否已从另一报告加载(下一年的比较数据、匹配的中期报告)- 通常是的,
这将“缺口”转化为“已覆盖”。在项目文档中记录结果。
代币和时间经济性
- 先分诊;切勿在没有原因和页面提示的情况下派工作者去“找出问题”。
- 先文本层后图像:干净的文本层免费确认数字;图像确定布局。渲染1500像素;裁剪(
render.py --clip) 而不是渲染整页的大图像。带有垃圾文本层("9,O4349L")的扫描件需要使用图像。 - 按公司分批:一种布局,多个季度。每批次约10份报告或约12个成本单位,同时约6个工作者。
- 工作者即使在校验仍为UNCHECKED时也写入完整表格,外加一行
MISSING行:如果之后扩展了检查器, 表格无需重新读取即可加载。 - 在代码(检查器、加载器)中一次性集中修复系统性问题;重新运行
vision_tables.py(只需几秒) 而不是重新读取页面。 - 协调器不查看页面图像,除非解决争议;它读取摘要、运行校验和审计。
- 工作者摘要保持在3-6行;详细信息放在文件注释中。
完成定义
每份报告:EXTRACTED,或者PARTIAL/FAILED,每个缺失报表附有命名印刷证据的原因
(例如 "printed total 3,115,995,977 vs equity + liabilities 3,115,995,968"),或者 "covered by report N"。
仅通过 stage_publish.py 发布,附带备份,审计干净(或每个标志都已解释),测试通过,并且在项目文档中
包含结果表及带日期注释。