订单清单 → Excel(物 / 数量 / 店 / 备注)
用途
把用户随手写的清单文本落到固定四列:
| 物 | 数量 | 店 | 备注 |
|---|---|---|---|
| 弹簧水 | 1 | 招财猫 | 已付 |
架构:谁负责什么(这是本技能的核心约定)
| 环节 | 承担者 | 原因 |
|---|---|---|
| 看懂:哪段是物品名、哪个是人名、哪个像门店 | 大模型(你) | 只有语义理解能判断「秋婷 / 昆明 / vivian」这种陌生词,纯规则做不到 |
| 写对:JSON → 校验 → 渲染 .xlsx | 脚本 scripts/build_xlsx.py |
确定性的活交给确定性代码,结果可复现、可回归 |
| 兜底/基线:无模型可用的纯规则抽取 | 脚本 scripts/rules_extract.py |
离线路径 + 交叉校验基线,不是主路径 |
| 移动端落表:无脚本执行能力时的降级交付 | 大模型(你)+ tencent-docs 连接器 |
手机云上受限环境可能没有 Python,降级链见「工作流 B」 |
不要用规则脚本代替大模型做抽取;也不要让大模型去手写 xlsx。
运行环境判定(第 0 步,先做)
本技能有两条工作流,入口由执行环境决定。开工前先探测一次(只探测一次,不要反复重试):
python3 --version # 或 python --version / py -3 --version
| 探测结果 | 环境 | 走哪条 |
|---|---|---|
| 有版本输出,且能读写文件 | 桌面端 / 完整沙箱 | 工作流 A(脚本落表,完整样式 + 核对页) |
| 命令执行不了,或明确知道自己在手机 App / 小程序的云端受限会话 | 移动端云上受限环境 | 工作流 B(纯大模型抽取 + 在线表格 / 表格文本) |
判定原则:
- 探测失败立即切 B,不要卡在重试上。移动端用户在等一个能用的结果,不是等一个报错。
- A 路径中途
pip install openpyxl失败,同样算 A 不可用 → 切 B,抽取成果直接复用,不返工。 - 拿不准自己是什么环境:先按 A 走,第 4 步脚本跑不起来就无缝转 B。
工作流 A:桌面 / 沙箱路径(可执行脚本,主路径)
> 仅当第 0 步判定为「桌面端 / 完整沙箱」时走本节。移动端直接跳「工作流 B」。
- 读词表:打开
stores.json,拿到店名清单(含别名)、人名清单、备注关键词。这些是硬约束。 - 读规范:完整读
references/llm-extraction-guide.md。其中第三、四节是判定铁律与必须对齐的 few-shot 案例,必须逐条遵守。 - 语义抽取:逐行判断,产出 JSON(结构见规范第二节)。
- 单批 ≤ 200 行;超过就分part1.json/part2.json…
-原始行逐字保留原行,便于核对对齐。 - 落表(脚本干确定性的活):
bash <PYTHON> "<SKILL_DIR>/scripts/build_xlsx.py" --input extracted.json --output results/订单_YYYYMMDD.xlsx
-<SKILL_DIR>= 本 SKILL.md 所在目录。
- 结果落盘约定:所有生成结果(xlsx/json/中间抽取文件)统一放到当前工作目录的results/子目录,不散落在工作区根目录。省略--output时脚本默认就是results/订单_时间戳.xlsx,目录不存在会自动创建。
-<PYTHON>用当前环境可用的python(Windows 下py -3亦可);缺openpyxl就先pip install openpyxl。
- 分批结果合并:--input part1.json part2.json;加--strict则存在校验提示时退出码非 0。
- 脚本会打印实际用到的词表路径(词表:...),用它确认生效的是哪一份词表。 - 看脚本的校验输出:脚本会打印"读入 N 行 / 写入 M 行 / 校验提示 K 行"。
- 若报「店『X』不在店名清单,已从明细中移除」——说明你违反了铁律 1,回到第 3 步修正 JSON 重跑,不要就这么交付。
- 若报「数量『X』不是纯数字」——你误把规格数字当成了数量。 - 交付:用
present_files交出.xlsx,回复里只讲总行数、写入行数、以及需要用户决策的异常(例如"某店名不在清单,是否加进清单"),不要复述整张表。 - 存到腾讯文档(可选,默认尝试):把生成的
.xlsx存到用户的个人腾讯文档,并把文档链接一并给出。
- 依赖:tencent-docs能力(腾讯文档)。先按下面的「探活三步法」确定通道,不要一上来就找脚本。
- 可用时:走「本地文件上云」通道,保留原文件结构(不要用create_*重新生成内容,会丢样式与 Sheet2 核对页)。
- 不可用时:不要报错、不要中断。照常交付本地.xlsx,并补一句提示:如需自动存入腾讯文档,先在连接器里启用「腾讯文档」。
- 本步骤失败不影响已生成的 xlsx,交付以本地文件为准。
- ⚠️ 先查参数再调用(MCP 通道用ToolSearch查 schema,脚本通道用tdoc_schema),禁止凭记忆拼参数。
腾讯文档探活三步法(必须按顺序)
> ⚠️ 踩过的坑:本技能是跨平台打包的,文档里写的 tencentdocs.py / tdoc_init 只对部分平台成立
> (OpenClaw 那类提供脚本通道的 Agent)。在把腾讯文档作为 MCP 工具接入的环境(如 WorkBuddy)
> 里根本不存在该脚本。拿「找不到脚本文件」当「连接器未授权」是误判——会把本来能上云的结果
> 白白降级成本地交付。
| 步 | 动作 | 判据 |
|---|---|---|
| 1 | 列 MCP 资源 / 看工具列表(如 ListMcpResources) |
看到 tdocs-app(或同类腾讯文档 server)在线 → 走 MCP 通道,直接进第 3 步,不必碰脚本 |
| 2 | 第 1 步没有 → 找脚本通道:python3 tencentdocs.py tdoc_init |
返回 ERROR:no_token = 连接器在但未授权;脚本不存在 = 本环境没这条通道,与授权无关 |
| 3 | 两条通道都没有 | 才判定「连接器不可用」→ 按上面的降级方式交付,不要报错中断 |
关键区分:以下三种状态是不同的,不能混为一谈 ——
| 状态 | 特征 | 该怎么办 |
|---|---|---|
| 连接器不存在 | 既无 MCP server,也无脚本 | 降级本地交付 |
| 连接器存在但未授权 | MCP 调用返回 no_token 类错误,或 tdoc_init 返回 ERROR:no_token |
提示用户去连接器里启用「腾讯文档」 |
| 连接器存在且可用 | MCP server 在线 / tdoc_init 正常 |
走「本地文件上云」 |
> 通用原则:跨平台技能里,任何「本地脚本探活」都只对特定平台成立,不能作为统一的可用性判据。
> 优先用宿主自身的工具发现机制(MCP 资源/工具列表)判断能力是否存在。
MCP 通道的调用序列(WorkBuddy 等 MCP 环境)
1. ListMcpResources -> 确认 tdocs-app 在线
2. ToolSearch -> 取 pre_import / async_import / import_progress 的 schema
3. manage.pre_import(file_name, file_size, file_md5)
-> 拿 upload_url / file_key / task_id
4. curl -X PUT -T <xlsx> <upload_url> -> 上传到 COS(期望 HTTP 200)
5. manage.async_import(file_key, file_name, file_md5, file_size, task_id)
-> 触发导入
6. manage.import_progress(task_id) -> 每 5 秒轮询,progress=100 时返回 file_id / file_url
7. 用 sheet.get_cell_data 拉回线上内容逐格核对(**只看进度条不算数**)
8. 把 file_url 交付给用户
file_md5 用 md5sum <xlsx> | cut -d' ' -f1 取;file_size 用 stat -c%s <xlsx>。
线上核对请覆盖关键行:兜底备注行、裸单位行、店名命中行。
工作流 B:移动端路径(手机 App / 小程序,无脚本执行能力)
> 触发条件:第 0 步判定无法执行脚本(云上受限环境)。抽取逻辑与 A 完全一致,只是「落表」换载体。
> 本路径没有 build_xlsx.py 替你把关,自检就是最后一道校验,逐条执行,不能省。
B1. 读词表与规范
按以下顺序取词表(先命中者胜出):
- 技能目录的
stores.json; - 读不到 → 读
references/stores-snapshot.md——这是打包时由stores.json自动渲染的词表快照,内容与真源一致; - 两处都读不到 → 按对话上下文谨慎处理,交付时明确说明「本次未加载到词表,【店】列可能不全」;
不要默默把疑似店名填进【店】列。
然后完整读 references/llm-extraction-guide.md,判定铁律与 few-shot 逐条遵守。
B2. 语义抽取(与 A 相同)+ 自检
逐行抽取,产出 guide 第二节结构的 JSON(只在内存里维护即可,无需落盘)。抽取完成后,把 guide 第六节自检与下列各条逐条过一遍:
- 行数守恒:输出条数 == 原文有效行数,顺序一致;
- 【店】白名单:每个店名都在词表
stores(或别名)内——宁可留空,不可猜; - 【数量】全是独立成词的纯数字,粘着数字一律不取;
- 【备注】没有「认不出的词」;
- 凡【物】【数量】都空的行,【备注】== 整条原文;这类行连【店】也空的,回头复核;
- 非高置信度的行都写了理由。
自检不过就修正,不要带病进入 B3。
B3. 落表(降级链,从上往下试,成功即停)
| 级别 | 载体 | 前提 | 做法 |
|---|---|---|---|
| B3-a | 腾讯文档在线表格 | tencent-docs 连接器可用 |
① 按「腾讯文档探活三步法」判定通道(先看 MCP 资源,别先找脚本;不可用或无 python3 → 直接跳 B3-b);② 建表 tdoc_call tencent-docs manage.create_file '{"title":"订单_YYYYMMDD","doc_type":"sheet"}',取回 file_id;③ 把明细序列化成 CSV,用 sheet-mcp 的 set_range_value_by_csv 一次灌入(先 tdoc_schema sheet-mcp set_range_value_by_csv 查参数,禁止凭记忆拼);④ 有核对内容(人名 / 未识别片段 / 低置信度说明)就 add_sheet 建「解析核对」子表再灌一次。交付时给出文档链接。 |
| B3-b | 对话内表格 | 上面都不通 | 直接在回复里输出两张 Markdown 表:① 订单明细(物/数量/店/备注);② 解析核对(人名/未识别片段/说明)。并附一个 CSV 代码块,方便用户粘贴进 Excel / 腾讯文档。 |
注意:
- B3-a 走
create_file + set_range_value_by_csv,没有本地 xlsx 的样式与核对页结构——这是移动端可接受的降级,不是错误。表头底色浅蓝DDEBF7可用set_cell_style补(可选;不要为它多耗步骤)。 - 「不要用
create_*重新生成内容」的禁令只针对已有本地 xlsx 的上云场景(工作流 A 第 7 步,会丢样式);B 路径本来就没有本地文件,create_file是唯一通道。
B4. 交付
- 明确告知用户:当前是移动端简化模式,没有本地
.xlsx文件;需要带完整样式与核对页的 xlsx 时,回桌面端跑工作流 A。 - 其余口径与 A 第 6 步相同:只讲总行数、写入行数与需用户决策的异常,不复述整表。
词表维护(stores.json)
唯一的维护入口,新增门店/人名只改这里,不改代码。
词表放在哪(查找顺序)
脚本按以下顺序定位词表,先命中者胜出:
- 命令行显式指定的
--config - 当前工作目录下的
stores.json← 推荐 - 技能目录下的
stores.json
为什么支持放工作目录:词表是个人私有的资产(每人有自己的门店清单和人名)。
技能包自带一份可直接使用的 stores.json,所以开箱即用;但需要多人之间隔离词表时,
让每个人在自己工作目录放一份 stores.json 即可覆盖包内那份,互不干扰。
两处都没有 stores.json 时,脚本会报错退出并说明应把词表放在哪里。
落表时脚本会打印实际用到的词表路径(词表:...)——怀疑词表没生效就先看这一行。
| 键 | 作用 |
|---|---|
stores |
店名清单。【店】列只允许出现这里的店名。 |
store_aliases |
店名别名/简写 → 正式店名。 |
person_names |
人名清单。命中后只进核对表「人名」,不进交付四列。 |
item_words |
物品名补充词(纯规则兜底脚本使用)。 |
short_cjk_is_name |
纯规则兜底脚本的启发式开关。 |
remark_keywords |
备注关键词。独立成词 → 【备注】;紧贴店名的残留 → 店名进【店】、残留进【备注】。 |
action_words |
动作词(拿东西/拿货/取货/提货/送货…)。店名 + 动作词 表示"去该店办事"而非买商品:店名进【店】,物/数量留空,整条原文进【备注】。 |
> 用户提到新门店时:先提议加进 stores.json,再重跑落表。不要为了跑通而放宽脚本校验。
> 移动端兜底(快照文件):打包时 pack_skill.py 会把 stores.json 自动渲染成 references/stores-snapshot.md 写进 zip——源目录里没有这个文件,勿手改、勿手建。改词表只改 stores.json,重新打包即可,永远只有一份真源。
判定规则速查
- 去掉行首
- [ ]/- [x]/1./*标记,按空白切词(含全角空格)。 - 数量 = ① 独立成词的纯数字(可带单位),与位置无关;② 独立成词的裸单位(
支/瓶/盒…,前后无其他文字 → 数量 1 的省略写法)。
双萃75、善存钙片50+里粘着的数字是规格,不算数量;万宁牌湿巾里粘着的「巾」也不算单位。 - 店 = 命中店名清单(含别名)的词。另有一条「店名 + 可解释残留」的宽松通道:残留命中备注关键词(
招财猫已付)或动作词(壹佳拿东西)时,店名进【店】、残留进【备注】。万宁牌湿巾的残留「牌湿巾」两头都不沾,所以不会被误判成店名。 - 物 = 第一个「普通词」起,持续并入后续普通词,直到遇到 数量/店/备注,或遇到「人名样」的词(命中
person_names,或纯中文且 ≤2 字)。故swisse 维c 君→ 物=swisse 维c、君是人名。 - 备注 = ① 独立成词的备注关键词;② 紧贴店名且可解释的残留;③ 独立成词的动作词;④ 整行兜底——【物】和【数量】都提不出来时,把整条原文写进【备注】(
壹佳拿东西→ 店=壹佳、备注=壹佳拿东西)。④ 只在双空时触发,不会覆盖已提取到的备注值。 - 其余一切 → 只进核对表「未识别片段」,不写入四列任何一列。
> 裸单位顺序:判定时「裸单位」先于「人名」。支/只/份 既可能是单位也可能是单字人名
> (这些词原先在 person_names 里),用户 2026-10-08 确认约定为「行尾孤立单位词按数量处理」。
> 不做数字/单位纠缠判断——不去猜这个单位属于哪件商品,独立成词就认。
硬性约束
- 【店】列只能来自
stores.json;清单外的店名宁可留空,脚本也会强制剔除。 - 【备注】只来自备注关键词、店名残留、动作词、整行兜底这四种来源,不塞认不出的词充数。
- 数量必须是独立成词的纯数字;粘在物品名/人名上的数字是规格,不是数量。
- 行数守恒:原文几行输出几条,不合并、不拆分。
- 输出四列之外的诊断信息(人名、未识别片段、置信度、判断说明)一律进 Sheet2「解析核对」,绝不污染 Sheet1。
- 落表样式:所有单元格默认开启自动换行——列宽被改窄后长内容折行完整显示,不截断、不溢出到邻列。
行高一律不写死(只固定表头 22),交给 Excel 自适应;不要为了"好看"给数据行设固定行高,否则折行内容会被压扁。 - 列宽不用默认值:订单明细四列取自
DETAIL_WIDTHS(build_xlsx.py/rules_extract.py顶部常量,
由用户本人在腾讯文档里手动调定后固化,2026-10-06)。不要擅自改成"看起来更合适"的宽度;
用户报出新列宽时,同步改这两个常量,并保持一致。 - 表头底色固定浅蓝
DDEBF7(HEADER_FILL常量,2026-10-06 用户指定)。
Sheet2 的诊断底色(低置信度FFF7E6/ 校验提示FDE9E9)另有含义,不要一并染成表头色。 - 对齐:数量列(B)居中(数据行与表头都居中),其余列默认左对齐、垂直居中。不要把数量列改成默认对齐。
- 边框:所有单元格黑色细线(
BORDER_COLOR = "FF000000",表头与数据行同一套)。
不要用浅灰(D9D9D9/BFBFBF)——那在屏幕上看不出,用户会当成"没有边框"。 - 交付前必须过校验:工作流 A 跑
build_xlsx.py的自检,工作流 B 执行 B2 自检清单;有校验提示就先修正,不能带病交付。
脚本清单
| 脚本 | 角色 | 用法 |
|---|---|---|
scripts/build_xlsx.py |
主路径:JSON → 校验 → xlsx | --input a.json [b.json...] --output x.xlsx [--strict] [--json] |
scripts/rules_extract.py |
离线兜底/基线:纯规则抽取 | --input 清单.txt --output x.xlsx 或 --json 输出同结构 JSON |
scripts/pack_skill.py |
发布用:打包成可上架的 zip(校验 frontmatter、保证 SKILL.md 在 zip 根、自动把 stores.json 渲染为 references/stores-snapshot.md 注入包内) |
python scripts/pack_skill.py [--out dist] [--check-only] [--wrapped] |
规则细则、推导过程与回归基准见 references/extraction-rules.md;回归用例见 references/cases.txt(31 条真实数据)与 references/cases-edge.txt(边界/确认案例)。