RAGSpine
指南

抽取

文档 → 冻结的 StyledGrid IR。感知样式与颜色的 xlsx/pptx/pdf 抽取器、逐页 PDF 路由、版本化的颜色语义注册表,以及交叉核对进评审队列的双通道校验。

extraction 领域把办公文档转换为冻结的、感知样式的中间 表示(StyledGrid IR)——不是"简单的文本切分"。每个抽取器都是 确定性优先、感知颜色与样式的,要么直接产出事实,要么发出 StyledGrid 供下游摄取。重量级解析器(Docling、PaddleOCR)位于 Protocol 缝之后并延迟 import,因此核心可离线运行。

包位于 src/ragspine/extraction/。其领域契约是 src/ragspine/extraction/CLAUDE.md。IR(ir.py)在代码中被描述为整个项目 最稳定的接口——抽取器向它收敛,而下游一切(颜色 语义、摄取、评审、评估)都消费它。

目录结构

ir.py — StyledGrid IR(StyledCell / StyledGrid)

StyledGrid IR

ir.py 定义两个 dataclass。一个 StyledGrid 对应一个工作表或一个表格页面;其 cells 是从 cell_refStyledCell 的稀疏映射。

StyledCell

单个感知样式的单元格。

属性

类型

StyledCell.rgb_tag_key() 返回颜色聚类键:cf_affected 时为 None, 否则为 resolved_rgb。条件格式区域内的单元格被刻意 排除在颜色语义之外,因为其填充颜色不可信。

StyledGrid

属性

类型

关键方法:get(cell_ref)iter_cells()add_warning(message),以及 cells_by_rgb()——按 resolved_rgb 对颜色可靠的单元格分组,跳过 Nonecf_affected 的单元格。参见 StyledGrid IR

抽取器

按设计存在两类抽取目标:

  • 事实抽取器xlsx_extractorpptx_extractor)针对已知 schema (如五年汇总表)直接产出 Fact 对象——零幻觉、零 LLM。
  • 样式抽取器*_styled_extractorpdf_*)为通用摄取路径产出 StyledGrid IR,保留颜色与样式。

xlsx_styled_extractor.extract_grids(path) 为每个工作表返回一个 StyledGrid。它 把 OOXML 的 theme + tint 解析为真实的 RRGGBB 值(resolve_theme_color),展开 合并单元格,保留数字格式,并检测条件格式区域—— 将这些单元格标记为 cf_affected=True 并添加网格警告,以便颜色层跳过 它们。compute_file_hash(path) 返回用于版本链路的 sha256(并被 PDF 路由器复用)。

更简单的 xlsx_extractor.extract_facts(path) -> tuple[list[Fact], list[str]] 把 已知的汇总表 schema 直接映射为 Fact 对象(指标名沿 A 列排布、期间表头 沿第 1 行排布),默认 channel="TOTAL"unit="USD_M"

from ragspine.extraction.extractors.xlsx_styled_extractor import extract_grids

grids = extract_grids("report.xlsx")        # list[StyledGrid], one per sheet
for cell in grids[0].iter_cells():
    print(cell.cell_ref, cell.value, cell.resolved_rgb)

两个并存的模块。pptx_extractor.extract_facts(path) 读取原生表格和 原生图表数据(来自图表 XML,绝不来自图片),产出 Fact 对象——零 OCR、零 LLM。较新的 pptx_styled_extractor 增加两条路径:

  • extract_grids(path) — 原生表格 → StyledGrid(sheet 为 'slide{N}_table{M}', cell_ref 为 'R{row}C{col}'),通过幻灯片主题配色方案解析填充颜色。
  • extract_note_fragments(path) -> list[NoteFragment]含数字的文本框 + 演讲者备注片段,按幻灯片排序,供叙事层使用。

NoteFragment 携带 slide_nosource_kind"textbox" / "notes")、locator (如 'slide2/notes')、textglossary_hits。其版本戳常量是 EXTRACTOR_VERSION = "pptx_styled_v0"

pdf_digital_extractor.extract_grids(path) 通过包装 Docling 抽取_数字版_ PDF 的 每个表格(每个表格一个 StyledGrid)——Docling 在函数体内延迟 import,绝不在模块顶层。此通道的 resolved_rgb 始终为 None。扫描版、 不可读或无表格的 PDF 返回 [],不抛异常、不做 OCR。Docling 配置为 do_ocr=Falsedo_table_structure=True

该模块还定义了 GridExtractor 缝——参见下文

pdf_scanned_extractor.extract_grids(path, backend, *, min_confidence=0.85, queue=None) 把页面渲染为 PNG(pypdfium2RENDER_DPI = 200),调用注入的 OcrBackend.recognize,并为每个识别出的表格构建一个 StyledGrid。低置信度 单元格(confidence < min_confidence)仍会进入网格,但会添加网格警告,并且—— 如果提供了 queue——以原因 "low_confidence_ocr"priority=30 入队评审。

中立的结果类型是 OcrCellrowcoltextconfidence)、OcrTableOcrPageResult。真实后端 PaddleOcrVlBackend(PaddleOCR PPStructureV3, GPU)位于 gpu pytest marker 之后;与模块无关的逻辑用 假后端离线测试。版本戳:EXTRACTOR_VERSION = "pdf_scanned_paddleocrvl_v0"

PDF 路由 — 逐页分诊

抽取之前,PDF 会被逐页分诊。routing/pdf_router.route(path) 返回 一个 RoutingDecision,携带文件级 verdict、每页一个 PageInfo,以及——对_混合_ 文件——一个把每个页码映射到管线名称的 channel_plan

classify_page(page, page_no) 根据两个信号——可抽取 文本字符数和图像覆盖率——对照 TEXT_MIN_CHARS = 50IMG_COVER_SCAN = 0.55 推导每页的 kind

字符数图像覆盖率kind
≥ 50< 0.55digital
≥ 50≥ 0.55ocr_scan
< 50≥ 0.55img_scan
< 50< 0.55low_text

route() 以 90% 阈值把页面聚合为文件级判定(digital / scanned / ocr_scan / mixed / unreadable),把 producer/creator 元数据读入 origin_meta,并在 producer 看起来像 PowerPoint / Keynote / Impress 导出时设置 ask_for_pptx=True(以便调用方转而索取原生源文件)。加密或 损坏的文件返回 verdict="unreadable" 并设置 error——绝不抛异常。

被路由为 digital 的页面进入 digital_extractor 管线;其他所有类型(扫描 / ocr / 低文本)进入 scanned_extractor。路由器只做决定——实际工作 仍由对应的抽取器完成。

颜色语义 — 聚类、图例、版本化注册表

color/color_semantics.pyL2 受控推断层:它把单元格填充 颜色映射为业务含义,但仅在人工确认映射之后。管线:

聚类cluster_colors(grid) -> list[ColorCluster] 按 RGB 对颜色可靠的单元格 分组,按 (-count, rgb) 排序。

检测图例detect_legend(grid) -> list[LegendEntry] 找到与文本标签相邻的 色块单元格,产出颜色→含义的草稿

确认 — 草稿进入 MappingRegistry 并保持 status="draft",直到 SME 确认。确认新版本会取代(绝不删除)之前的活跃版本。

应用apply_mapping(grid, mapping) -> dict[str, dict[str, str]] 返回 {cell_ref: {tag_key: tag_value}}。若映射不是 active,它返回 {} 并 添加网格警告——未确认的映射永远不能静默地给事实打标签。

MappingRegistry 是独立的 sqlite 存储(color_mapping 表,主键 (scope, version))。其 API:register_draft(mapping)(按 scope 自动递增版本)、 confirm(scope, version, actor, note=None)reject(...)get_active(scope)。 事实通过其 mapping_version 引用已确认的映射,因此链路能在 修订后存续。参见颜色语义

双通道校验

verification/dual_channel_verifier.verify(facts_a, facts_b, queue=None, tolerance=0.0) 交叉核对同一表格的两次独立抽取(docstring 中的示例:Docling 表格解析 vs. 文本层重建)。每一侧都是一个 ChannelFact 列表,按 dim_key = (metric_code, entity, period_type, period, channel) 对齐:

  • 一致(键相同、值在 tolerance 之内)→ 自动通过。
  • 冲突(键相同、值不同)→ 以原因 "dual_channel_conflict"priority=10 入队。
  • 仅单通道(键只在一侧存在)→ 以原因 "single_channel_only"priority=50 入队。

它返回一个 VerificationResultagreedconflictsonly_in_aonly_in_bn_auto_passedn_enqueued)。当 queue=None 时只做分类、不入队。 冲突比仅单通道更早进入评审,因为其优先级数字更小。这段纯 逻辑不依赖 Docling。

Protocol 缝

重量级依赖通过 @runtime_checkableProtocol 注入,因此可以在 不触碰摄取调用点的情况下换掉解析器,且该路径可用假实现离线测试。

GridExtractor

pdf_digital_extractor。具有 version: str + extract_grids(path)。默认实现 DoclingGridExtractorversion = "pdf_digital@1" ——即写入每个 事实 extractor_version 的值。数字版解析器的输出变化时请递增它。

OcrBackend

pdf_scanned_extractorrecognize(image_bytes, page_no) -> OcrPageResult。默认 真实实现为 PaddleOcrVlBackend;测试注入假实现——离线无需 PaddleOCR。

GridExtractor.version 是契约的一部分。 它会成为写入 事实链路的 extractor_version,使换掉的解析器(Docling → pdfplumber / camelot / …)在 溯源中仍可区分。

本领域坚守的不变量

  • 确定性优先、零幻觉——原生表格与图表数据按结构 读取;OCR/LLM 是缝之后的回退手段,绝不是默认。
  • 颜色信任——cf_affected 单元格与未确认的映射永远不会产生静默标签。
  • 版本链路——source_file_hash + extractor_version(+ mapping_version) 随每个抽取值一同流转。
  • 可插拔——重量级解析器是延迟 import 的 Protocol 缝;核心可离线运行。

相关阅读

本页目录