Skip to content

移植计划

本页描述如何将 Cartex 富化管道集成至 Cato-v2 生产系统,涵盖数据模型映射、集成点、已知缺口及 Cato-v2 代码库审计识别的风险项。

Cato-v2 提取输出

Cato-v2 不产生单一结构化的 ExtractionResult。提取数据分散于数据库表和内存结构中。

当前数据模型

Cato-v2 概念 存储 关键字段
Evidence 每个检测区域一条数据库记录 idtypepolygon(边界框)、file_key(S3 裁剪图)、ocr_textsub_text
TakeOffResultItem 每条提取行一条数据库记录 result(JSON 数据块)、evidence_idevidence_idsproject_file_id
TakeOffResult 父级容器 original_result(完整 Gemini 响应)、template_idproject_file_ids

Evidence 类型映射

每个 Evidence.type 字符串对应一个 Cartex 概念。

Evidence.type Cartex 对应
"Window Door Unit" 角色为 MAINTableModel
"Table" TableModel——角色为 MAINAUXILIARY(当前无区分)
"Elevation" ImageContextModel
"Floor Plan" ImageContextModel
"Key Notes" TextContextModel(类别 general_note
"Drawing Index" 无 Cartex 对应
"Title Info" 无 Cartex 对应

Result item JSON 结构

每个 TakeOffResultItem.result 是一个扁平 JSON 字典,顶级键与当前 PromptTemplate 字段匹配。嵌套字段使用点分路径(如 Glass.TypeGlass.Arrangement.Configuration)。

{
  "Label": "W-1",
  "Product": "Window",
  "Product Type": "Direct Set / Picture / Fixed",
  "Operability": "Fixed",
  "Width": 36,
  "Height": 48,
  "Quantity": 1,
  "Frame": { "Profile": "", "Material": "" },
  "Special Notes": "",
  "Source Type": "Image"
}

映射至 ExtractionResult

Cartex 字段 Cato-v2 来源 缺口
TableModel.table_id Evidence.id
TableModel.role Evidence.type 无 MAIN vs AUXILIARY 区分
TableModel.headers PromptTemplateField 名称 间接——需从模板中提取
TableModel.rows TakeOffResultItem.result 条目 可用,需重塑格式
TableModel.bbox Evidence.polygon 可用
TextContextModel.content Evidence.ocr_text(type 为 Key Notes 可用
TextContextModel.category 硬编码为 notes 需要映射逻辑
ImageContextModel.interpretation 不可用 关键缺口——见下文

映射层设计

转换器模块将 Cato-v2 的提取后状态转换为 Cartex 的 ExtractionResult。需要两个函数:to_extraction_result() 用于提取数据,build_user_table_schema() 用于输出模式。

to_extraction_result()

该函数接收 TakeOffResult、关联的 evidence 列表及当前模板,迭代 evidence,按类型分类,并构建相应的 Cartex 模型。

def to_extraction_result(
    take_off_result: TakeOffResult,
    evidence_map: dict[int, Evidence],
    template: PromptTemplate,
) -> ExtractionResult:
    tables = []
    text_contexts = []
    image_contexts = []

    for evidence in evidence_map.values():
        if evidence.type in ("Window Door Unit", "Table"):
            role = classify_table_role(evidence, take_off_result)
            rows = collect_rows_for_evidence(evidence.id, take_off_result.items)
            headers = [f.name for f in template.fields]
            tables.append(TableModel(
                table_id=str(evidence.id),
                role=role,
                headers=headers,
                rows=rows,
                bbox=parse_polygon(evidence.polygon),
            ))
        elif evidence.type == "Key Notes":
            text_contexts.append(TextContextModel(
                category="general_notes",
                content=evidence.ocr_text or "",
            ))
        elif evidence.type in ("Elevation", "Floor Plan"):
            image_contexts.append(ImageContextModel(
                interpretation=evidence.interpretation,
            ))

    return ExtractionResult(
        tables=tables,
        context=text_contexts + image_contexts,
    )

classify_table_role() 是新逻辑——详见风险 R1 中的启发式方案。

build_user_table_schema()

该函数将 Cato-v2 的 PromptTemplate 转换为 Cartex 的 UserTableSchema,从模板字段提取列名并从 config_json 构建逐列指令字符串。

def build_user_table_schema(template: PromptTemplate) -> UserTableSchema:
    column_instructions = {}
    columns = []

    for field in template.fields:
        config = field.config_json
        parts = []
        if config.get("available_values"):
            parts.append(f"Valid values: {', '.join(config['available_values'])}")
        if config.get("extraction_rules"):
            parts.append("; ".join(config["extraction_rules"]))
        if config.get("unit"):
            parts.append(f"Unit: {config['unit']}")

        column_instructions[field.name] = " | ".join(parts) if parts else ""
        columns.append(field.name)

    return UserTableSchema(
        columns=columns,
        column_instructions=column_instructions,
    )

PromptTemplate 上的 analysis_ai_prompt 自由文本字段携带全局指令(如"重点关注铝包窗")。若 UserTableSchema 支持顶级指令字段,应将其映射至该字段;否则前置到每个列指令中。

当前直接附加到 Cato-v2 提取提示词中的 Key Notes OCR 文本应改为通过 TextContextModel 传递,而非重复写入列指令。

集成点

Cartex 富化器插入 app/services/drawing_ai.py 中现有的 DrawingAIService.analyze_item_by_source_type() 管道。

当前 Cato-v2 流程

提取管道分八步运行:

  1. 为所有项目文件加载 evidence
  2. 生成 S3 图像裁剪
  3. OCR Key Notes 文本
  4. PromptTemplate 生成提示词
  5. 过滤至清单类 evidence
  6. 通过 BatchingFiles.process_files() 调用 Gemini——原始提取
  7. 将结果解析为 TakeOffResultItem
  8. 保存至数据库,将 TakeOffResult 标记为状态 Analyzed

富化器插入点

富化器调用位于第 7 步(解析)和第 8 步(持久化)之间,作为新的步骤 7.5。

# 第 7 步之后,第 8 步之前:

# --- Cartex 富化 ---
extraction_result = to_extraction_result(
    take_off_result, evidence_map, template
)
user_schema = build_user_table_schema(template)
enriched_rows = await cartex_enricher.enrich(extraction_result, user_schema)
result_items = merge_enriched_rows(result_items, enriched_rows)
# --- 结束 Cartex 富化 ---

# 第 8 步:保存至数据库(现有代码)

下图展示富化在管道中的位置。

flowchart TB
    subgraph cato["Cato-v2 管道"]
        direction TB
        S1["1. 加载 evidence"]
        S2["2. 生成 S3 裁剪"]
        S3["3. OCR Key Notes"]
        S4["4. 生成提示词"]
        S5["5. 过滤清单 evidence"]
        S6["6. Gemini 批量提取"]
        S7["7. 解析为 TakeOffResultItems"]
        S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
    end

    subgraph cartex["Cartex 富化(新步骤 7.5)"]
        direction TB
        MAP["to_extraction_result()"]
        SCHEMA["build_user_table_schema()"]
        ENRICH["Enricher.enrich()"]
        MERGE["merge_enriched_rows()"]
        MAP --> ENRICH
        SCHEMA --> ENRICH
        ENRICH --> MERGE
    end

    subgraph save["Cato-v2 持久化"]
        S8["8. 保存至数据库"]
    end

    cato --> cartex --> save

备选方案:分析后端点

解耦方案添加一个按需富化的新路由:

POST /api/v1/take-off/{take_off_id}/enrich

该路由加载现有 TakeOffResultItem 行,从存储数据构建 ExtractionResult,运行富化,并更新条目。支持手动编辑后重新富化,但首次分析时不自动触发富化。

图像上下文缺口

Cato-v2 不生成图像的文字解读,这是 T3(图例富化)和 T4(尺寸富化)的主要阻断项。

P0 阻断项

缺少 ImageContextModel.interpretation,五种专业策略中的两种将无法运行。 这必须在 Cartex 富化上线前解决。

当前状态

Cato-v2 管道通过机器学习检测模型检测边界框,裁剪区域并存储为 S3 上的 PNG,仅对标签子区域运行 OCR。没有任何步骤生成描述图例图示、条目卡片或立面图内容的丰富文字解读。

方案选项

方案 工作量 保真度
A:移植 Cartex 的解读提示词 — 在 S3 图像裁剪生成后,为每个非清单类 evidence 添加 Gemini 视觉调用。将结果存储在 evidences 表的新 interpretation TEXT 列中。 中等
B:在映射层按需生成 — 转换器在转换时调用 Gemini,而非存储解读结果。 中等 中等
C:初期跳过 T3/T4 — 先不包含图例和尺寸策略上线。 覆盖率降低

建议

推荐方案 A。向 evidences 表添加 interpretation 列,并在 DrawingAIService.analyze_item_by_source_type() 的第 3 步(S3 图像裁剪生成后)填充该列。只需保留 Cartex 提取器中的解读提示词——CONTEXT_EXTRACTION 或等效提示词。不需要完整的 TABLE_EXTRACTION 提示词,因为 Cato-v2 自身的提取管道已处理表格检测。

逐字段提示词缺口

Cato-v2 将字段级指令存储在 prompt_template_field 行中。Cartex 需要将这些指令转换为 UserTableSchema 列指令。

词汇存储位置

来源 包含内容
prompt_template_field.config_json.available_values 有效值列表(如 ["Steel", "Brass", "Galvanized Steel"]
prompt_template_field.config_json.extraction_rules 指令字符串(如 ["Prioritize exact match"]
prompt_template_field.config_json.unit 数字字段的计量单位
prompt_template_field.config_json.default_value 回退值
prompt_template.analysis_ai_prompt 自由文本全局指令
参考库(all_product_typesall_operabilityproduct_attributes 公司专属分类体系

接线方式

build_user_table_schema() 函数(见映射层设计)将 available_valuesextraction_rulesunit 拼接为每列的单条指令字符串。analysis_ai_prompt 全局文本应前置到每条指令,或映射到顶级字段。

当前直接附加到 Cato-v2 提取提示词中的 Key Notes OCR 文本应改为通过 TextContextModel 传递,而非重复写入列指令。

风险项

Cato-v2 审计识别出七项风险。三项为 P0 阻断项,四项为应在生产上线前处理的 P1 事项。

R1:无 MAIN vs AUXILIARY 表格角色分类

Cato-v2 对所有 Window Door UnitTable evidence 一视同仁。Cartex 的 T1 策略依赖 TableModel.role 区分主清单和参考表。

影响。 T1 将不触发或表格角色识别错误,降低辅助表富化质量。

缓解措施。 短期:在 classify_table_role() 中实现启发式——行数较少或表头匹配已知辅助模式(CodeDescriptionAbbreviation)的表格获得角色 AUXILIARY;每页行数最多的表格获得角色 MAIN。长期:训练分类器或在 evidence 审核时添加用户开关。

R2:图像解读完全缺失

T3(图例)和 T4(尺寸)需要 ImageContextModel.interpretation。Cato-v2 存储裁剪图像但从不生成文字描述。

影响。 两种专业策略在缺少该字段时无法运行。

缓解措施。 必须在图像上下文缺口中实施方案 A,这是阻断性依赖项。

R3:结果格式不匹配

Cato-v2 的 TakeOffResultItem.result 是扁平 JSON 字典。Cartex 的 EnrichedRow 新增了 field_sourcesconfidencereasoning,当前无存储位置。

影响。 富化元数据(来源、置信度、推理)在保存时丢失。

缓解措施。take_off_result_item 添加 cartex_metadata JSON 列,或在关联表中存储富化审计数据。

R4:提示词模板作为列列表的唯一来源

UserTableSchema.columns 必须与 TableModel.rows 中的键完全匹配。提示词模板与实际提取输出之间的字段名不一致会导致静默数据丢失。

影响。 提取中存在但模式中缺失的列被忽略;模式中存在但提取中缺失的列无警告地保持为空。

缓解措施。 在映射层添加验证步骤,将模板字段名与 TakeOffResultItem.result 中的实际键对比,对不匹配字段记录警告。

R5:批量处理架构

Cato-v2 通过 BatchingFiles.process_files() 将所有清单 evidence 图像在单次批量调用中发送给 Gemini,结果是一个没有明确 evidence 归属的扁平行列表。Cartex 期望按表格分组的行数据。

影响。 映射层可能难以将提取行与来源 evidence 关联,而这对于正确构建 TableModel.rows 是必要的。

缓解措施。 使用 TakeOffResultItem.evidence_ids 将行归属至来源 evidence。考虑切换为逐 evidence 的 Gemini 调用以获得更清晰的表格分离,代价是更多 API 调用。

R6:Gemini 模型依赖

Cartex 专门针对 Gemini。Cato-v2 同时使用 OpenAI 和 Gemini,模型选择按部署配置。

影响。 在基于 OpenAI 的部署中,富化调用的模型与提取不同。

缓解措施。 无论 Cato-v2 使用何种提取模型,Cartex 富化均应始终使用 Gemini。富化器维护独立的模型配置。

R7:门的 Product Type 保留

对于来自立面的条目,Product Type 来自检测模型的标签名称。对于来自清单的条目,取决于当前 PromptTemplate 是否包含 Product Type 字段。

影响。 门行可能在没有 Product Type 的情况下到达富化阶段,降低开启方式和配置映射的富化质量。

缓解措施。 确保默认提示词模板始终包含带有完整提取规则的 Product Type 字段。若当前模板缺少该字段,添加验证警告。

风险登记摘要

# 风险 优先级
R1 无 MAIN vs AUXILIARY 表格角色分类 P0
R2 图像解读完全缺失 P0
R3 结果格式不匹配——富化元数据无存储位置 P1
R4 提示词模板 / 提取输出字段名对齐 P1
R5 批量处理阻碍逐表行归属 P1
R6 Gemini 模型依赖 P1
R7 Product Type 字段可能在模板中缺失 P1

阻断性依赖项

以下事项必须在 Cartex 富化能够在 Cato-v2 中运行之前完成。

# 事项 章节 优先级
1 为非清单类 evidence 添加 interpretation 列 + Gemini 视觉调用 图像上下文缺口 P0
2 实现 classify_table_role()(MAIN/AUXILIARY) 映射层设计R1 P0
3 构建映射层(to_extraction_resultbuild_user_table_schema 映射层设计逐字段提示词缺口 P0
4 为富化审计字段添加 cartex_metadata 存储 R3 P1
5 验证模板与提取输出之间的字段名对齐 R4 P1
6 确保默认模板包含 Product Type 字段 R7 P1