修复 PDD 尺码采集混入建议说明和价格导致采购精确匹配失败 #160

Open
opened 2026-08-31 08:56:01 +08:00 by ila · 3 comments
Owner

本正文为最终版(2026-08-31 四次修订)。 已按全栈复核补充存量盘点、固定根因位置、确定尺码价格处理、收紧清洗规则、修正服务端部分接受语义与自相矛盾的验收项;本次补充 SKU Complete 基准、contains 现存假阳性漏洞,并更正文档影响。历史评论仅作决策记录,如与本正文冲突以本正文为准。

原始需求摘要

来源:用户于 2026-08-31 反馈 CG-28 采购失败并提示“再次执行仍未能精确选择商品规格”,人工检查 PDD 规格面板存在“黑色、XL”。用户确认按分析建议建工单,并于同日确认按复核建议修订本工单。

目的:修复 PDD 规格采集把尺码价格混入规格名称,导致采购严格匹配失败。

已核实事实(2026-08-31 复核数据库与代码)

  1. CG-28:状态 failed、错误码 PURCHASE_SPEC_NOT_MATCHED、目标 黑色 / XL、固化映射 黑色 / XL【建议121-140斤】 ¥15.78、来源 ai_match。
  2. PDD 商品 717778239538(内部 ID 6425)当前档案:颜色 酒红色 / 卡其色 / 黑色 干净且价格保存在 priceCent;尺码 7 个值全部形如 XL【建议121-140斤】 ¥15.78。
  3. AI 未自由改写,只是从被污染候选中返回了唯一正确项;Agent 严格相等匹配行为正确。
  4. 存量污染不止一个商品:含 size 维度的 PDD 商品共 1744 个,其中 15 个的尺码值含货币符号。污染样式包含 XL【建议121-140斤】 ¥15.78 和 100cm ¥3.69 两类,共同点是价格位于文本尾部。
  5. 虾皮商品规格映射被污染数为 0;被污染的采购任务仅 CG-28 一条。
  6. 根因位置具体在采集侧尺码取值:PddProductDetailCollector.kt 解析阶段把节点 label(必要时取首个非空后代 label)整体作为 VisibleSpecValue.text。颜色已有独立价格提取写入 priceCent,尺码没有对等的名称与价格分离。
  7. 服务端存在硬约束:dimension.Role != "color" 时 PriceCent 必须为空,因此尺码价格当前无处存放。
  8. 服务端当前不校验规格值文本:SubmitResult → persistResult → resultProductSpecs → applyResultToProduct 全链路把 dimension.Values 原样写入档案,没有价格文本检查。只改 Android 而服务端不设防,旧版 Agent 仍会写入污染值。
  9. persistResult 写 SKU 时存在零值外键风险:id := valueIDs[key][value],维度被丢弃后取零值 0,会写出 DimensionValueID = 0 的孤儿行或触发外键失败。
  10. 同处的 Complete: len(input.Specs) == len(valueIDs) 依赖维度总数:丢弃一个维度会使 len(valueIDs) 减一,导致原本不完整的 SKU 被误标为完整。
  11. 采购端存在现存 contains 假阳性漏洞:isExactSpecSelected 与 verifySummary 均使用 selectedSummary?.contains(target)。目标为 XL 而摘要为 已选 黑色 2XL 时,contains 返回 true,会把 2XL 已选误判为 XL 已选,直接违反“不猜测、不选相近候选”的安全边界。

目标

  1. 采集尺码时剥离尾部价格,保存稳定业务值。
  2. 服务端阻止含价格文本的规格值进入可采购候选,且不产生看似完整实则缺规格的档案。
  3. 维持并修复 Agent 严格规格匹配,消除 contains 子串误判。
  4. 提供受影响商品只读清单,由人工按清单重新采集。

非目标

  • 不修改 CG-28 已固化快照或任何历史审计事实。
  • 不直接改数据库绕过重新采集,不做自动批量清洗。
  • 不新增尺码价格字段,不放宽服务端 priceCent 只允许颜色的校验。
  • 不改变 AI 置信度、自动确认门槛或候选必须严格属于当前档案的规则。
  • 不增加 OCR/VLM,不创建订单,不执行支付。
  • 不顺带修复其他采集或采购问题。

固定实施方案

一、清洗规则(最小安全集)

只剥离位于字符串尾部的价格片段:¥ 或 ¥ 加数字(可含一至两位小数),及其前置空白。

  • 本单不剥离中括号说明。 4XL 160-170斤、黑色+白色【纯棉两件装】 简约亲肤 这类文本中,括号或体重区间是规格身份的一部分;过度剥离会导致 4XL 160-170斤 与其他值冲突,制造新的匹配失败甚至误选。
  • 剥离后去除首尾空白。
  • 剥离后为空、或与同维度另一个值重复时,视为无法安全清洗,不得写入。
  • 已知的 15 个受影响商品和 CG-28 全部属于尾部价格形态,该规则足以覆盖。

二、Android 采集

  • 改动限定在尺码规格取值路径,不改动通用页面解析、颜色提取、标题、价格、评价和面板判定等既有稳定逻辑。
  • 尺码价格按上节规则剥离后直接丢弃,不上送、不落库。
  • 保持当前可点击性、可用性、动态行发现和稳定终止逻辑不变。
  • XL / 2XL / 3XL / 4XL 必须保持互相独立,禁止使用简单 contains 截断。

三、服务端防线

  • 采集结果落库前校验规格值。
  • 同一维度只要出现无法安全清洗的值,该维度整体不更新,计入 missing,任务标记 completed_partial。
  • 禁止同维度部分接受:残缺候选会让 AI 在不完整集合中“成功”匹配,比明显失败更危险。
  • 不得用污染值覆盖已有可用档案。
  • 不修改共享 API 字段形状;新增的稳定 missing 值按“文档影响”一节同步契约与 Wiki。

四、存量盘点

  • 增加只读盘点,输出受影响 PDD 商品 ID 清单(当前为 15 个),回写本工单。
  • 不执行自动清洗,由人工按清单重新采集。仅重新采集 717778239538 无法覆盖其余 14 个,下次采购会以同样方式失败。

五、采购端

  • 保持严格相等选择与选中态复核。
  • 增加回归测试,证明目标 XL 不会匹配 2XL、3XL,且不依赖包含匹配。

六、CG-28 恢复路径

  • 实施前必须先确认虾皮侧该商品的目标尺码文本。若虾皮目标本身是带说明的文本,仅清洗 PDD 侧仍可能匹配不上。
  • 清洗并重新采集后,通过既有采购重试路径创建新任务,不手工改库、不修改终态失败任务。
  • 若 #148 的匹配工作项已处于 manual_required,由 Admin 重新入队或人工选择规格;不绕过既有门禁。

七、双端规范化、关联数据与最终复核补充

1. 固定 Android 与 Server 的规范化顺序

  • Android 与 Server 必须实现相同语义的“尾部价格规范化”,不能一端剥离、另一端只拒绝。
  • Server 收到结果后,先对尺码值执行与 Android 相同的安全尾价剥离,再进行空值、同维度重复、残留货币符号和非法值检查;完成规范化后才进入现有结果契约校验。
  • 尾价满足本工单最小安全集时,Server 规范化后接受,以兼容尚未升级但仍可能提交该形态的旧 Agent。
  • 剥离后为空、规范化后同维度重复、仍残留货币/价格文本或不能由最小安全集确定时,判定该维度无法安全规范化,整维拒绝。
  • Android 与 Server 应分别用同一组契约样本测试,避免正则或 Unicode 空白处理产生端间差异;不得在任一端扩展为模糊规格归一化。

2. 固定被拒维度与关联结果的原子处理

  • Server 的规范化必须发生在现有 ResultRequest 完整性校验之前。
  • 某维度被整维拒绝时,必须同时丢弃所有引用该维度的 SKU;不得只从 SKU.specs 删除该键后保留残缺组合,也不得因此制造重复 SKU。
  • 若被拒维度为 color,还必须同时丢弃 colorPrices 以及所有引用该颜色维度的 SKU。
  • persistResult 的零值外键必须被消除:不得再出现 DimensionValueID = 0 的 CollectionSKUValue;任一 SKU 引用到已被丢弃维度时整条 SKU 丢弃,而不是写入零值关联。
  • SKU 完整性基准固定为“被拒前的维度总数”:现有 Complete: len(input.Specs) == len(valueIDs) 会因维度被丢弃而缩小分母,把原本不完整的 SKU 误标为完整。实施必须使用原始维度总数计算 Complete,或在维度被拒时直接判定相关 SKU 不完整,禁止出现“维度变少反而更完整”的结果。
  • 经上述原子裁剪后的结果再执行现有规格引用、SKU 唯一性和颜色价格引用校验。
  • 任务强制收敛为 completed_partial,并追加稳定 missing 项:spec_dimension_invalid:<dimensionKey>。服务端不得接受客户端仍声明 completed 的完整状态。
  • 对已有商品执行 partial 合并时,被拒维度不得以污染值覆盖旧档案;任务详情必须如实展示该维度缺失。存量污染档案仍按只读清单重新采集,不通过本逻辑批量改写。

3. 固定长候选与短“已选摘要”的安全复核

现存缺陷(本工单必须修复,不只是复核方式不足):PurchaseRehearsalExecutor.kt 的 isExactSpecSelected 与 verifySummary 都以 selectedSummary?.contains(target) 作为独立成立条件,存在双向问题:

  • 假阳性(安全漏洞):目标 XL、摘要 已选 黑色 2XL 时 contains 返回 true,会把 2XL 已选误判为 XL 已选。这直接违反“不猜测、不选相近候选”的永久边界,必须修复。
  • 假阴性:尾价剥离后候选仍为 XL【建议121-140斤】,而摘要只显示 XL 时 contains 返回 false,会阻断 CG-28 的正常恢复。

固定要求:

  • isExactSpecSelected 与 verifySummary 必须同时修改,不得只改其一;两者不得再把裸 contains 作为独立成立条件。
  • 规格定位仍只允许对规范化后的可选候选做严格相等匹配,禁止用 contains、前缀或相近文本寻找和点击候选。
  • 点击后优先使用重新抓取的同维度选项 selected / checked 状态,并对该选项应用同一尾价规范化后与目标严格相等,作为选中证据。
  • 若页面切换后不再暴露选项状态,只能使用能够无歧义解析出独立规格 token 的“已选”摘要进行复核;摘要 token 必须与目标的明确主规格 token 完全相等,并证明当前同维度不存在 XL / 2XL / 3XL 等歧义。不能退化为任意子串包含。
  • 无法取得上述任一明确证据时继续安全失败,不猜测已选成功。
  • 本工单必须覆盖“候选为 XL【建议121-140斤】、摘要仅为 XL”的成功样本,以及 XL 不得匹配 2XL / 3XL、存在重复主规格 token 时必须失败的反例。

4. 固定主规格 token 的唯一性边界

  • 主规格 token 只用于规格已经点击后的选中结果复核,不得用于定位、筛选或点击候选;候选定位仍必须使用规范化后的完整规格值严格相等。
  • XL、2XL、3XL、4XL 是彼此不同的独立 token,允许同时存在于同一尺码维度,不因相邻文本而视为歧义。
  • 只有多个不同候选映射到同一个主规格 token 时才判定为歧义,例如 XL【建议A】与 XL【建议B】同时映射为 XL;此时摘要只显示 XL 不足以证明选中了哪个候选,必须安全失败。
  • token 唯一性必须基于本次执行中、同一次已打开规格面板的新鲜候选集合判断;不得使用服务端历史商品档案、任务旧快照、上一次抓取或页面变化前的过期候选作为唯一性证据。
  • 若点击后页面不再暴露完整候选集合,允许使用点击前最后一次新鲜抓取的候选集合,但必须与本次点击目标、页面实例和执行阶段绑定,且点击期间未发生页面离开、面板重开或候选集合变化;否则证据失效并安全失败。
  • 主 token 提取必须是确定性的边界解析,并分别覆盖英文/数字尺码;不得通过任意子串、前缀、编辑距离或相似度生成 token。

数据、接口与迁移

  • 不新增数据库字段,不需要迁移。
  • 不改变现有 API 字段形状;新增稳定 missing 值 spec_dimension_invalid:<dimensionKey> 属于共享契约新增,按“文档影响”一节处理。
  • 历史任务、规格决策和失败记录保持不可变。

设计证据

  • 非 UI 缺陷,不要求交互原型。
  • 数据与流程设计以本正文及用户 2026-08-31 的三次确认为准。
  • 页面、导航、权限和用户交互不变。

验收标准

  • 样本中规格显示 XL【建议121-140斤】 ¥15.78 时,采集结果为 XL【建议121-140斤】,不含任何货币符号或价格数字。

  • 100cm ¥3.69 形态清洗为 100cm。

  • 中括号说明保持原样,4XL 160-170斤 类文本不被截断。

  • XL、2XL、3XL、4XL 相互独立,未被归一。

  • 清洗后为空或与同维度重复时判为无法清洗,不写入。

  • 同维度出现无法清洗的值时该维度整体不更新,计入 missing 且任务为 completed_partial;不产生部分接受的残缺档案。

  • 污染值不会覆盖已有可用档案。

  • 尺码价格被丢弃,未新增字段,服务端 priceCent 只允许颜色的校验未放宽。

  • AI 输入候选为清洁值,AI 仍只能返回候选集合中的精确值。

  • 采购端仍严格匹配,未引入包含或模糊点击。

  • 输出受影响 PDD 商品 ID 只读清单并回写工单;未执行自动批量清洗。

  • 重新采集 717778239538 后尺码候选不含货币符号,相邻尺码互相独立;具体保留文本以真机页面为准。

  • 已确认虾皮侧目标尺码文本,并说明清洗后能否精确匹配。

  • 通过既有重试路径生成新任务;未修改 CG-28 快照,未手工改库。

  • 未执行订单创建和支付;继续验证创建待付款订单需另行人工授权。

  • Server 对安全尾价执行与 Android 相同的规范化后再校验;旧 Agent 提交安全尾价样本也不会写入污染值。

  • 被拒维度与其关联 SKU 原子丢弃;color 被拒时 colorPrices 和相关 SKU 同步丢弃,未产生残缺或重复 SKU。

  • 不再产生 DimensionValueID = 0 的 CollectionSKUValue 记录。

  • 维度被丢弃时 SKU 的 Complete 以被拒前的维度总数为准,残缺 SKU 不会被误标为完整。

  • 服务端发现污染时强制 completed_partial,并记录稳定 missing 值 spec_dimension_invalid:<dimensionKey>。

  • 候选为 XL【建议121-140斤】 且“已选”摘要仅为 XL 时,可通过明确选中态或无歧义 token 安全复核。

  • 目标 XL、摘要为 已选 黑色 2XL 时必须判定为未选中,现存 contains 假阳性漏洞已消除。

  • isExactSpecSelected 与 verifySummary 均已修改,两处都不再把裸 contains 作为独立成立条件。

  • Android-Agent-API-Contract 已记录新增稳定 missing 值,并完成一轮 sync 与一轮 sync --check。

  • Android、Server 相关单元/契约测试与 APK 构建通过。

  • 同一面板同时存在 XL、2XL、3XL 时分别解析为不同 token,不误判歧义,也不发生子串误认。

  • 同一面板存在两个不同候选但主 token 均为 XL 时,短摘要 XL 必须判定证据不足并安全失败。

  • token 唯一性只使用本次执行同一规格面板的新鲜候选集合;旧档案、旧快照和页面变化前候选不能作为证据。

  • 摘要 token 仅参与点击后复核,规格定位和点击仍对完整规范化候选执行严格相等。

风险与回归

  • 过度清洗会误伤合法规格:已通过“只剥离尾部价格、不动中括号”把风险降到最低;实施不得扩大规则。
  • 不同商品规格节点结构不同:至少覆盖单行、带建议说明、带价格、XL/2XL/3XL、100cm 数字尺码和动态规格行。
  • 服务端部分接受会制造残缺候选:已固定为同维度整体不更新。
  • 修复 contains 时若退化为更宽松的 token 匹配,会重新引入相近候选误选:token 必须严格相等且证明同维度无歧义。
  • 不得弱化 Agent 不猜测、不点击相近候选及禁止支付边界。

文档影响

有长期文档影响。 §7.2 引入稳定 missing 值 spec_dimension_invalid:<dimensionKey>,属于 Agent 与服务端共享契约的新增稳定代码,必须:

  • 更新 Wiki Android-Agent-API-Contract,说明该 missing 值的含义、产生条件与客户端处理方式;
  • 按 Wiki-first 顺序先改线上页面并回读 revision,再执行一次 python dev_scripts/harness.py sync 和一次 sync --check,提交本地镜像并把页面与 revision 回写本工单。

其余部分恢复既有“采集规格是稳定业务值、采购只精确匹配”的已确认规则,不改变 API 字段形状、状态机或安全边界。

状态

待实施。

> **本正文为最终版(2026-08-31 四次修订)。** 已按全栈复核补充存量盘点、固定根因位置、确定尺码价格处理、收紧清洗规则、修正服务端部分接受语义与自相矛盾的验收项;本次补充 SKU `Complete` 基准、`contains` 现存假阳性漏洞,并更正文档影响。历史评论仅作决策记录,如与本正文冲突以本正文为准。 ## 原始需求摘要 来源:用户于 2026-08-31 反馈 CG-28 采购失败并提示“再次执行仍未能精确选择商品规格”,人工检查 PDD 规格面板存在“黑色、XL”。用户确认按分析建议建工单,并于同日确认按复核建议修订本工单。 目的:修复 PDD 规格采集把尺码价格混入规格名称,导致采购严格匹配失败。 ## 已核实事实(2026-08-31 复核数据库与代码) 1. CG-28:状态 `failed`、错误码 `PURCHASE_SPEC_NOT_MATCHED`、目标 `黑色 / XL`、固化映射 `黑色 / XL【建议121-140斤】 ¥15.78`、来源 `ai_match`。 2. PDD 商品 `717778239538`(内部 ID 6425)当前档案:颜色 `酒红色 / 卡其色 / 黑色` 干净且价格保存在 `priceCent`;尺码 7 个值全部形如 `XL【建议121-140斤】 ¥15.78`。 3. AI 未自由改写,只是从被污染候选中返回了唯一正确项;Agent 严格相等匹配行为正确。 4. **存量污染不止一个商品**:含 `size` 维度的 PDD 商品共 1744 个,其中 15 个的尺码值含货币符号。污染样式包含 `XL【建议121-140斤】 ¥15.78` 和 `100cm ¥3.69` 两类,共同点是**价格位于文本尾部**。 5. 虾皮商品规格映射被污染数为 0;被污染的采购任务仅 CG-28 一条。 6. 根因位置具体在采集侧尺码取值:`PddProductDetailCollector.kt` 解析阶段把节点 `label`(必要时取首个非空后代 label)整体作为 `VisibleSpecValue.text`。颜色已有独立价格提取写入 `priceCent`,尺码没有对等的名称与价格分离。 7. 服务端存在硬约束:`dimension.Role != "color"` 时 `PriceCent` 必须为空,因此尺码价格当前无处存放。 8. **服务端当前不校验规格值文本**:`SubmitResult → persistResult → resultProductSpecs → applyResultToProduct` 全链路把 `dimension.Values` 原样写入档案,没有价格文本检查。只改 Android 而服务端不设防,旧版 Agent 仍会写入污染值。 9. **`persistResult` 写 SKU 时存在零值外键风险**:`id := valueIDs[key][value]`,维度被丢弃后取零值 `0`,会写出 `DimensionValueID = 0` 的孤儿行或触发外键失败。 10. **同处的 `Complete: len(input.Specs) == len(valueIDs)` 依赖维度总数**:丢弃一个维度会使 `len(valueIDs)` 减一,导致原本不完整的 SKU 被误标为完整。 11. **采购端存在现存 `contains` 假阳性漏洞**:`isExactSpecSelected` 与 `verifySummary` 均使用 `selectedSummary?.contains(target)`。目标为 `XL` 而摘要为 `已选 黑色 2XL` 时,`contains` 返回 `true`,会把 `2XL` 已选误判为 `XL` 已选,直接违反“不猜测、不选相近候选”的安全边界。 ## 目标 1. 采集尺码时剥离尾部价格,保存稳定业务值。 2. 服务端阻止含价格文本的规格值进入可采购候选,且不产生看似完整实则缺规格的档案。 3. 维持并修复 Agent 严格规格匹配,消除 `contains` 子串误判。 4. 提供受影响商品只读清单,由人工按清单重新采集。 ## 非目标 - 不修改 CG-28 已固化快照或任何历史审计事实。 - 不直接改数据库绕过重新采集,不做自动批量清洗。 - **不新增尺码价格字段,不放宽服务端 `priceCent` 只允许颜色的校验**。 - 不改变 AI 置信度、自动确认门槛或候选必须严格属于当前档案的规则。 - 不增加 OCR/VLM,不创建订单,不执行支付。 - 不顺带修复其他采集或采购问题。 ## 固定实施方案 ### 一、清洗规则(最小安全集) **只剥离位于字符串尾部的价格片段**:`¥` 或 `¥` 加数字(可含一至两位小数),及其前置空白。 - **本单不剥离中括号说明。** `4XL 160-170斤`、`黑色+白色【纯棉两件装】 简约亲肤` 这类文本中,括号或体重区间是规格身份的一部分;过度剥离会导致 `4XL 160-170斤` 与其他值冲突,制造新的匹配失败甚至误选。 - 剥离后去除首尾空白。 - 剥离后为空、或与同维度另一个值重复时,视为**无法安全清洗**,不得写入。 - 已知的 15 个受影响商品和 CG-28 全部属于尾部价格形态,该规则足以覆盖。 ### 二、Android 采集 - 改动限定在尺码规格取值路径,不改动通用页面解析、颜色提取、标题、价格、评价和面板判定等既有稳定逻辑。 - 尺码价格按上节规则剥离后**直接丢弃**,不上送、不落库。 - 保持当前可点击性、可用性、动态行发现和稳定终止逻辑不变。 - `XL` / `2XL` / `3XL` / `4XL` 必须保持互相独立,禁止使用简单 contains 截断。 ### 三、服务端防线 - 采集结果落库前校验规格值。 - **同一维度只要出现无法安全清洗的值,该维度整体不更新**,计入 `missing`,任务标记 `completed_partial`。 - 禁止同维度部分接受:残缺候选会让 AI 在不完整集合中“成功”匹配,比明显失败更危险。 - 不得用污染值覆盖已有可用档案。 - 不修改共享 API 字段形状;新增的稳定 missing 值按“文档影响”一节同步契约与 Wiki。 ### 四、存量盘点 - 增加只读盘点,输出受影响 PDD 商品 ID 清单(当前为 15 个),回写本工单。 - **不执行自动清洗**,由人工按清单重新采集。仅重新采集 `717778239538` 无法覆盖其余 14 个,下次采购会以同样方式失败。 ### 五、采购端 - 保持严格相等选择与选中态复核。 - 增加回归测试,证明目标 `XL` 不会匹配 `2XL`、`3XL`,且不依赖包含匹配。 ### 六、CG-28 恢复路径 - **实施前必须先确认虾皮侧该商品的目标尺码文本**。若虾皮目标本身是带说明的文本,仅清洗 PDD 侧仍可能匹配不上。 - 清洗并重新采集后,通过**既有采购重试路径**创建新任务,不手工改库、不修改终态失败任务。 - 若 #148 的匹配工作项已处于 `manual_required`,由 Admin 重新入队或人工选择规格;不绕过既有门禁。 ### 七、双端规范化、关联数据与最终复核补充 #### 1. 固定 Android 与 Server 的规范化顺序 - Android 与 Server 必须实现相同语义的“尾部价格规范化”,不能一端剥离、另一端只拒绝。 - Server 收到结果后,先对尺码值执行与 Android 相同的安全尾价剥离,再进行空值、同维度重复、残留货币符号和非法值检查;完成规范化后才进入现有结果契约校验。 - 尾价满足本工单最小安全集时,Server 规范化后接受,以兼容尚未升级但仍可能提交该形态的旧 Agent。 - 剥离后为空、规范化后同维度重复、仍残留货币/价格文本或不能由最小安全集确定时,判定该维度无法安全规范化,整维拒绝。 - Android 与 Server 应分别用同一组契约样本测试,避免正则或 Unicode 空白处理产生端间差异;不得在任一端扩展为模糊规格归一化。 #### 2. 固定被拒维度与关联结果的原子处理 - Server 的规范化必须发生在现有 `ResultRequest` 完整性校验之前。 - 某维度被整维拒绝时,必须同时丢弃所有引用该维度的 SKU;不得只从 `SKU.specs` 删除该键后保留残缺组合,也不得因此制造重复 SKU。 - 若被拒维度为 `color`,还必须同时丢弃 `colorPrices` 以及所有引用该颜色维度的 SKU。 - **`persistResult` 的零值外键必须被消除**:不得再出现 `DimensionValueID = 0` 的 `CollectionSKUValue`;任一 SKU 引用到已被丢弃维度时整条 SKU 丢弃,而不是写入零值关联。 - **SKU 完整性基准固定为“被拒前的维度总数”**:现有 `Complete: len(input.Specs) == len(valueIDs)` 会因维度被丢弃而缩小分母,把原本不完整的 SKU 误标为完整。实施必须使用原始维度总数计算 `Complete`,或在维度被拒时直接判定相关 SKU 不完整,禁止出现“维度变少反而更完整”的结果。 - 经上述原子裁剪后的结果再执行现有规格引用、SKU 唯一性和颜色价格引用校验。 - 任务强制收敛为 `completed_partial`,并追加稳定 missing 项:`spec_dimension_invalid:<dimensionKey>`。服务端不得接受客户端仍声明 `completed` 的完整状态。 - 对已有商品执行 partial 合并时,被拒维度不得以污染值覆盖旧档案;任务详情必须如实展示该维度缺失。存量污染档案仍按只读清单重新采集,不通过本逻辑批量改写。 #### 3. 固定长候选与短“已选摘要”的安全复核 **现存缺陷(本工单必须修复,不只是复核方式不足)**:`PurchaseRehearsalExecutor.kt` 的 `isExactSpecSelected` 与 `verifySummary` 都以 `selectedSummary?.contains(target)` 作为独立成立条件,存在双向问题: - **假阳性(安全漏洞)**:目标 `XL`、摘要 `已选 黑色 2XL` 时 `contains` 返回 `true`,会把 `2XL` 已选误判为 `XL` 已选。这直接违反“不猜测、不选相近候选”的永久边界,必须修复。 - **假阴性**:尾价剥离后候选仍为 `XL【建议121-140斤】`,而摘要只显示 `XL` 时 `contains` 返回 `false`,会阻断 CG-28 的正常恢复。 固定要求: - **`isExactSpecSelected` 与 `verifySummary` 必须同时修改**,不得只改其一;两者不得再把裸 `contains` 作为独立成立条件。 - 规格定位仍只允许对规范化后的可选候选做严格相等匹配,禁止用 `contains`、前缀或相近文本寻找和点击候选。 - 点击后优先使用重新抓取的同维度选项 `selected` / `checked` 状态,并对该选项应用同一尾价规范化后与目标严格相等,作为选中证据。 - 若页面切换后不再暴露选项状态,只能使用能够无歧义解析出独立规格 token 的“已选”摘要进行复核;摘要 token 必须与目标的明确主规格 token 完全相等,并证明当前同维度不存在 `XL` / `2XL` / `3XL` 等歧义。不能退化为任意子串包含。 - 无法取得上述任一明确证据时继续安全失败,不猜测已选成功。 - 本工单必须覆盖“候选为 `XL【建议121-140斤】`、摘要仅为 `XL`”的成功样本,以及 `XL` 不得匹配 `2XL` / `3XL`、存在重复主规格 token 时必须失败的反例。 #### 4. 固定主规格 token 的唯一性边界 - 主规格 token 只用于规格已经点击后的选中结果复核,不得用于定位、筛选或点击候选;候选定位仍必须使用规范化后的完整规格值严格相等。 - XL、2XL、3XL、4XL 是彼此不同的独立 token,允许同时存在于同一尺码维度,不因相邻文本而视为歧义。 - 只有多个不同候选映射到同一个主规格 token 时才判定为歧义,例如 XL【建议A】与 XL【建议B】同时映射为 XL;此时摘要只显示 XL 不足以证明选中了哪个候选,必须安全失败。 - token 唯一性必须基于本次执行中、同一次已打开规格面板的新鲜候选集合判断;不得使用服务端历史商品档案、任务旧快照、上一次抓取或页面变化前的过期候选作为唯一性证据。 - 若点击后页面不再暴露完整候选集合,允许使用点击前最后一次新鲜抓取的候选集合,但必须与本次点击目标、页面实例和执行阶段绑定,且点击期间未发生页面离开、面板重开或候选集合变化;否则证据失效并安全失败。 - 主 token 提取必须是确定性的边界解析,并分别覆盖英文/数字尺码;不得通过任意子串、前缀、编辑距离或相似度生成 token。 ## 数据、接口与迁移 - 不新增数据库字段,不需要迁移。 - 不改变现有 API 字段形状;新增稳定 missing 值 `spec_dimension_invalid:<dimensionKey>` 属于共享契约新增,按“文档影响”一节处理。 - 历史任务、规格决策和失败记录保持不可变。 ## 设计证据 - 非 UI 缺陷,不要求交互原型。 - 数据与流程设计以本正文及用户 2026-08-31 的三次确认为准。 - 页面、导航、权限和用户交互不变。 ## 验收标准 - [ ] 样本中规格显示 `XL【建议121-140斤】 ¥15.78` 时,采集结果为 `XL【建议121-140斤】`,不含任何货币符号或价格数字。 - [ ] `100cm ¥3.69` 形态清洗为 `100cm`。 - [ ] 中括号说明保持原样,`4XL 160-170斤` 类文本不被截断。 - [ ] `XL`、`2XL`、`3XL`、`4XL` 相互独立,未被归一。 - [ ] 清洗后为空或与同维度重复时判为无法清洗,不写入。 - [ ] 同维度出现无法清洗的值时该维度整体不更新,计入 `missing` 且任务为 `completed_partial`;不产生部分接受的残缺档案。 - [ ] 污染值不会覆盖已有可用档案。 - [ ] 尺码价格被丢弃,未新增字段,服务端 `priceCent` 只允许颜色的校验未放宽。 - [ ] AI 输入候选为清洁值,AI 仍只能返回候选集合中的精确值。 - [ ] 采购端仍严格匹配,未引入包含或模糊点击。 - [ ] 输出受影响 PDD 商品 ID 只读清单并回写工单;未执行自动批量清洗。 - [ ] 重新采集 `717778239538` 后尺码候选不含货币符号,相邻尺码互相独立;具体保留文本以真机页面为准。 - [ ] 已确认虾皮侧目标尺码文本,并说明清洗后能否精确匹配。 - [ ] 通过既有重试路径生成新任务;未修改 CG-28 快照,未手工改库。 - [ ] 未执行订单创建和支付;继续验证创建待付款订单需另行人工授权。 - [ ] Server 对安全尾价执行与 Android 相同的规范化后再校验;旧 Agent 提交安全尾价样本也不会写入污染值。 - [ ] 被拒维度与其关联 SKU 原子丢弃;`color` 被拒时 `colorPrices` 和相关 SKU 同步丢弃,未产生残缺或重复 SKU。 - [ ] 不再产生 `DimensionValueID = 0` 的 `CollectionSKUValue` 记录。 - [ ] 维度被丢弃时 SKU 的 `Complete` 以被拒前的维度总数为准,残缺 SKU 不会被误标为完整。 - [ ] 服务端发现污染时强制 `completed_partial`,并记录稳定 missing 值 `spec_dimension_invalid:<dimensionKey>`。 - [ ] 候选为 `XL【建议121-140斤】` 且“已选”摘要仅为 `XL` 时,可通过明确选中态或无歧义 token 安全复核。 - [ ] 目标 `XL`、摘要为 `已选 黑色 2XL` 时必须判定为未选中,现存 `contains` 假阳性漏洞已消除。 - [ ] `isExactSpecSelected` 与 `verifySummary` 均已修改,两处都不再把裸 `contains` 作为独立成立条件。 - [ ] `Android-Agent-API-Contract` 已记录新增稳定 missing 值,并完成一轮 `sync` 与一轮 `sync --check`。 - [ ] Android、Server 相关单元/契约测试与 APK 构建通过。 - [ ] 同一面板同时存在 XL、2XL、3XL 时分别解析为不同 token,不误判歧义,也不发生子串误认。 - [ ] 同一面板存在两个不同候选但主 token 均为 XL 时,短摘要 XL 必须判定证据不足并安全失败。 - [ ] token 唯一性只使用本次执行同一规格面板的新鲜候选集合;旧档案、旧快照和页面变化前候选不能作为证据。 - [ ] 摘要 token 仅参与点击后复核,规格定位和点击仍对完整规范化候选执行严格相等。 ## 风险与回归 - 过度清洗会误伤合法规格:已通过“只剥离尾部价格、不动中括号”把风险降到最低;实施不得扩大规则。 - 不同商品规格节点结构不同:至少覆盖单行、带建议说明、带价格、`XL/2XL/3XL`、`100cm` 数字尺码和动态规格行。 - 服务端部分接受会制造残缺候选:已固定为同维度整体不更新。 - 修复 `contains` 时若退化为更宽松的 token 匹配,会重新引入相近候选误选:token 必须严格相等且证明同维度无歧义。 - 不得弱化 Agent 不猜测、不点击相近候选及禁止支付边界。 ## 文档影响 **有长期文档影响。** §7.2 引入稳定 missing 值 `spec_dimension_invalid:<dimensionKey>`,属于 Agent 与服务端共享契约的新增稳定代码,必须: - 更新 Wiki `Android-Agent-API-Contract`,说明该 missing 值的含义、产生条件与客户端处理方式; - 按 Wiki-first 顺序先改线上页面并回读 revision,再执行一次 `python dev_scripts/harness.py sync` 和一次 `sync --check`,提交本地镜像并把页面与 revision 回写本工单。 其余部分恢复既有“采集规格是稳定业务值、采购只精确匹配”的已确认规则,不改变 API 字段形状、状态机或安全边界。 ## 状态 待实施。
Author
Owner

开始实施 #160(基线:5d85625)。

范围按四次修订最终正文执行:

  1. Android 与 Server 使用同语义的尺码尾价规范化;
  2. Server 在结果校验前完成整维拒绝、关联 SKU/colorPrices 原子裁剪、completed_partial 与稳定 missing;
  3. 修复采购选中复核中的裸 contains 假阳性,并实现仅用于点击后复核的新鲜候选 token 唯一性边界;
  4. 输出存量污染只读清单并更新 Android Agent API 契约。

安全边界:不修改 CG-28 历史快照,不自动清洗数据库,不创建订单,不支付;真机只验证到订单创建前,若需要继续创建待付款订单将另行等待授权。

开始实施 #160(基线:5d85625)。 范围按四次修订最终正文执行: 1. Android 与 Server 使用同语义的尺码尾价规范化; 2. Server 在结果校验前完成整维拒绝、关联 SKU/colorPrices 原子裁剪、completed_partial 与稳定 missing; 3. 修复采购选中复核中的裸 contains 假阳性,并实现仅用于点击后复核的新鲜候选 token 唯一性边界; 4. 输出存量污染只读清单并更新 Android Agent API 契约。 安全边界:不修改 CG-28 历史快照,不自动清洗数据库,不创建订单,不支付;真机只验证到订单创建前,若需要继续创建待付款订单将另行等待授权。
Author
Owner

实施中只读盘点纠正(2026-08-31,本机 goauto 当前数据库):

  • 含 size 维度商品:1744 个,size 值总数:3323。
  • size 值含 ¥/¥:3 个商品、17 个值:
    • PDD ID 6425 / goods_id 717778239538
    • PDD ID 6777 / goods_id 757123298435
    • PDD ID 8473 / goods_id 925227518638
  • 正文先前的“15 个商品”来自“商品存在 size 维度,同时整份 specs_json 任意位置含货币符号”的宽查询;其中另 12 个实际是 color 值含价格,不是 size 值污染。
  • CG-28 来源已再次核对:purchase target=黑色/XL;SYB target=黑色/XL;parse_status=success;manually_confirmed=false。

范围处理:

  • #160 按已确认正文继续只修改 Android 尺码取值;不擅自扩展颜色采集逻辑。
  • Server 防线会对任何仍含货币符号、无法安全规范化的维度整维拒绝并 completed_partial,避免新增污染。
  • 3 个尺码污染商品可在本单修复后重新采集纠正。
  • 其余 12 个颜色污染商品仅重新采集不会被本单 Android 尺码修复清洁,需作为相邻问题另行确认范围;本单不会自动清洗或改写它们。

完整15个只读商品清单仍会在最终待验收回写中提供,并明确标注 size/color 分类。

实施中只读盘点纠正(2026-08-31,本机 goauto 当前数据库): - 含 size 维度商品:1744 个,size 值总数:3323。 - size 值含 ¥/¥:3 个商品、17 个值: - PDD ID 6425 / goods_id 717778239538 - PDD ID 6777 / goods_id 757123298435 - PDD ID 8473 / goods_id 925227518638 - 正文先前的“15 个商品”来自“商品存在 size 维度,同时整份 specs_json 任意位置含货币符号”的宽查询;其中另 12 个实际是 color 值含价格,不是 size 值污染。 - CG-28 来源已再次核对:purchase target=黑色/XL;SYB target=黑色/XL;parse_status=success;manually_confirmed=false。 范围处理: - #160 按已确认正文继续只修改 Android 尺码取值;不擅自扩展颜色采集逻辑。 - Server 防线会对任何仍含货币符号、无法安全规范化的维度整维拒绝并 completed_partial,避免新增污染。 - 3 个尺码污染商品可在本单修复后重新采集纠正。 - 其余 12 个颜色污染商品仅重新采集不会被本单 Android 尺码修复清洁,需作为相邻问题另行确认范围;本单不会自动清洗或改写它们。 完整15个只读商品清单仍会在最终待验收回写中提供,并明确标注 size/color 分类。
Author
Owner

实施完成,等待验收(2026-08-31)

当前会话未暴露 Gitea MCP 工具,按仓库规则回退项目根目录安全配置与 Gitea API 完成本评论。

实现

  • Android 采集:只对 size 剥离位于文本尾部的 ¥ / ¥ + 数字价格;保留括号建议、体重区间等规格身份文本,同时保留原始文本用于碰撞检查。
  • Android 安全收敛:规范化后为空、残留货币符号,或两个原始值折叠成同一规格值时,整条尺码维度拒绝,移除关联 SKU,结果强制 completed_partial,并加入 spec_dimension_invalid:size。
  • Server 最终防线:提交结果在完整性校验前执行同语义规范化;任何维度存在空值、规范化重复或残留货币符号时整维拒绝,原子丢弃依赖 SKU;color 被拒时同步丢弃 colorPrices;禁止写入 dimension_value_id=0。
  • 采购规格确认:删除 selectedSummary.contains(target) 模糊命中。优先使用当前节点 selected/checked 精确证据;仅在当前候选中主 token 唯一且摘要为独立完整 token 时允许短摘要回退,2XL 不再误判为 XL。
  • 长期契约已更新至 Wiki Android-Agent-API-Contract,在线回读 revision:5caf37fa5589e63e128e2beb9bc6acc24edf1cb1;已执行一次 sync 和一次 sync --check。

提交并已推送:67f3a74 fix(goauto): safely normalize specification values (#160)

自动化验证

  • Android 定向测试:SpecValueNormalizerTest、PddProductDetailCollectorTest、PurchaseRehearsalExecutorTest 通过。
  • Android 全量单测及 APK 构建:.\gradlew.bat testDebugUnitTest assembleDebug,BUILD SUCCESSFUL。
  • Server 定向测试:go test ./app/goauto/task 通过。
  • Server 全量测试与构建:go test ./...、go build ./... 通过。
  • DevHarness:python dev_scripts/harness.py check --strict 通过。
  • Wiki 镜像一致性:python dev_scripts/harness.py sync --check 通过。
  • git diff --check 通过。
  • 仓库总验证 .\scripts\verify.ps1 -Component all 中 Server 通过;Web lint 被未修改的 web/src/views/goauto/purchase-tasks/index.vue 既有 24 个 Tab/空格错误阻断。#160 未修改 Web,按范围未顺手修复。

只读存量盘点

  • 含 size 维度:1744 个商品、3323 个尺码值。
  • size 尾价污染:3 个商品、17 个尺码值:
    • PDD 商品记录 6425 / goods 717778239538:7 个(S–4XL 尾价)。
    • 6777 / 757123298435:3 个套餐尺码值(尾部 ¥5.69)。
    • 8473 / 925227518638:7 个(100cm–160cm 尾价)。
  • 另外 12 个商品是 color 维度货币污染,不属于 Android 本次“尺码尾价剥离”范围,但 Server 新防线会阻止后续污染结果入库:
    • 9/378666384863、1563/339317949086、3310/474624457919、5118/607234829804
    • 5426/625761667128、6365/712088396137、6957/7828258098、8061/960129324986
    • 8190/966564660291、8466/99584550593、8471/992229024823、8472/993585204674
  • CG-28 源数据确认采购/SYB 目标为 黑色 / XL,parse_status=success,manually_confirmed=false。

明确边界 / 待人工验收

  • 无数据库迁移;未自动清洗或修改任何历史商品/任务数据。
  • 未修改、重置或重试 CG-28;未创建订单、未执行付款。
  • 本轮未安装 APK、未做真机重新采集。需在同一设备、当前提交构建和同一规则快照下,重新采集 goods 717778239538,并验证采购规格 黑色 / XL 不再被 2XL 或长标签摘要误判。
  • 工单保持打开,等待人工验收。
## 实施完成,等待验收(2026-08-31) > 当前会话未暴露 Gitea MCP 工具,按仓库规则回退项目根目录安全配置与 Gitea API 完成本评论。 ### 实现 - Android 采集:只对 `size` 剥离位于文本尾部的 `¥ / ¥ + 数字价格`;保留括号建议、体重区间等规格身份文本,同时保留原始文本用于碰撞检查。 - Android 安全收敛:规范化后为空、残留货币符号,或两个原始值折叠成同一规格值时,整条尺码维度拒绝,移除关联 SKU,结果强制 `completed_partial`,并加入 `spec_dimension_invalid:size`。 - Server 最终防线:提交结果在完整性校验前执行同语义规范化;任何维度存在空值、规范化重复或残留货币符号时整维拒绝,原子丢弃依赖 SKU;color 被拒时同步丢弃 colorPrices;禁止写入 `dimension_value_id=0`。 - 采购规格确认:删除 `selectedSummary.contains(target)` 模糊命中。优先使用当前节点 selected/checked 精确证据;仅在当前候选中主 token 唯一且摘要为独立完整 token 时允许短摘要回退,`2XL` 不再误判为 `XL`。 - 长期契约已更新至 Wiki `Android-Agent-API-Contract`,在线回读 revision:`5caf37fa5589e63e128e2beb9bc6acc24edf1cb1`;已执行一次 `sync` 和一次 `sync --check`。 提交并已推送:`67f3a74 fix(goauto): safely normalize specification values (#160)` ### 自动化验证 - Android 定向测试:`SpecValueNormalizerTest`、`PddProductDetailCollectorTest`、`PurchaseRehearsalExecutorTest` 通过。 - Android 全量单测及 APK 构建:`.\gradlew.bat testDebugUnitTest assembleDebug`,BUILD SUCCESSFUL。 - Server 定向测试:`go test ./app/goauto/task` 通过。 - Server 全量测试与构建:`go test ./...`、`go build ./...` 通过。 - DevHarness:`python dev_scripts/harness.py check --strict` 通过。 - Wiki 镜像一致性:`python dev_scripts/harness.py sync --check` 通过。 - `git diff --check` 通过。 - 仓库总验证 `.\scripts\verify.ps1 -Component all` 中 Server 通过;Web lint 被未修改的 `web/src/views/goauto/purchase-tasks/index.vue` 既有 24 个 Tab/空格错误阻断。#160 未修改 Web,按范围未顺手修复。 ### 只读存量盘点 - 含 size 维度:1744 个商品、3323 个尺码值。 - size 尾价污染:3 个商品、17 个尺码值: - PDD 商品记录 6425 / goods `717778239538`:7 个(S–4XL 尾价)。 - 6777 / `757123298435`:3 个套餐尺码值(尾部 `¥5.69`)。 - 8473 / `925227518638`:7 个(100cm–160cm 尾价)。 - 另外 12 个商品是 color 维度货币污染,不属于 Android 本次“尺码尾价剥离”范围,但 Server 新防线会阻止后续污染结果入库: - 9/`378666384863`、1563/`339317949086`、3310/`474624457919`、5118/`607234829804` - 5426/`625761667128`、6365/`712088396137`、6957/`7828258098`、8061/`960129324986` - 8190/`966564660291`、8466/`99584550593`、8471/`992229024823`、8472/`993585204674` - CG-28 源数据确认采购/SYB 目标为 `黑色 / XL`,`parse_status=success`,`manually_confirmed=false`。 ### 明确边界 / 待人工验收 - 无数据库迁移;未自动清洗或修改任何历史商品/任务数据。 - 未修改、重置或重试 CG-28;未创建订单、未执行付款。 - 本轮未安装 APK、未做真机重新采集。需在同一设备、当前提交构建和同一规则快照下,重新采集 goods `717778239538`,并验证采购规格 `黑色 / XL` 不再被 `2XL` 或长标签摘要误判。 - 工单保持打开,等待人工验收。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#160