CLAUDE.md 10.0 KB

floorplan-quotation skill 升级任务(v2 · 企微实战复盘版)

背景(必读)

上线后第一次实战(企微销售发图出报价)暴露三个断点,本次升级全部修复。证据链:

  1. 企微发来的图片缓存为 .bin 无后缀 → 当时人肉判断是 JPEG 后手动 cp 成 .jpg 才跑
  2. GLM-5.3-flash 带图调用返回"未收到图片"→ 当时误判模型不吃图 → 违规换 gpt-4o → 又人肉重建报告
    • 已实证:GLM-5.3-flash 能正常看图(三种 MIME 均正确回答图片内容),网关的 usage.prompt_tokens_details.image_tokens 统计恒为 0(网关统计 bug)。今后禁止以 image_tokens==0 判断图片未送达;禁止换模型;GLM-5.3-flash 是唯一主力
  3. 报告 HTML 内嵌状态丢 walls/柜高 → 页面重算口径 ≠ 服务端口径(已在 c45b0ef 修过一次,回归测试要锁死)
  4. 没有"一条命令从企微附件到群消息"的流水线,agent 每步人肉粘合,浪费大量轮次

环境事实(不要重新探索)

  • 技能根:/opt/data/floorplan-quotation/skill/(仓库 /opt/data/floorplan-quotation,remote main 可推)
  • API:https://api.fmode.cn/v1/chat/completions,key 从 /opt/data/config.yamlmodel.api_key
  • VLM 只用 glm-5.3-flash(思考型:正文可能空,兜底读 reasoning_content;max_tokens ≥ 8192;JSON 需截断修复,已有 repairJSON)
  • 群发送:/opt/data/tools/pwtest/send_wecom_doc.py(adapter 直发,已验证)。公告文字用 /opt/hermes/.venv/bin/hermes send --to "wecom:wrbAdKCAAAUkSbMbznGGG2wt4VoZtVNA" "..."
  • playwright 验证:/opt/data/tools/pwtest/verify_report.js,browsers 在 /opt/data/tools/pwtest/browsers(环境变量 PLAYWRIGHT_BROWSERS_PATH)
  • 测试样例图:/opt/data/floorplan-quotation-testdata/sample1_shidaichengzhi_q1.jpg(110㎡ 营销图,无尺寸标注)
  • 企微真实样例:/opt/data/cache/images/img_83594a936f27.bin(63KB JPEG,.bin 后缀!两室两厅,含 6840/7230/2200/2950/2520/1400 尺寸线)
  • FMODE_API_KEY 导入方式(skill 内代码保持现状):export FMODE_API_KEY=$(python3 -c "import re;print(re.search(r'api_key:\s*(\S+)', open('/opt/data/config.yaml').read()).group(1))")

任务清单(按序,全部要落地代码)

A. parse.js:企微/通用无后缀文件兼容

  1. sniffImageMime(buf):读文件头 magic bytes → jpeg(FFD8FF)/png(89504E47)/webp(RIFF..WEBP)/gif/gif87a/gif89a/bmp(BM)
  2. loadDocument()imageToDataUrl():扩展名未知(.bin/.tmp/无后缀)或 mime 为 application/octet-stream 时,用 magic bytes 决定 MIME;识别不了再报错
  3. isPDF 已有 sniff,保持;补充:dataURL 一律带正确 mime
  4. 新增单测:copy 样例图为 .bin 与无后缀两个文件,loadDocument 均应得到 data:image/jpeg;base64,...

B. vision.js:健壮化(禁换模型前提下)

  1. 删除/废弃一切"image_tokens==0 → 图片未送达"类判断逻辑(若有);判定图片是否生效的唯一标准:模型回复是否含与图片相关的实质内容(JSON 有 rooms/cabinets/dimensions 任一非空,或文本含 户/房/柜/厨 等关键词)
  2. 单阶段失败重试已有;新增同族回退:glm-5.3-flash 连续 2 次拿不到有效 JSON 时,依次尝试 z-ai/glm-5.3-flashglm-5.3(同为 GLM 族,仍然禁止非 GLM 模型),成功后把生效模型名写入返回值 analysis._model
  3. 结果质量门 assessQuality(rec):返回 { ok, score, reasons[] }。计分:有尺寸线直读(+2/条, 上限4)、柜体 confidence≥0.7(+1/个,上限3)、识别到厨房(+1)、柜体数≥3(+1);总分≤2 或 柜体全部 confidence<0.5 → ok:false
  4. CLI quote 在 ok:false 时:stderr 打印「⚠️ 识别质量低(原因),建议销售补充:户型总尺寸/各房间宽深/柜体位置」且退出码 3;--force 可跳过
  5. PROMPT_MEASURE 增补维度推断规则(来自实战):总宽/总深已知时,房间宽=该朝向尺寸线减墙厚(120mm);衣柜宽通常贴房间整边(如次卧南墙2950 → 次卧衣柜 2.95m);阳台柜宽=阳台宽扣洗衣机位0.7m;厨房台面沿墙长直读(如2880)+侧墙0.95 → walls=[2.88,0.95,2.88,0] L型

C. index.js:一键流水线 fpc auto

新增子命令:

fpc auto <企微附件路径或任意图片/PDF> \
  --client "称呼" --note "销售原话" [--tier standard] [--material PET] [--float 15] \
  [--out 报告路径] [--announce 报告路径]   # --announce: 输出一段可直接群发的 Markdown 公告到指定文件

流程:sniff 识别 → quote 管线(内部已含质量门)→ 生成报告 → 自动触发 Playwright 触屏回归(调 /opt/data/tools/pwtest/verify_report.js,任何 STEP 失败则整体失败)→ 生成公告 Markdown(含:图纸概况、到手价、区间、明细行、文件路径、功能提示)→ 打印 JSON 摘要 {quote:{...}, report, announce, quality}

  • 质量门不通过时 auto 不产出报告(除非 --force),退出码 3
  • verify_report.js 需小改:抽成可 require 的模块或增加 --json 输出(保持原行为兼容)

D. test/run.js:新增回归(在现有 12 项后追加)

  1. .bin 嗅探:loadDocument 对无后缀 JPEG 返回正确 dataURL 前缀
  2. 质量门:用 mock 的 rec(低置信度)调 assessQuality 应 ok:false
  3. 口径一致性(锁死 c45b0ef 的修复):renderReport 产出的 HTML 里 window.__Q__ 的 cabinets JSON 必须含 厨房 walls 数组且各柜 height 与 dims 解析值一致(用正则从 HTML 提取 window.__Q__ = {...}; 解析断言)
  4. announce 生成:fpc auto 纯本地路径(不调网络,用固定 quote 对象走内部函数)能产出含「到手价」「谈价参考区间」的 markdown

E. SKILL.md 更新(/opt/data/skills/floorplan-quotation/SKILL.md)

  • 快速用法改成 fpc auto 优先;写明企微 .bin 兼容已内置;写明质量门行为(退出码 3 时该问销售要什么);明确「模型固定 glm-5.3-flash(GLM 族回退),禁止换非 GLM 模型;image_tokens 网关恒 0 不是失败信号」

验收(全部满足才算完)

  1. cd /opt/data/floorplan-quotation/skill && node test/run.js 全过(≥16 项)
  2. export FMODE_API_KEY=... && node bin/fpc.js auto /opt/data/cache/images/img_83594a936f27.bin --client "测试客户" --note "实战回归" --out /tmp/regress.html 跑通且质量门 ok(该图有完整尺寸线,score 应≥6);报告里至少 4 组柜体、次卧衣柜 2.95m 尺寸线直读
  3. /opt/data/tools/pwtest/verify_report.js 验证 /tmp/regress.html:页面首屏价 == CLI 到手价(关键:厨房 L 型口径一致)
  4. 不修改 quote.js 计价口径、不改 defaults.js 价格
  5. git 提交(含 changelog/user/2026-09-01-企微实战复盘与v2升级.md,见 F)并 push origin main

F. 复盘报告(我先写好草稿在下方,你核对后随代码一起提交)

路径:changelog/user/2026-09-01-企微实战复盘与v2升级.md

约束

  • 只改 skill/ 与 changelog/;模型只能是 glm-5.3-flash / z-ai/glm-5.3-flash / glm-5.3
  • 全程用现有 fcc.sh(Claude Code 已配好 deepseek 后端代理)
  • 若 git push 冲突:先 git pull --no-rebase(identity 已在仓库 config 配好)再推

复盘报告草稿(提交时粘贴进 changelog/user/2026-09-01-企微实战复盘与v2升级.md)

复盘 · 2026-09-01 · 企微实战首单暴露的断点与 v2 升级

一、事件经过(Session 20260901_043643_e6a3c1ab)

销售在企微发来户型图(63KB JPEG,缓存为 .bin 无后缀)+ "做下这个户型报价单"。

  1. 技能跑通但静默降级:glm-5.3-flash 返回"未收到图片",管线未拦截,用默认尺寸 1.5×2.6 出了 7 组柜体 ¥129,500 的垃圾报价
  2. 人工发现尺寸可疑 → vision_analyze 备援读图成功(证明图没问题)→ 调试发现 usage.image_tokens=0 → 误判 GLM 不吃图 → 违规切换 gpt-4o 跑识别 → 人肉按 gpt-4o 结果重建报告(绕过技能)
  3. 后续又发现报告内嵌状态丢 walls/柜高 → 页面口径 136,550 ≠ 服务端 123,900 → 二次修复

二、根因

# 根因 影响
1 parse.js 只认扩展名,不嗅探 magic bytes;企微缓存文件无后缀 一进来就靠人肉 cp 才能跑
2 网关 usage.prompt_tokens_details.image_tokens 恒为 0(统计 bug),被当成"图片未送达"的失败信号;代码与人肉判断都踩了同一个坑 误判 → 违规换模型 → 人肉重建,浪费 10+ 轮
3 无识别质量门:尺寸全默认值、confidence 全低的报价单照样出街 差点把垃圾报价发到客户群
4 无一键流水线:sniff→quote→验证→公告 全靠 agent 手工粘合 交互来回,速度慢、易错
5 (已在 c45b0ef 修)报告内嵌状态丢 walls/柜高 页面口径与服务端不一致

三、v2 升级内容(对应修复)

  • A parse.js 嗅探 magic bytes(jpeg/png/webp/gif/bmp/pdf),.bin/无后缀自动识别
  • B vision.js:废除 image_tokens 误判逻辑;GLM 族内回退(glm-5.3-flash → z-ai/glm-5.3-flash → glm-5.3),模型固定不换族;新增 assessQuality 质量门(尺寸线直读/置信度/厨房/柜体数计分),低质量不出版(退出码 3)并给出该问销售要什么;PROMPT_MEASURE 纳入实战维度推断规则
  • C fpc auto 一键流水线:附件→识别→报价→Playwright 触屏回归→群发公告 Markdown→JSON 摘要
  • D 回归测试 4 项新增(含 HTML 内嵌口径一致性锁死)
  • SKILL.md 同步更新,明确模型纪律

四、纪律(新增)

  1. 模型纪律:VLM 只用 GLM 族;image_tokens=0 是网关统计 bug,不是失败信号;失败判定以"回复是否与图片内容相关"为准
  2. 质量纪律:低置信度报价不出街;宁可问销售要尺寸,不发默认值垃圾单
  3. 流程纪律:实战走 fpc auto,不许人肉拆步骤;群发走 send_wecom_doc.py