feat(server+web): SYB 同步按 variationSku 过滤商品(结构判据 + 可配置关键词) #269

Closed
opened 2026-09-11 14:55:21 +08:00 by ila · 3 comments
Owner

原始需求

来源:用户,2026-09-11 会话。

现在从syb同步商品信息后需要过滤掉一些商品,不入库,规则如下:去掉variationSku字段包含"-"和"#"的商品,去掉包含以下文字的商品:CVC短袖220斤 / 180克純棉圓領短袖 / 拼色純棉短袖 / 300斤牛奶絲圓領 / 印花大學T / 印花帽T

先排除"-"和"#",再排除包含后面字符串的。这样就能够排除大部分,不需要的。

空 variationSku不过滤。

线上数据核验(2026-09-11)

只读查询线上库 goauto(13010 行 syb_product,7586 张货运单,2026-08-20 ~ 2026-09-11)。

规则1 命中分布

条件 命中
含 # 8498
含 - 6392
两者都含(AND) 6386
任一(OR) 8504(65%)
空串 2724(21%)

命中样本全部是「档口-供应商#货号」格式:新寮-雅伊阁大码女装#301、广达-爆款大码女装#9018、山湖-金顺大码女装#2797#(&13)。
未命中样本是自有编码:PDD0582、my238、le171、D256437。

结论:该规则的语义是排除线下档口直供、保留可走 PDD 采购的商品,65% 被过滤是预期结果。

[必须] 按 OR 实现。AND 只命中 6386,会漏掉 DD#004、300斤牛奶絲圓領#A057 等 2118 行。

规则2 命中分布

关键词 in productTitle in variationSku 规则1 之外净增
CVC短袖220斤 0 213 0
180克純棉圓領短袖 0 923 0
300斤牛奶絲圓領 0 358 0
印花大學T 0 12 0
印花帽T 0 6 0
拼色純棉短袖 0 0 0

[必须] 六条关键词只匹配 variationSku,不匹配 productTitle(标题实测 0 命中)。

[必须] 六条在当前数据上净增 0,全部已被规则1 覆盖。规则2 的价值是前瞻兜底:档口若改用不含 # 的编码,由它接住。因此必须可配置且可观测命中数。

拼色純棉短袖 当前 0 命中(拼色、純棉短袖 单独查亦为 0),仍写入初始数据。

保留集核验:过滤后 4506 行非空值无档口格式残留。1(295 行)与 引流款(9 行)经抽查为正常商品的人工编码,非脏数据。空 variationSku 2724 行分布在正常启用店铺且持续产生至今,按用户确认保留。

方案

过滤位置

server/app/goauto/sybimport/sync.go 明细循环,紧接既有店铺白名单检查之后、applyStockDetail 之前。沿用该处既有的按明细行 continue 模式(docs/12-syb-erp-interface.md:一张货运单可含多个商品,过滤粒度是明细行而非整单)。

规则1:代码判据

variationSku 含 - 或 # 即跳过。不做成配置——它是「什么叫档口货」的业务定义,不是参数;做成可开关的配置反而有被误关、导致 65% 无效数据涌入的风险。

规则2:独立表 + 管理页

新增 syb_product_filter,形态对齐既有 syb_shop(同为「SYB 同步按名单过滤 + 管理页维护」):

字段 说明
keyword 人看的原文
normalized_keyword 匹配键,唯一索引防重复添加
enabled 停用 ≠ 删除
last_hit_at / last_hit_count 上次同步命中时间与条数
note 添加理由,可空

软删除沿用 SYBShop 的 DeletedAt + DeletedFlag 哨兵写法(唯一索引与可空 deleted_at 共存)。

归一化复用 sybshop.Normalize(去首尾空格、全角折半角、字母小写)。用户清单前三条带尾随空格,CVC 可能为全角或小写,三者均需要。[必须] 不做简繁转换,SYB 数据为繁体,保持原样。

API 路由照 sybshop/router.go:GET 与写操作分开注册,写操作加 middleware.RequireRoleKey("admin")(go-admin 按 API 路径授权,不分开无法表达「配置仅管理员,其他角色只读」)。

管理页 web/src/views/goauto/syb-product-filters,菜单与 syb-shops 同组。

统计

[必须] 两条规则命中数分别记入 SyncReport,不得合并成单一「过滤总数」。

理由:规则2 今日净增 0。只有分开计数,才能在它由 0 变为非 0 时立刻看出档口已改编码格式。合并统计会使规则1 是否失效不可观测。

非目标

  • 不清理存量 8504 行档口数据(删生产数据属高风险,需单独建单确认)
  • 不过滤空 variationSku
  • 不把规则1 做成配置
  • 不匹配 productTitle

前置依赖

[必须] 新增管理页属于新页面,按 AGENTS.md「工单与设计证据双门禁」需先有可审阅原型并经用户确认,才能编写该页生产代码。 服务端过滤逻辑、表结构与 API 不受此门禁阻塞,可先行实施。

可并行:与 #241 / #242 无代码重叠,可并行。

子项目影响

  • server/:sybimport(过滤 + 统计)、models(新表)、新增 filter 包(API)、migrations
  • web/:新增管理页
  • android/:无

验收

  1. 同步时 variationSku 含 - 或 # 的明细不入库
  2. 命中已启用关键词的明细不入库;停用后不再生效
  3. 空 variationSku 正常入库
  4. 同步结果页分别显示规则1 与规则2 的命中条数
  5. 关键词重复添加被唯一索引拒绝;大小写/全角半角/首尾空格视为同一条
  6. 非管理员可读取列表,不能增删改
  7. 存量数据未被修改

验证

  • sybimport 过滤与统计单元测试(含 OR 而非 AND 的回归用例:DD#004、300斤牛奶絲圓領#A057 必须被过滤)
  • 归一化用例:尾随空格、全角 CVC、小写 cvc 归一为同一键;繁体不被转换
  • 表与 API 的 CRUD + 权限测试
  • .\scripts\verify.ps1 -Component all

风险

  • 过滤比例高:65% 明细将不再入库。属预期,但上线后首次同步须核对 SyncReport 命中数与本单核验数据(8504/13010)量级一致,不一致须停止排查。
  • 规则1 误配置风险:已通过「不做成配置」规避。
  • 新旧口径不一致:存量 8504 行保留,历史数据与新规则口径不同。本单不处理,在验收说明中记录。

文档影响

  • docs/12-syb-erp-interface.md:新增同步过滤规则章节(长期业务规则,需先改线上 Wiki 再 harness.py sync)
  • docs/08-agent-api-contract.md:新增 syb-product-filters 接口(同上)

[必须] 按 AGENTS.md,先修改线上 Wiki 页面并回读 revision,再运行一次 sync 与一次 sync --check 提交镜像;不得直接编辑本地镜像反向覆盖。


2026-09-11 范围变更:结构判据改为可配置(仅开关)

用户确认:- 与 # 也在同一管理页配置。

变更内容

原范围「规则1:代码判据 / 不做成配置」作废,替换为下述设计。原「不把规则1 做成配置」一条从非目标中移除。

syb_product_filter 增加 kind 列,取值 char | keyword,两类规则共用一张表与一套 API:

kind 内容 可新增 可删除 可启停
char -、# 两条,随迁移写入 否 否 是
keyword 关键词清单 是 是 是

[必须] API 必须在服务端拒绝对 kind=char 行的新增与删除,不得只靠前端隐藏按钮。

理由:档口编码的判据就是这两个字符。允许自由增删等于把业务定义交给误操作;判据本身需要变更(例如档口改用 / 或 @)属于需要评估的范围变化,应走新建工单,不应是一次点击。

[必须] 停用 kind=char 需二次确认

停用结构判据会使大量档口明细恢复入库(依本单核验:# 对应 8498 行,- 对应 6392 行)。前端必须弹出二次确认,文案示例:

停用「#」后,SYB 同步将不再过滤含 # 的档口商品。按最近一次同步数据估算,约 8498 条明细会恢复入库。确认停用?

[必须] 文案中的条数取自该行实时的 last_hit_count,不得写死。写死的数字在数据变化后会给出错误警告。

kind=keyword 的启停不需要二次确认(影响面为 0 行)。

[必须] 变更审计

kind=char 行的启停必须记录操作人与时间。关键词改错影响 0 行,结构判据改错影响约 65% 明细,两者审计要求不同。kind=keyword 沿用 updated_at 即可。

过滤实现相应调整

sybimport/sync.go 的过滤逻辑由写死判据改为查表:先应用全部启用的 kind=char 行(任一命中即跳过),再应用启用的 kind=keyword 行。执行顺序与统计口径不变,两类命中数仍须分别记入 SyncReport。

验收补充

  1. kind=char 行无法通过 API 新增或删除(前端隐藏 + 服务端拒绝,二者都要测)
  2. 停用 kind=char 弹出二次确认,且提示条数与该行 last_hit_count 一致
  3. 停用 # 后,含 # 的明细恢复入库;重新启用后恢复过滤
  4. kind=char 的启停操作可查到操作人与时间

风险补充

  • 误停用结构判据:影响面约 65%。已通过「禁止增删 + 二次确认 + 实时影响量提示 + 操作审计」四项控制。
  • 原「规则1 误配置风险已通过不做成配置规避」一条作废,改由上述四项控制。

设计证据

原型需相应重做,覆盖:结构过滤分段(两条 char 行、无增删按钮)、停用二次确认弹窗。旧版原型(screen 10001/10002/10003/10004/10240)作废,以重做后的版本为准。

## 原始需求 来源:用户,2026-09-11 会话。 > 现在从syb同步商品信息后需要过滤掉一些商品,不入库,规则如下:去掉variationSku字段包含"-"和"#"的商品,去掉包含以下文字的商品:CVC短袖220斤 / 180克純棉圓領短袖 / 拼色純棉短袖 / 300斤牛奶絲圓領 / 印花大學T / 印花帽T > 先排除"-"和"#",再排除包含后面字符串的。这样就能够排除大部分,不需要的。 > 空 variationSku不过滤。 ## 线上数据核验(2026-09-11) 只读查询线上库 `goauto`(13010 行 `syb_product`,7586 张货运单,2026-08-20 ~ 2026-09-11)。 **规则1 命中分布** | 条件 | 命中 | |---|---| | 含 `#` | 8498 | | 含 `-` | 6392 | | 两者都含(AND) | 6386 | | 任一(OR) | **8504(65%)** | | 空串 | 2724(21%) | 命中样本全部是「档口-供应商#货号」格式:`新寮-雅伊阁大码女装#301`、`广达-爆款大码女装#9018`、`山湖-金顺大码女装#2797#(&13)`。 未命中样本是自有编码:`PDD0582`、`my238`、`le171`、`D256437`。 结论:该规则的语义是**排除线下档口直供、保留可走 PDD 采购的商品**,65% 被过滤是预期结果。 `[必须]` 按 **OR** 实现。AND 只命中 6386,会漏掉 `DD#004`、`300斤牛奶絲圓領#A057` 等 2118 行。 **规则2 命中分布** | 关键词 | in productTitle | in variationSku | 规则1 之外净增 | |---|---|---|---| | CVC短袖220斤 | 0 | 213 | 0 | | 180克純棉圓領短袖 | 0 | 923 | 0 | | 300斤牛奶絲圓領 | 0 | 358 | 0 | | 印花大學T | 0 | 12 | 0 | | 印花帽T | 0 | 6 | 0 | | 拼色純棉短袖 | 0 | 0 | 0 | `[必须]` 六条关键词**只匹配 `variationSku`,不匹配 `productTitle`**(标题实测 0 命中)。 `[必须]` 六条在当前数据上**净增 0**,全部已被规则1 覆盖。规则2 的价值是**前瞻兜底**:档口若改用不含 `#` 的编码,由它接住。因此必须可配置且可观测命中数。 `拼色純棉短袖` 当前 0 命中(`拼色`、`純棉短袖` 单独查亦为 0),仍写入初始数据。 **保留集核验**:过滤后 4506 行非空值无档口格式残留。`1`(295 行)与 `引流款`(9 行)经抽查为正常商品的人工编码,非脏数据。空 `variationSku` 2724 行分布在正常启用店铺且持续产生至今,按用户确认**保留**。 ## 方案 ### 过滤位置 `server/app/goauto/sybimport/sync.go` 明细循环,紧接既有店铺白名单检查之后、`applyStockDetail` 之前。沿用该处既有的按明细行 `continue` 模式(`docs/12-syb-erp-interface.md`:一张货运单可含多个商品,过滤粒度是明细行而非整单)。 ### 规则1:代码判据 `variationSku` 含 `-` 或 `#` 即跳过。**不做成配置**——它是「什么叫档口货」的业务定义,不是参数;做成可开关的配置反而有被误关、导致 65% 无效数据涌入的风险。 ### 规则2:独立表 + 管理页 新增 `syb_product_filter`,形态对齐既有 `syb_shop`(同为「SYB 同步按名单过滤 + 管理页维护」): | 字段 | 说明 | |---|---| | `keyword` | 人看的原文 | | `normalized_keyword` | 匹配键,唯一索引防重复添加 | | `enabled` | 停用 ≠ 删除 | | `last_hit_at` / `last_hit_count` | 上次同步命中时间与条数 | | `note` | 添加理由,可空 | 软删除沿用 `SYBShop` 的 `DeletedAt` + `DeletedFlag` 哨兵写法(唯一索引与可空 `deleted_at` 共存)。 归一化复用 `sybshop.Normalize`(去首尾空格、全角折半角、字母小写)。用户清单前三条带尾随空格,`CVC` 可能为全角或小写,三者均需要。`[必须]` **不做简繁转换**,SYB 数据为繁体,保持原样。 API 路由照 `sybshop/router.go`:GET 与写操作分开注册,写操作加 `middleware.RequireRoleKey("admin")`(go-admin 按 API 路径授权,不分开无法表达「配置仅管理员,其他角色只读」)。 管理页 `web/src/views/goauto/syb-product-filters`,菜单与 `syb-shops` 同组。 ### 统计 `[必须]` 两条规则命中数**分别**记入 `SyncReport`,不得合并成单一「过滤总数」。 理由:规则2 今日净增 0。只有分开计数,才能在它由 0 变为非 0 时立刻看出档口已改编码格式。合并统计会使规则1 是否失效不可观测。 ## 非目标 - 不清理存量 8504 行档口数据(删生产数据属高风险,需单独建单确认) - 不过滤空 `variationSku` - 不把规则1 做成配置 - 不匹配 `productTitle` ## 前置依赖 `[必须]` **新增管理页属于新页面,按 AGENTS.md「工单与设计证据双门禁」需先有可审阅原型并经用户确认,才能编写该页生产代码。** 服务端过滤逻辑、表结构与 API 不受此门禁阻塞,可先行实施。 可并行:与 #241 / #242 无代码重叠,可并行。 ## 子项目影响 - `server/`:`sybimport`(过滤 + 统计)、`models`(新表)、新增 filter 包(API)、`migrations` - `web/`:新增管理页 - `android/`:无 ## 验收 1. 同步时 `variationSku` 含 `-` 或 `#` 的明细不入库 2. 命中已启用关键词的明细不入库;停用后不再生效 3. 空 `variationSku` 正常入库 4. 同步结果页分别显示规则1 与规则2 的命中条数 5. 关键词重复添加被唯一索引拒绝;大小写/全角半角/首尾空格视为同一条 6. 非管理员可读取列表,不能增删改 7. 存量数据未被修改 ## 验证 - `sybimport` 过滤与统计单元测试(含 OR 而非 AND 的回归用例:`DD#004`、`300斤牛奶絲圓領#A057` 必须被过滤) - 归一化用例:尾随空格、全角 `CVC`、小写 `cvc` 归一为同一键;繁体不被转换 - 表与 API 的 CRUD + 权限测试 - `.\scripts\verify.ps1 -Component all` ## 风险 - **过滤比例高**:65% 明细将不再入库。属预期,但上线后首次同步须核对 `SyncReport` 命中数与本单核验数据(8504/13010)量级一致,不一致须停止排查。 - **规则1 误配置风险**:已通过「不做成配置」规避。 - **新旧口径不一致**:存量 8504 行保留,历史数据与新规则口径不同。本单不处理,在验收说明中记录。 ## 文档影响 - `docs/12-syb-erp-interface.md`:新增同步过滤规则章节(长期业务规则,需先改线上 Wiki 再 `harness.py sync`) - `docs/08-agent-api-contract.md`:新增 `syb-product-filters` 接口(同上) `[必须]` 按 AGENTS.md,先修改线上 Wiki 页面并回读 revision,再运行一次 `sync` 与一次 `sync --check` 提交镜像;不得直接编辑本地镜像反向覆盖。 --- ## 2026-09-11 范围变更:结构判据改为可配置(仅开关) 用户确认:`-` 与 `#` 也在同一管理页配置。 ### 变更内容 **原范围**「规则1:代码判据 / 不做成配置」作废,替换为下述设计。原「不把规则1 做成配置」一条从非目标中移除。 `syb_product_filter` 增加 `kind` 列,取值 `char` | `keyword`,两类规则共用一张表与一套 API: | kind | 内容 | 可新增 | 可删除 | 可启停 | |---|---|---|---|---| | `char` | `-`、`#` 两条,随迁移写入 | **否** | **否** | 是 | | `keyword` | 关键词清单 | 是 | 是 | 是 | `[必须]` API 必须在服务端拒绝对 `kind=char` 行的新增与删除,不得只靠前端隐藏按钮。 理由:档口编码的判据就是这两个字符。允许自由增删等于把业务定义交给误操作;判据本身需要变更(例如档口改用 `/` 或 `@`)属于需要评估的范围变化,应走新建工单,不应是一次点击。 ### `[必须]` 停用 `kind=char` 需二次确认 停用结构判据会使大量档口明细恢复入库(依本单核验:`#` 对应 8498 行,`-` 对应 6392 行)。前端必须弹出二次确认,文案示例: > 停用「#」后,SYB 同步将不再过滤含 `#` 的档口商品。按最近一次同步数据估算,约 8498 条明细会恢复入库。确认停用? `[必须]` 文案中的条数取自该行实时的 `last_hit_count`,不得写死。写死的数字在数据变化后会给出错误警告。 `kind=keyword` 的启停不需要二次确认(影响面为 0 行)。 ### `[必须]` 变更审计 `kind=char` 行的启停必须记录操作人与时间。关键词改错影响 0 行,结构判据改错影响约 65% 明细,两者审计要求不同。`kind=keyword` 沿用 `updated_at` 即可。 ### 过滤实现相应调整 `sybimport/sync.go` 的过滤逻辑由写死判据改为查表:先应用全部启用的 `kind=char` 行(任一命中即跳过),再应用启用的 `kind=keyword` 行。执行顺序与统计口径不变,两类命中数仍须分别记入 `SyncReport`。 ### 验收补充 8. `kind=char` 行无法通过 API 新增或删除(前端隐藏 + 服务端拒绝,二者都要测) 9. 停用 `kind=char` 弹出二次确认,且提示条数与该行 `last_hit_count` 一致 10. 停用 `#` 后,含 `#` 的明细恢复入库;重新启用后恢复过滤 11. `kind=char` 的启停操作可查到操作人与时间 ### 风险补充 - **误停用结构判据**:影响面约 65%。已通过「禁止增删 + 二次确认 + 实时影响量提示 + 操作审计」四项控制。 - 原「规则1 误配置风险已通过不做成配置规避」一条作废,改由上述四项控制。 ### 设计证据 原型需相应重做,覆盖:结构过滤分段(两条 char 行、无增删按钮)、停用二次确认弹窗。旧版原型(screen 10001/10002/10003/10004/10240)作废,以重做后的版本为准。
Author
Owner

设计证据(原型,待确认)

项 值
链接 https://qux.ilapage.cn/#/apps/6aa3a707191a826306a7fe21.html
App ID 6aa3a707191a826306a7fe21
画布 1280 × 860
起始页 10001
规模 5 screen / 243 widget / 3 条 flow
版本识别方式 以 App ID + screen 集合(10001/10002/10003/10004/10240)识别;QuantUX 无独立 revision 号
状态 草稿,待用户确认
确认人 / 确认时间 待填

覆盖范围

Screen 状态 widget
10001 列表-正常(管理员) 88
10002 新增关键词弹窗(含唯一索引冲突错误态) 33
10003 空状态 25
10004 非管理员只读(按钮置灰 + tag + 只读) 85
10240 加载中 12

对应 AGENTS.md「新组件记录正常、空、加载、失败、禁用和权限边界」六项,已全部覆盖。

设计基准

复用 web/src/views/goauto/syb-shops/index.vue 的现有规范:inline 搜索栏(主按钮 + 搜索 + 只看启用 + 重置)、顶部 el-alert 提示条、summary 行、el-table border stripe + 操作列 fixed=right、底部 pagination + scope-note、非管理员 tooltip 说明。

界面上固定下来的关键文案

  • 列表页与弹窗均声明:关键词匹配 variationSku,不匹配商品标题。
  • summary muted 行:本规则在「-、# 结构过滤」之后执行,命中数为结构过滤未覆盖的部分。
  • 唯一索引冲突:该关键词已存在(归一化后与「CVC短袖220斤」相同)
  • 空状态:还没有配置过滤关键词。结构过滤(variationSku 含 - 或 #)仍然生效,本页只影响额外的关键词过滤。
  • 只读:当前账号只有查看权限。新增、编辑、启用、停用和删除关键词需要管理员操作。
  • 底部:停用或删除只影响后续同步,已经入库的商品明细不会被删除。

示例数据使用本单线上核验结论:6 条关键词命中数均为 0(已被结构过滤覆盖),其中 5 条启用、1 条停用。

制作与复核

由 Codex(gpt-5.6-sol)依据本单方案制作,主 Agent 复核。复核发现并已修正三项:

  1. summary 行把样式类名 muted: 当作可见文案渲染 —— 已去掉前缀
  2. 备注列空值渲染为字面「(空)」 —— 已改为占位符 —
  3. 缺少加载态,不满足双门禁六状态要求 —— 已补 screen 10240

首轮曾以 gpt-6-astra 派发,返回 Selected model is at capacity,未产生任何改动,改用 gpt-5.6-sol 重跑。

未导出 HTML(用户未要求),未修改仓库文件,未提交 Git。

门禁状态

[必须] 本原型经用户确认前,不得编写管理页生产代码。服务端过滤逻辑、syb_product_filter 表结构与 API 不受此门禁阻塞,可先行实施。

## 设计证据(原型,待确认) | 项 | 值 | |---|---| | 链接 | https://qux.ilapage.cn/#/apps/6aa3a707191a826306a7fe21.html | | App ID | `6aa3a707191a826306a7fe21` | | 画布 | 1280 × 860 | | 起始页 | `10001` | | 规模 | 5 screen / 243 widget / 3 条 flow | | 版本识别方式 | 以 App ID + screen 集合(10001/10002/10003/10004/10240)识别;QuantUX 无独立 revision 号 | | 状态 | **草稿,待用户确认** | | 确认人 / 确认时间 | 待填 | ### 覆盖范围 | Screen | 状态 | widget | |---|---|---| | 10001 | 列表-正常(管理员) | 88 | | 10002 | 新增关键词弹窗(含唯一索引冲突错误态) | 33 | | 10003 | 空状态 | 25 | | 10004 | 非管理员只读(按钮置灰 + tag + 只读) | 85 | | 10240 | 加载中 | 12 | 对应 AGENTS.md「新组件记录正常、空、加载、失败、禁用和权限边界」六项,已全部覆盖。 ### 设计基准 复用 `web/src/views/goauto/syb-shops/index.vue` 的现有规范:inline 搜索栏(主按钮 + 搜索 + 只看启用 + 重置)、顶部 el-alert 提示条、summary 行、el-table border stripe + 操作列 fixed=right、底部 pagination + scope-note、非管理员 tooltip 说明。 ### 界面上固定下来的关键文案 - 列表页与弹窗均声明:`关键词匹配 variationSku,不匹配商品标题。` - summary muted 行:`本规则在「-、# 结构过滤」之后执行,命中数为结构过滤未覆盖的部分。` - 唯一索引冲突:`该关键词已存在(归一化后与「CVC短袖220斤」相同)` - 空状态:`还没有配置过滤关键词。结构过滤(variationSku 含 - 或 #)仍然生效,本页只影响额外的关键词过滤。` - 只读:`当前账号只有查看权限。新增、编辑、启用、停用和删除关键词需要管理员操作。` - 底部:`停用或删除只影响后续同步,已经入库的商品明细不会被删除。` 示例数据使用本单线上核验结论:6 条关键词命中数均为 0(已被结构过滤覆盖),其中 5 条启用、1 条停用。 ### 制作与复核 由 Codex(gpt-5.6-sol)依据本单方案制作,主 Agent 复核。复核发现并已修正三项: 1. summary 行把样式类名 `muted:` 当作可见文案渲染 —— 已去掉前缀 2. 备注列空值渲染为字面「(空)」 —— 已改为占位符 `—` 3. 缺少加载态,不满足双门禁六状态要求 —— 已补 screen `10240` 首轮曾以 `gpt-6-astra` 派发,返回 `Selected model is at capacity`,未产生任何改动,改用 `gpt-5.6-sol` 重跑。 未导出 HTML(用户未要求),未修改仓库文件,未提交 Git。 ### 门禁状态 `[必须]` 本原型经用户确认前,不得编写管理页生产代码。服务端过滤逻辑、`syb_product_filter` 表结构与 API 不受此门禁阻塞,可先行实施。
Author
Owner

设计证据(原型,已确认)

用户于 2026-09-11 确认:「原型通过」。旧版五屏证据(2026-09-11 早前提交的 screen 10001/10002/10003/10004/10240 版本)作废,以本条为准。

项 值
链接 https://qux.ilapage.cn/#/apps/6aa3a707191a826306a7fe21.html
App ID 6aa3a707191a826306a7fe21
画布 1280 × 1060
起始页 10001
规模 6 screen / 295 widget / 7 条 flow
版本识别方式 App ID + screen 集合(10001/10002/10003/10004/10240/10276)+ lastUUID 10309;QuantUX 无独立 revision 号
可见性 isPublic = true(为便于审阅开启,原型不含个人数据或凭据)
确认人 用户
确认时间 2026-09-11

覆盖范围

Screen 状态 widget
10001 列表-正常:结构过滤 + 关键词过滤两分段(管理员) 112
10002 新增关键词弹窗(含唯一索引冲突错误态) 33
10003 空状态(结构过滤照常显示,仅关键词为空) 33
10004 非管理员只读 97
10240 加载中 12
10276 停用结构判据二次确认 8

覆盖 AGENTS.md「新组件记录正常、空、加载、失败、禁用和权限边界」六项。

界面固定下来的关键约束

  • 结构过滤分段标题:结构过滤(不可增删,仅可停用);说明:这两个字符是识别档口商品的判据,命中即不入库。需要变更判据请另行建单评估。
  • kind=char 两行操作列为不可点击的灰字「不可删除」,无任何增删控件(服务端仍须独立拒绝,见验收第 8 条)
  • 结构过滤行含「最近变更」列(2026-09-11 14:06 admin 启用),对应审计要求
  • 二次确认弹窗:确认停用结构过滤「#」? + 约 8498 条明细会恢复入库 + 小字 条数取自该规则最近一次同步的命中数,会随数据变化。(对应「不得写死」)
  • 列表页与弹窗均声明 关键词匹配 variationSku,不匹配商品标题。
  • 关键词分段说明:在结构过滤之后执行,命中数为结构过滤未覆盖的部分。

示例数据为本单线上核验结论:# 命中 8498、- 命中 6392;6 条关键词命中均为 0(已被结构过滤覆盖),5 启用 1 停用。

门禁

设计证据门禁已解除,管理页生产代码可开始编写。

制作说明

首轮由 Codex 制作四屏;主 Agent 复核修正三项(样式类名 muted: 被当作可见文案、备注空值渲染为字面「(空)」、缺加载态)。本轮结构过滤分段与二次确认弹窗由主 Agent 使用 QuantUX MCP 完成——两次派发 Codex 均返回 Selected model is at capacity(gpt-6-astra、gpt-5.6-sol),零改动。

过程中主 Agent 误用 apply_changes(替换语义而非合并)导致 5 个 screen 的 name/start/children 被清空,已用改动前 dump 重建并校验 children 合计 295 = widgets 295。

## 设计证据(原型,已确认) 用户于 2026-09-11 确认:「原型通过」。旧版五屏证据(2026-09-11 早前提交的 screen 10001/10002/10003/10004/10240 版本)作废,以本条为准。 | 项 | 值 | |---|---| | 链接 | https://qux.ilapage.cn/#/apps/6aa3a707191a826306a7fe21.html | | App ID | `6aa3a707191a826306a7fe21` | | 画布 | 1280 × 1060 | | 起始页 | `10001` | | 规模 | 6 screen / 295 widget / 7 条 flow | | 版本识别方式 | App ID + screen 集合(10001/10002/10003/10004/10240/10276)+ `lastUUID 10309`;QuantUX 无独立 revision 号 | | 可见性 | `isPublic = true`(为便于审阅开启,原型不含个人数据或凭据) | | 确认人 | 用户 | | 确认时间 | 2026-09-11 | ### 覆盖范围 | Screen | 状态 | widget | |---|---|---| | 10001 | 列表-正常:结构过滤 + 关键词过滤两分段(管理员) | 112 | | 10002 | 新增关键词弹窗(含唯一索引冲突错误态) | 33 | | 10003 | 空状态(结构过滤照常显示,仅关键词为空) | 33 | | 10004 | 非管理员只读 | 97 | | 10240 | 加载中 | 12 | | 10276 | 停用结构判据二次确认 | 8 | 覆盖 AGENTS.md「新组件记录正常、空、加载、失败、禁用和权限边界」六项。 ### 界面固定下来的关键约束 - 结构过滤分段标题:`结构过滤(不可增删,仅可停用)`;说明:`这两个字符是识别档口商品的判据,命中即不入库。需要变更判据请另行建单评估。` - `kind=char` 两行操作列为**不可点击的灰字**「不可删除」,无任何增删控件(服务端仍须独立拒绝,见验收第 8 条) - 结构过滤行含「最近变更」列(`2026-09-11 14:06 admin 启用`),对应审计要求 - 二次确认弹窗:`确认停用结构过滤「#」?` + `约 8498 条明细会恢复入库` + 小字 `条数取自该规则最近一次同步的命中数,会随数据变化。`(对应「不得写死」) - 列表页与弹窗均声明 `关键词匹配 variationSku,不匹配商品标题。` - 关键词分段说明:`在结构过滤之后执行,命中数为结构过滤未覆盖的部分。` 示例数据为本单线上核验结论:`#` 命中 8498、`-` 命中 6392;6 条关键词命中均为 0(已被结构过滤覆盖),5 启用 1 停用。 ### 门禁 设计证据门禁已解除,管理页生产代码可开始编写。 ### 制作说明 首轮由 Codex 制作四屏;主 Agent 复核修正三项(样式类名 `muted:` 被当作可见文案、备注空值渲染为字面「(空)」、缺加载态)。本轮结构过滤分段与二次确认弹窗由主 Agent 使用 QuantUX MCP 完成——两次派发 Codex 均返回 `Selected model is at capacity`(`gpt-6-astra`、`gpt-5.6-sol`),零改动。 过程中主 Agent 误用 `apply_changes`(替换语义而非合并)导致 5 个 screen 的 name/start/children 被清空,已用改动前 dump 重建并校验 `children 合计 295 = widgets 295`。
Author
Owner

实施完成并验收通过(2026-09-11)

用户 2026-09-11 在本地真实环境测试后确认验收通过。已合并 main 并推送:7992903..c24e8a4。

提交

哈希 内容
652bf30 初始实现(Codex,gpt-5.6-terra)
20f59b6 修 last_hit_count 按类汇总而非按行
78df997 用线上真实 variationSku 样本补回归测试
cad9f6d 补菜单版本迁移
503b3cb 补建表版本迁移、MySQL 列长度、去掉「只看启用」
40a254d Wiki 镜像(本单)
c24e8a4 Wiki 镜像(无关页面滞后,同一次 sync 带出,单独提交)

复核发现并修复的四个缺陷

初版 go test -p 1 ./... 33 个包全绿、pnpm build:prod 成功,但带着以下四个缺陷;其中三个只有在真实 MySQL 上跑迁移才会暴露。

  1. last_hit_count 按 kind 写同一个汇总值。Match 只返回 (char, keyword bool),无法归因到具体规则。停用确认弹窗引用该值,会对 # 和 - 提示同一个数字——按线上数据其中一个会偏差约 2100 行。改为 Match 返回命中的规则,新增 HitCounter 按规则 id 累加,UpdateHits 写每条自己的数,未命中写 0(避免上一轮的旧数字留存)。
  2. 表缺版本迁移。只加进 migrations.Migrate 的 AutoMigrate 列表,对已有数据库无效。由 cmd/migrate 的迁移后建表检查捕获:迁移后仍缺少表:syb_product_filter。
  3. 菜单缺版本迁移。access.GoAutoModules() 注册不产生 sys_menu 行;菜单由首次建菜单的版本迁移写入,该版本已记录在 sys_migration 中不再执行,启动时的 ReconcilePurchaserPermissions 只对齐 Casbin API 矩阵。结果是表、接口、路由都在,但无法从菜单进入。
  4. MySQL 无法建唯一索引。keyword / normalized_keyword 声明为 type:text,MySQL 拒绝无前缀长度的 BLOB/TEXT 进入键定义(Error 1170)。改为 size:200(对齐 SYBShop 的做法)。SQLite 接受文本列入索引,所以整套单元测试无法发现。

验证

  • go build ./... 通过;go test -p 1 ./... 33 个包全绿(合并后在 main 上重跑确认)
  • pnpm lint 本单文件 0 error;pnpm build:prod 成功
  • python dev_scripts/harness.py check --strict 通过;sync --check 通过
  • 真实 MySQL 验证(用户本地 goauto 库,迁移前已完整备份):迁移应用成功,syb_product_filter 8 行(2 结构 + 6 关键词),繁体保留(印花大學T → 印花大學t,只降大小写),sys_menu 菜单行创建于 采采管理 组下
  • 接口验证(隔离 SQLite 实例):char 新增 403 结构过滤不可新增;char 删除 403 结构过滤不可删除;重复关键词 409 该关键词已存在(归一化后与「CVC短袖220斤」相同);全角加空格 cvc短袖220斤 被判为重复
  • 真实样本回归(realworld_test.go):新寮-雅伊阁大码女装#301、广达-爆款大码女装#9018、DD#004、300斤牛奶絲圓領#A057、上寮-琪琪尚雅网批#2604##(&15) 被过滤;PDD0582、my238、le171、1、引流款、空值 保留;两条结构规则记录到不同的命中数
  • 菜单迁移验证:从已迁移数据库删除菜单行后重跑 migrate,输出 新执行 1 个,跳过 42 个,行恢复且 admin、purchaser 两角色均绑定

未验证

  • 线上 SYB 实际同步未执行。过滤比例风险仍在:上线后首次同步须核对 SyncReport 的两类命中数与本单核验量级(8504 / 13010)一致,不一致须停止排查。
  • 未在生产数据库执行迁移。

与已确认原型的偏离

  1. 去掉「只看启用」开关(用户 2026-09-11 确认)。原因:停用某条结构判据时该行会被筛掉,而「有结构判据被停用」正是本页最需要被看见的状态——它意味着约 65% 档口数据恢复入库。关键词搜索保留。
  2. 关键词行未实现「编辑」,只有删除。原型上有该按钮,服务端也无 Update 方法。影响有限(删除后重加等效),未处理,如需补应另建工单。

文档影响(已完成)

先改线上 Wiki 并回读 revision,再同步镜像:

  • SYB-ERP-Interface-Contract → revision f1792b7f(镜像 docs/12-syb-erp-interface.md)
  • Android-Agent-API-Contract → revision 38869285(镜像 docs/08-agent-api-contract.md)

验收结论

用户 2026-09-11 明确验收通过。关闭本工单。

## 实施完成并验收通过(2026-09-11) 用户 2026-09-11 在本地真实环境测试后确认验收通过。已合并 main 并推送:`7992903..c24e8a4`。 ### 提交 | 哈希 | 内容 | |---|---| | `652bf30` | 初始实现(Codex,gpt-5.6-terra) | | `20f59b6` | 修 `last_hit_count` 按类汇总而非按行 | | `78df997` | 用线上真实 `variationSku` 样本补回归测试 | | `cad9f6d` | 补菜单版本迁移 | | `503b3cb` | 补建表版本迁移、MySQL 列长度、去掉「只看启用」 | | `40a254d` | Wiki 镜像(本单) | | `c24e8a4` | Wiki 镜像(无关页面滞后,同一次 sync 带出,单独提交) | ### 复核发现并修复的四个缺陷 初版 `go test -p 1 ./...` 33 个包全绿、`pnpm build:prod` 成功,但带着以下四个缺陷;其中三个只有在真实 MySQL 上跑迁移才会暴露。 1. **`last_hit_count` 按 `kind` 写同一个汇总值**。`Match` 只返回 `(char, keyword bool)`,无法归因到具体规则。停用确认弹窗引用该值,会对 `#` 和 `-` 提示同一个数字——按线上数据其中一个会偏差约 2100 行。改为 `Match` 返回命中的规则,新增 `HitCounter` 按规则 id 累加,`UpdateHits` 写每条自己的数,未命中写 0(避免上一轮的旧数字留存)。 2. **表缺版本迁移**。只加进 `migrations.Migrate` 的 AutoMigrate 列表,对已有数据库无效。由 `cmd/migrate` 的迁移后建表检查捕获:`迁移后仍缺少表:syb_product_filter`。 3. **菜单缺版本迁移**。`access.GoAutoModules()` 注册不产生 `sys_menu` 行;菜单由首次建菜单的版本迁移写入,该版本已记录在 `sys_migration` 中不再执行,启动时的 `ReconcilePurchaserPermissions` 只对齐 Casbin API 矩阵。结果是表、接口、路由都在,但无法从菜单进入。 4. **MySQL 无法建唯一索引**。`keyword` / `normalized_keyword` 声明为 `type:text`,MySQL 拒绝无前缀长度的 BLOB/TEXT 进入键定义(`Error 1170`)。改为 `size:200`(对齐 `SYBShop` 的做法)。SQLite 接受文本列入索引,所以整套单元测试无法发现。 ### 验证 - `go build ./...` 通过;`go test -p 1 ./...` 33 个包全绿(合并后在 main 上重跑确认) - `pnpm lint` 本单文件 0 error;`pnpm build:prod` 成功 - `python dev_scripts/harness.py check --strict` 通过;`sync --check` 通过 - **真实 MySQL 验证**(用户本地 `goauto` 库,迁移前已完整备份):迁移应用成功,`syb_product_filter` 8 行(2 结构 + 6 关键词),繁体保留(`印花大學T` → `印花大學t`,只降大小写),`sys_menu` 菜单行创建于 采采管理 组下 - **接口验证**(隔离 SQLite 实例):`char` 新增 403 `结构过滤不可新增`;`char` 删除 403 `结构过滤不可删除`;重复关键词 409 `该关键词已存在(归一化后与「CVC短袖220斤」相同)`;全角加空格 ` cvc短袖220斤 ` 被判为重复 - **真实样本回归**(`realworld_test.go`):`新寮-雅伊阁大码女装#301`、`广达-爆款大码女装#9018`、`DD#004`、`300斤牛奶絲圓領#A057`、`上寮-琪琪尚雅网批#2604##(&15)` 被过滤;`PDD0582`、`my238`、`le171`、`1`、`引流款`、空值 保留;两条结构规则记录到不同的命中数 - **菜单迁移验证**:从已迁移数据库删除菜单行后重跑 `migrate`,输出 `新执行 1 个,跳过 42 个`,行恢复且 `admin`、`purchaser` 两角色均绑定 ### 未验证 - **线上 SYB 实际同步未执行**。过滤比例风险仍在:上线后首次同步须核对 `SyncReport` 的两类命中数与本单核验量级(8504 / 13010)一致,不一致须停止排查。 - 未在生产数据库执行迁移。 ### 与已确认原型的偏离 1. **去掉「只看启用」开关**(用户 2026-09-11 确认)。原因:停用某条结构判据时该行会被筛掉,而「有结构判据被停用」正是本页最需要被看见的状态——它意味着约 65% 档口数据恢复入库。关键词搜索保留。 2. **关键词行未实现「编辑」**,只有删除。原型上有该按钮,服务端也无 `Update` 方法。影响有限(删除后重加等效),未处理,如需补应另建工单。 ### 文档影响(已完成) 先改线上 Wiki 并回读 revision,再同步镜像: - `SYB-ERP-Interface-Contract` → revision `f1792b7f`(镜像 `docs/12-syb-erp-interface.md`) - `Android-Agent-API-Contract` → revision `38869285`(镜像 `docs/08-agent-api-contract.md`) ### 验收结论 用户 2026-09-11 明确验收通过。关闭本工单。
ila closed this issue 2026-09-11 17:21:28 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#269