跳转至

Excel 解析:怎样把“人能看懂”的表格变成“系统敢使用”的事实

这一页我会先这样讲

很多人听到 Excel 解析,会觉得就是读取行和列。实际工作中最难的不是打开文件,而是判断一块内容到底是什么、哪一行是表头、哪些是备注、同一个字段的不同叫法如何统一,以及不确定时谁来负责确认。

所以我没有把目标定义成“把 Excel 转成文本”,而是定义成:

在不丢失原始证据的前提下,把能确定的内容变成标准业务记录,把不能确定的内容变成可回答的问题。

小黑把混乱 Excel 证据分拣为正式事实、待确认和仅留证

趣味图只负责建立直觉:先分拣、再确认,不把“看起来像”当成事实。

0. 这个模块做之前和做之后有什么不同

改造前,信息整理很依赖具体的人。餐厅经理、厨师长、服务和食安人员可能各自维护表格,同一含义使用不同标题;一张 Sheet 里也可能混着菜单、备注、结算和布场。未写进 Excel 的内容还可能留在其他文件或口头交接中。老员工凭经验能理解,新人却要先找版本、问含义、补缺失,才能开始做方案。

系统没有假设这些旧文件天然规范,也没有假装能自动知道没有被记录的口头信息。它做的是把“混乱留给每一个使用者反复理解”,改成“系统先保留证据并标准化,无法确认的地方集中问一次”。

维度 改造前 当前系统带来的效果
文件和格式 每份 Excel 的布局、表头、字段叫法可能不同 原文件不被覆盖,解析器把可识别内容映射到版本化标准字段
信息完整性 空白、残缺和口头信息容易被默认补全或遗漏 缺失和歧义形成明确问题;口头信息必须重新录入或由责任人确认
来源与版本 看到一个数字,不一定知道来自哪份表、哪个位置、哪个版本 保存文件哈希、Sheet、坐标、公式、解析版本和确认记录
人员依赖 新人需要老员工逐份解释,理解成本高 新人可以先看统一字段、来源证据和确认状态,再处理业务例外
历史复用 每次重新翻文件,容易把旧人数、旧金额带到新活动 正式事实进入 PostgreSQL,允许发布的历史经验进入 WISE,并与本次需求分开
方案准备 人工找资料、核数字、拼方案 Agent 调用已确认事实和规则生成方案,把人的精力留给不确定项和最终决定

因此,当前可以证明的是流程发生了变化:数据有来源、不确定性可见、正式事实可复用。项目还没有做大规模业务计时,所以不能声称已经量化节省了多少人时,也不能承诺任意 Excel 都能零人工解析。

1. 我思考过哪些工业界做法

做法 优点 在这个项目中的问题 我的取舍
Excel 转 HTML 能保留行列、合并单元格和部分样式,适合让模型“看见版面” HTML 很像原表,但不等于业务事实;来源、审核、版本和字段类型仍要另外管理 可作为视觉/模型输入的一种表达,不作为最终事实库
Excel 转 Markdown 文本紧凑,适合 RAG、Prompt 和人工阅读 复杂合并表头、并排表、图片和空间关系容易丢失;金额和数量也不应只靠语义检索 用于 WISE 发布的受控文本投影,不直接代表整份 Excel
每行做成宽表,放入 DuckDB 对结构稳定的 CSV/Excel 很适合,SQL 聚合和批量分析快 当前工作簿不是一张稳定宽表;还需要人工审核、版本替换、来源坐标和发布状态 DuckDB 可作为后续探查/分析层,不替代 PostgreSQL 事实与审核模型
整份表直接交给大模型 开发快,能处理一些模糊语义 输出不稳定、成本高、难以逐字段回溯,模型可能“理解过度” 只分析确定性代码解决不了的疑难区域,而且只输出候选
当前方案:证据 + 规则 + 候选 + 人审 可复现、可回溯、模型可替换、失败可降级 工程量更大,第一版不能覆盖任意格式 符合围餐业务对来源、食安、金额和确认责任的要求

选择当前方案的核心原因不是“它最先进”,而是它更符合业务风险:菜单描述可以做语义检索,但人数、桌数、金额、食安状态和审核结论不能只靠相似度或模型猜测。

这个判断在项目早期的 Git 任务里已经明确:DuckDB 如果引入,只做分析与探查;PostgreSQL 负责事实、审核、版本和发布状态;WISE 只接收经过策略筛选的文本投影。

2. 为什么“读取成功”不等于“解析成功”

我把解析拆成五级,这样现场讨论准确率时不会把不同问题混在一起。

层级 业务问题 技术判定
文件级 文件能不能打开 .xlsx 合法、未加密、大小和 Sheet 数在限制内
证据级 原内容有没有保存 文件哈希、Sheet、非空单元格、坐标、公式、样式和图片引用可回查
结构级 哪些单元格属于同一张表 连续区域、横向子表、表头行、合并表头和范围被识别
语义级 这是什么业务、字段是什么意思 业务域分类、标准字段映射、类型转换和质量问题生成
业务级 能不能进入正式菜单/食安/人员等表 满足身份字段和审核门禁,经过确认并成功投影
flowchart TB
    F["文件级<br/>能安全打开"] --> E["证据级<br/>哈希、Sheet、坐标、公式"]
    E --> S["结构级<br/>表块、表头、并排子表"]
    S --> M["语义级<br/>业务域与标准字段"]
    M --> B{"业务级门禁<br/>身份字段与审核是否满足?"}
    B -- "满足" --> OK["正式业务行"]
    B -- "不满足" --> KEEP["保留证据 / 发起确认"]

因此,“能在页面看到内容”只能说明证据级成功;“生成了规范记录”不一定代表已经形成正式业务行;“PostgreSQL 已提交”也不等于 WISE 已经发布成功。

3. 第一层:工作簿为什么打开两次

系统使用 openpyxl 读取 .xlsx,同一个工作簿会以两种方式打开:

  1. data_only=False:保留公式本身。例如单元格里写的是 =SUM(...),系统能记录原公式。
  2. data_only=True:读取 Excel 文件里缓存的计算结果,尽量拿到用户看到的值。

这样做的原因是:只读公式,不知道最终显示值;只读结果,又会丢失计算依据。

每个非空单元格不只保存一个字符串,还会保留:

  • 文件 SHA-256,用来证明后续确认针对的是同一份文件;
  • Sheet 名和 A1 这类坐标;
  • 原始值、显示值、数据类型和公式;
  • 合并区域、样式提示、图片或 DISPIMG 引用;
  • 解析器版本和解析状态。

这一步的业务意义是:即使后续分类错了,原始证据仍在,可以重新解析,不需要覆盖原文件来“修正确答案”。

4. 第二层:怎样从一张 Sheet 里找出多张表

真实围餐工作簿经常不是“一个 Sheet 一张表”。可能左边是菜单,右边是结算,中间隔几列;也可能标题占两行,下方才是数据。

当前解析器主要做这些判断:

  1. 根据非空单元格的连续行列区域找候选表块。
  2. 如果横向出现至少两列空白间隔,尝试把并排子表拆开。
  3. 只在候选区域前六行中寻找表头,避免把正文中间某一行误当表头。
  4. 对多层合并表头,自底向上优先组合字段名。
  5. 根据数据密度、是否有表头等信息计算表块置信度。

这里的“表块”可以理解成:系统认为应该一起解释的一组单元格。

当前阈值里,业务域或表块置信度低于 0.65 会进入检查范围。这个数字不是业务真理,它只是第一版工程门槛,后续要用更多真实工作簿校准。

5. 第三层:怎样判断这是菜单、食安还是布场

系统不是让模型自由给表格起名字,而是从受控业务域中分类,例如菜单、食安、酒水、人员、物资、结算、布场、工作进展和复盘。

分类依据包括:

  • Sheet 名、区域标题出现了哪些业务关键词;
  • 表头匹配到了哪些领域专用字段;
  • 内容值是否符合该领域的典型结构;
  • 某些领域的确定性纠偏规则。

例如,历史上“食安主表”曾经因为菜名很多被误分到菜单域。后续不能只看“菜名”,而要结合食安字段、标题和领域证据来消除平分。

如果候选分数接近或证据不足,后端不会让模型直接定案,而是返回一个中文问题和最多四个受控候选,让业务人员确认。确认后,系统从原文件重新执行结构、字段和记录解析。

6. 第四层:字段注册表解决了什么

业务人员会写“菜品”“菜名”“品名”,程序最终希望得到统一字段 dish_name。字段注册表就是一份版本化的数据字典,它定义:

  • 每个业务域允许哪些标准字段;
  • 常见中文别名如何映射;
  • 字段类型是文本、整数、小数、日期、时间、布尔值还是列表;
  • 哪些字段组成一条正式业务记录的身份;
  • 字段能否发布到 WISE,还是只保留在 PostgreSQL。

表头会先做空白、符号、大小写等标准化,再进行精确或强匹配。无法可靠匹配时,系统只返回“当前业务域内、当前缺失身份所需要、值类型兼容”的有限候选,并始终提供“仅保留原始证据”。

为什么不把所有数据库字段放在下拉框里?因为业务域、字段类型和发布策略是一套共同边界。让用户任意选字段看似灵活,实际可能把酒水的“品名”映射成人员“姓名”。

7. 第五层:一行怎样变成规范记录和正式业务行

表头确定后,系统按数据行生成 NormalizedRecord

  • fields:已经映射并通过类型转换的标准字段;
  • extra_fields:暂时无法理解但仍保留的内容;
  • raw_values:原始单元格值;
  • 来源 Sheet、表块范围、行号和记录 ID;
  • parsedparsed_with_warningsneeds_review 等质量状态。

接下来还要检查“身份字段”。例如一条菜单正式记录至少要能识别菜名;识别不了时,记录可以留在知识记录中,但不能冒充正式菜单行。

正式导入时,PostgreSQL 在一个事务中保存来源文件、单元格证据、表块、规范记录、正式业务投影、语义决定和 WISE Outbox。任何模型 HTTP 请求都在数据库事务外完成,避免长事务和外部超时拖垮数据库。

8. LLM 在 Excel 解析里到底做了什么

当前主流程里有两类模型能力,职责不同。

8.1 文本模型:把技术问题改写成业务人员看得懂的问题

后端先用代码确定问题类型、候选字段和证据,再让文本模型把问题改写得自然一些。模型不能增加候选、删除 retain_only、替用户选择,也不能改变证据范围。

模型未配置或超时时,使用确定性中文模板继续工作。因此它影响的是交互文案,不是解析结果。

实际 system Prompt 的核心内容是:

You rewrite one deterministic Chinese banquet Excel ambiguity question
for business users.

Return exactly one JSON object with only title, prompt, and candidates.
Keep every supplied candidate key exactly once.
Do not add, remove, rank, recommend, or choose a candidate.
Do not change evidence, sheet, cell range, affected count,
blocking status, or any other field.
Write concise Chinese business language.

也就是说,它只有“改写权”,没有“改题权”和“代选权”。

8.2 视觉模型:只处理平面布场疑难区域

普通行列表格继续走代码。只有确定性解析已经识别为 venue,但缺少正式身份字段的记录,才会触发布场视觉分析:

  1. 把相关 Sheet 渲染成带坐标的 PNG。
  2. 向模型提供 Sheet 范围、确定性表块摘要和真实来源记录 ID。
  3. 模型返回有限 JSON 候选,例如区域、物项、桌号、数量和单位。
  4. Pydantic 检查 JSON 结构;后端再检查 Sheet、范围、记录 ID、字段白名单和数值边界。
  5. 后端为候选生成签名信封,绑定源文件哈希、解析器版本和 15 分钟有效期。
  6. Reviewer 明确选择 acceptretain_only
  7. 正式导入重新解析同一文件并验证签名,模型候选才可能生成 setup_tasks

模型置信度不会让前端自动勾选。模型超时、非法 JSON、候选越界或零有效候选都会显式降级并阻止这部分被当成正式事实。

布场模型的实际 system Prompt 版本由 VENUE_LAYOUT_PROMPT_VERSION 管理,核心内容是:

You analyze one Chinese banquet venue-layout spreadsheet image.
Return exactly one JSON object with only a candidates array.

Each candidate must contain source_record_id, sheet_name, cell_range,
fields, reason, evidence, and confidence.
source_record_id, sheet_name, and cell_range must exactly match
one supplied record.

fields may use only supplied allowed_fields.
Do not extract staff names or URLs. Do not invent tasks or IDs.
Return no more than the supplied max_candidates.

Every candidate must contain at least one of area, item_name,
placement_requirement, or service_route.
Confidence is audit metadata only and never approval.

用户消息不是一句开放问题,而是结构化上下文加图片:源文件哈希、解析器/Prompt 版本、Sheet、允许字段、候选上限、真实记录列表、图片范围和图片 SHA-256。模型请求温度为 0;供应方在携带 response_format 时曾出现超时,因此这个专用适配器不依赖供应方的 JSON mode,而是在响应后自行提取 JSON,再经过 Pydantic 与后端业务校验。

模型候选怎样进入正式事实

flowchart TB
    D["确定性解析"] --> C{"规则能确认吗?"}
    C -- "能" --> V["后端类型与业务校验"]
    C -- "不能" --> M["模型提出有限候选"]
    M --> G["Schema + 白名单 + 来源绑定"]
    G --> R{"Reviewer 决定"}
    R -- "accept" --> V
    R -- "retain_only" --> K["仅保留原始证据"]
    V --> T["PostgreSQL 单事务写入"]
    T --> O["Outbox 等待发布"]

这张图的重点是:模型输出后面还有三道门,程序校验、来源绑定、人工决定,不是“模型置信度高就自动通过”。

9. 当前真实结果应该怎样表述

可以说:

当前已经打通“确定性解析—受控问题—人工选择—重新解析—正式投影”的闭环。专用布场模型在两份真实工作簿上分别形成 8 条正式布场任务,共 16 条。

必须紧接着补充:

这 16 条证明第一版受控任务投影可以工作,不代表完整空间图、全部节点关系和任意 Excel 已被解决。

历史真实样本还曾形成 5,403 个有效单元格、988 条规范记录和 604 条正式业务投影;剩余 384 条未投影记录说明系统保留了“不理解就不强投影”的边界。这里是历史验收基线,不应当作当前线上规模。

10. 当前不足和后续优化

当前不足 为什么三周内没有继续做深 下一步优化
Excel 格式覆盖不足 真实样本量小,继续加规则容易过拟合两份文件 建立黄金样本集,分别统计表块、字段、记录和正式投影准确率
can_import 更关注问题是否回答,不等于投影覆盖完整 先保证确认闭环和安全门禁 返回精确的新增、跳过、仅留证和失败原因,拆开“可导入”和“覆盖充分”
表头候选阈值是经验值 缺少足够标注数据校准 记录候选分数与最终人工选择,离线优化阈值
布场只形成任务,未形成完整空间图 空间节点、关系、动线编辑器工作量较大 引入节点/关系白名单、原图叠加审核和可视化编辑器
文本问题逐题调用模型可能慢 第一版优先完成安全合同 批量改写、缓存结构指纹、失败熔断
仅支持 .xlsx,公式缓存可能过期 先限定可控输入 增加 .xls/.xlsb 转换层、公式重算服务和文件兼容报告
业务人员审核成本仍高 当前先确保每个决定可追溯 聚合同类问题、显示影响行数、学习已确认规则但保持版本化
DuckDB 尚未接入 当前瓶颈首先是语义和审核,不是 SQL 扫描速度 数据量上升后用于批量探查、质量分析和 Parquet 中间层,不替代事实库

11. 技术评委追问时的代码入口

关注点 代码/文档
工作簿读取 banquet_agent/app/ingestion/excel/workbook_loader.py
表块检测 banquet_agent/app/ingestion/excel/block_detector.py
表头和字段解析 banquet_agent/app/ingestion/excel/header_resolver.py
字段注册表 banquet_agent/app/ingestion/excel/field_registry/registry.json
行规范化 banquet_agent/app/ingestion/excel/normalizer.py
主流程编排 banquet_agent/app/ingestion/excel/service.py
布场视觉适配器 banquet_agent/app/infrastructure/llm/venue_layout.py
完整技术底稿 banquet_agent/docs/Excel解析全景说明-2026-07-26.md

本模块一句话收尾

Excel 解析的技术价值,不是把单元格读成字符串,而是建立“原始证据不丢、确定内容可复现、不确定内容可确认、正式事实有门禁”的数据生产线。