# 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.yaml` 的 `model.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-flash`、`glm-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