Commit Graph
192 Commits
Author SHA1 Message Date
QiuSWandClaude Opus 5 7c43dc6029 feat(server): 放开未开始采集任务的取消 (#297)
批量建单后无法中途叫停:删除只放行 failed,pending 一条都撤不掉,只能等设备
逐个跑完。图搜成功率并不高,批量越大越需要叫停。

新增 cancelled 终态,与 PurchaseTaskStatusCancelled 的既有约定对齐。

- pending → cancelled;running 拒绝取消。running 正在设备上操作 PDD,中途打断
  后页面停在哪一步不可控,会影响下一个任务归位(#292 已为此付过代价)。语义是
  「停止后续,当前这个跑完」。
- 取消与 Claim 的互斥点是同一条件更新。关键:Claim 领取时并不改 status,只写
  device_id 和租约,因此条件里必须带 lease_expires_at,否则会把刚被领走的任务
  误取消。
- 批量逐条更新、不包在一个事务里:一条因并发领取而跳过,不应回滚已成功取消的
  其它任务。超出单批上限时以 HasMore 如实上报,不静默截断。
- status 的 CHECK 约束只认旧五值,GORM 在 MySQL 上不改写既有 CHECK,按同文件
  ensureMySQLDirectSelectConstraint 的手法补幂等 DROP/ADD。cancelled 并入
  syncGuardSlots 终态分支以满足 active_slot / device_run_slot 两个约束。

实施:sonnet 子代理,改动经独立复核与重跑验证。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 16:28:07 +08:00
QiuSWandClaude Opus 5 9cdc9f325c fix(server): 「一键匹配颜色和尺码」未拿到结论的规格值重问一次 (#299)
规格匹配有三条路径,#295 只覆盖了采购侧两条。虾皮商品详情页「一键匹配」走的是
shopeeproduct.suggestMappings,仍然一次失败即放弃。实测虾皮 1528 的「黑色」在
PDD 8514 里唯一对应(黑色-冰块猫,9 个在售组合),却被判为「AI 未给出可靠建议」。

这条路径是批量调用,不能照搬 #295 的逐值重试:

- 只把没拿到可靠结论的 source 组成第二次 SuggestBatch,已有结论的不重问。
- 第二次沿用同一份 candidates。
- SuggestBatch 整体报错时也重试一次;两条重试路径互斥,总调用严格不超过 2 次。

判据一律未动:候选集校验、candidateValues 的 defensive 检查、
AutoConfirmMinConfidence 门槛、preserved 与确定性匹配分支全部保持原样。

实施:grok-4.6(派单试点),改动经独立复核与重跑验证。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 15:46:08 +08:00
QiuSWandClaude Opus 5 08d77bd982 fix(server): 规格匹配的 AI 调用重试一次,并区分无匹配与服务不可用 (#295)
AI 对同一输入会给出不同答案(deepseek-v4-flash 在 Temperature=0 下仍如此:
紫色/S 第一次答「未找到可靠的 PDD 规格」,第二次答「唯一匹配」),而匹配一个值
只调一次、失败即放弃,于是本该匹上的值因一次抽风永久留空。

- resolveSpecMatch 重试一次,上限 2 次调用。真失败(PDD 没有该颜色、白色有歧义)
  每次都会失败,重试更多只是浪费调用——实测连续三轮失败数稳定在 12。
- 区分 AI 明确「无匹配」与服务不可用,两者处置不同。此前都记成 failed,排查时
  分不开(#294 的遗留问题)。
- BatchSpecMatch 与 MatchArchiveSpecs 两条路径都接上。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 14:59:16 +08:00
QiuSWandClaude Opus 5 b2b8c0d1e0 fix(ops): 记录图搜规格匹配的逐条结果,保留 Agent 原始失败原因 (#294)
排查时两次卡在缺记录上,只能靠手工重放接口和按失败耗时反推。

服务端:BatchSpecMatch 的返回值此前用 `_` 丢弃,23 条明细因 AI 服务 503 全部
失败时日志一个字都没有。改为记录四个计数并附前几条阻塞原因。

Agent:IMAGE_SEARCH_ENTRY_NOT_FOUND 有「找不到入口」和「归位失败」两个来源,
统一文案把两者抹平。改为在用户文案后括注内部原因,写法与候选点击失败一致。

两处都只记录错误码、计数与规格层面的原因,不含凭据、账号、订单和个人数据。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 11:53:43 +08:00
QiuSWandClaude Opus 5 1274161a1d fix(server): 规格同步后匹配档案里所有未映射的规格值 (#293)
BatchSpecMatch 遍历的是 SYB 明细,只匹配明细需要的值。#290 把虾皮完整颜色尺码
同步进档案后,还没有订单的值一个都不会被尝试——等订单真来了仍要人工点一次匹配,
正是 #290 想消除的动作。

新增 MatchArchiveSpecs:对档案里每个未确认映射的值逐个匹配,确定性优先、AI 兜底。

- 只写映射,不创建采购。人工检查点不变。
- 逐值匹配没有另一半规格,无法校验完整可售组合,因此至少要求该值出现在某个在售
  SKU 里;完整组合仍由采购预检把关。
- 没有 SKU 证据时放行,与 aiMatchQualificationForDataset 的既有口径一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 11:53:43 +08:00
QiuSWandClaude Opus 5 5b3c8df3bc feat(server): 同步虾皮完整颜色尺码并一次性匹配 (#290)
图搜采集成功并自动关联后,拉取该虾皮商品的完整颜色尺码清单合并进档案,
再触发一次匹配。此后同一虾皮商品的其它颜色 SYB 订单无需再采集、再点匹配。

- 新增 shopeespec 叶子包,凭据只从 GOAUTO_ERPGO_BASE_URL/GOAUTO_ERPGO_APIKEY
  读取;错误文本不携带 URL,避免 apikey 流进日志。
- 写入档案前经 sybspec.StripAnnotations 剥离 【...】,与 SYB 明细同源,
  否则 confirmedMappings 查不到、映射全部落空。
- 标记记录同步时对着哪个 PDD 商品(spec_sync_pdd_product_id),不是布尔值,
  重新关联时自动失效。
- 已完整同步的商品再次图搜时返回 IMAGE_SEARCH_SPEC_SYNCED。
- 迁移只加列不回填:既有档案仍是从 SYB 明细增量累积的,不能假装已同步。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 10:33:53 +08:00
QiuSWandClaude Opus 5 a27db5f960 fix(server): 检出 SYB 规格塌缩并明确失败,不再静默共用同一映射 (#289)
sybimport.Parse 剥离 【...】,而括号里经常是真正的规格标识而非备注:
款号 白色【207A】、色号 黑色【M0059C1】、颜色组合 【深灰+淺灰】。剥离后
不同的虾皮规格塌缩成同一个 target_color,而它正是采购查映射的键,同键
即同映射,Agent 会为不同规格点击同一个 PDD 值。

线上实测(2026-09-16):颜色塌缩键 78 个、涉及 61 个商品、190 个原始
规格;尺码塌缩键 24 个;受影响明细 407 条。落在塌缩键上的采购任务 3 笔,
order_created 0 笔——至今没出事是因为每个塌缩键目前只有一条规格真正
下过单,不是设计上有保证。最高危的 6 个键全部尚未采购。

本次不改解析契约、不迁移数据,只让它明确失败:采购预览命中塌缩键时返回
SPEC_KEY_AMBIGUOUS 并提前返回,不再落到就绪判定;process_stage 一并拦截,
因为塌缩的键不能走 AI 匹配那条路——再匹配一次也只会为同一个键写一份映射,
而问题恰恰是多个规格共用这个键。

`[必须]` 解析原语下沉到新的叶子包 sybspec。sybimport 已依赖 purchase,
采购侧直接引用会成环;而两边各写一份镜像实现必然漂移,届时塌缩检测会拿错
半边去比,结论反而不可信。sybimport 保留别名转发,调用方无需改动。

`[必须]` 检测按虾皮商品加载全部明细,不是本次请求的那几条:塌缩是商品级
属性,只看请求内的明细会漏判。

TestBatchPreviewBulkLoadsAndNeverCallsAIMatcher 的查询数由 7 改为 8,新增
的是一次有界批量查询。该断言守的是不得出现 N+1,不是具体数字。

测试样本全部取自线上真实数据。app/goauto/sybimport 的 12 个失败先于本次
存在(#285),未新增。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 10:20:09 +08:00
QiuSWandClaude Opus 5 41996f33b2 feat(server): 图搜采集成功后自动触发规格匹配 (#287)
图搜是异步的:Agent 在手机上跑几分钟,采购员必须离开页面、稍后回来、
重新勾选、再点 AI 匹配。旧流程虽然全手工但一气呵成,图搜把它切成了
两段,中间靠人记着回来。本次在关联写入之后接一个钩子消除这个断层。

`[必须]` 只能接在这里。图搜批量结果对话框在任务创建后立刻弹出,那时
Agent 一个都还没执行,无从匹配。

autoLinkImageSearch 改为返回是否确实写入关联及受影响的 SYB 明细。
RowsAffected 为 0 表示乐观并发谓词拒写——任务创建后有人改过这条关联,
目标商品已不是我们找到的那个,此时匹配上去只会把错误结果写进档案,
因此不触发。

匹配失败只记录不冒泡:采集结果已经提交完成,不能因为这个可选增强让
SubmitResult 失败、让 Agent 以为采集没成功。

不自动创建采购。匹配只把明细推到“采购就绪”,创建采购仍由人点击——那是
整条链上唯一的人工检查点,因为图搜找商品、自动关联、AI 匹配规格三步
都没有人看过,而 #200 已取消 SYB 批量入口的置信度门槛,不能以“AI 没
把握会停下”来兜底。

标记文案相应扩展为“商品由图搜找到、规格由 AI 自动匹配,均未经人工
确认”,覆盖 SYB 商品页、采购任务列表与详情、虾皮商品详情抽屉。

app/goauto/sybimport 的 12 个失败先于本次存在(#285),未新增。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 17:51:42 +08:00
QiuSWandClaude Opus 5 174d92a1c9 fix(server): SYB 结构过滤合并为一条,要求同时包含 - 和 # (#286)
结构过滤原为两条 char 规则(- 和 #),RuleSet.Match 逐条 Contains、
命中即返回,是或关系。线上最近一次完整同步(syb_sync_run 541)里
- 命中 384、# 命中 148,合计 532 条被过滤;因 - 先判且命中即返回,
那 148 条是含 # 但不含 - 的。

改为一条,keyword 存必需字符集合 -#,匹配要求集合中每个字符都出现:

- 同时含 - 和 # → 过滤
- 只含其一 → 放行
- 都不含 → 放行

已向用户说明每次同步至少多放行 148 条,用户确认后实施。

`[必须]` migrations/migrate.go 的种子必须同步改成一条。该函数每次启动
都会 FirstOrCreate,若继续播种旧的两条,下次重启就会把迁移
1789500000000 合并掉的行重新建回来,过滤静默退回或关系。已加
TestFreshDatabaseSeedsOneStructuralRule 锁住。

关键词过滤不变,仍是子串匹配,并加测试防止字符集合语义误用到关键词上。

连带效果:真实样本 300斤牛奶絲圓領#A057 只含 #,不再被结构过滤命中后
落到关键词规则上仍被过滤——#269 把关键词称为 forward safety net,这是
它第一次真的接住东西。realworld 用例的统计由 char=5/keyword=0/kept=6
变为 char=3/keyword=1/kept=7。

app/goauto/sybimport 的 12 个失败先于本次存在(#285),未新增。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 17:15:40 +08:00
QiuSWandClaude Opus 5 95b5f05ae8 fix(#277): 图搜候选点击改瞄有文案子节点,关联不再被跨币种判据挡死
真机连续排查出三处缺陷,均已实测确认:

1) 候选卡片点不动。快照里 SnapshotNode.label 只取节点自身的 text/
   contentDescription,而 clickFreshDetailed 重新定位时用
   preferredOrDescendantLabel()(会往子孙里挖)。候选卡片是自身无文案的
   FrameLayout,两者算出的 label 一个是空串一个是标题文字,永远对不上,
   重找直接 TARGET_NOT_FOUND。改为瞄准卡片内最靠上的有文案子节点,由
   clickFreshDetailed 既有的向上走到可点祖先逻辑落到卡片容器上。
   卡片内文案的归属同时从 path 前缀改为几何包含——path 父子语义从未核对,
   几何包含已用真机 dump 直接验证。

2) 错误码拆分。IMAGE_SEARCH_NO_CANDIDATES 此前同时表示“结果页没出现”和
   “卡片点不动”,定位问题时完全分不清。拆为 RESULTS_NOT_READY /
   NO_CANDIDATES / CANDIDATE_CLICK_FAILED,并把 clickFreshDetailed 的
   result 与 reason 带进失败消息——这两个值本来就有,此前被丢弃。

3) 自动关联从上线起一次都没执行过。snapshot.PriceGuardSkipped 与
   shopee_product.currency != "CNY" 两处各自 return nil,而 priceSkipped
   的唯一来源就是 referenceCurrency != "CNY",两者是同一个条件;线上 26902
   个虾皮商品全部是 TWD。表现为图搜任务 completed、PDD 商品采集完整,但
   关联停在旧值,采购因此无法创建(订单 26091536MJHXG8 即如此)。
   币种不同只说明价格没法比,不说明关联不该建立,两件事已解耦。

   补偿控制是 image_search_linked 标记,已显示在商品页与采购视图。
   ImageSearchPriceAllowed 零调用方,加 [未接线] 标注,避免再被当成生效的
   防线;待引入可配汇率后再接。

测试夹具默认币种由 CNY 改为 TWD(线上真实币种),并新增跨币种必须关联、
同币种行为不变两个用例。此前夹具用 CNY,所以整套用例全绿而线上从未成功。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 16:27:15 +08:00
QiuSWandClaude Opus 5 750b8763dc feat(#280): 图搜确认后覆盖已有关联,去掉覆盖复选框
用户 2026-09-15 选定行为:去掉「覆盖已有关联」复选框,点击创建时若
所选商品中存在已关联的,弹窗说明将被覆盖,确认后覆盖。

原复选框是误导的:它只放行任务创建,落库时的 CAS 谓词仍要求
pdd_product_id IS NULL,所以勾上之后设备白跑 20-40 秒、关联一点不变,
界面还显示任务已完成。

服务端把关联写入从「必须为空」改为乐观并发:比对快照里的
OriginalPDDProductID,只要任务创建后没人动过这条关联就写入。直接去掉
谓词会让 Agent 执行的 20-40 秒变成静默吞掉人工改动的窗口;改为乐观并发
既满足覆盖需求,又让并发的人工改动继续胜出。该快照字段与
sameImageSearchLink 早已存在,此前未被关联路径使用。

前端确认框只在确实存在已关联商品时弹出并给出条数,一条都没有时不弹,
避免变成每次都要点掉的噪声;取消则整批不创建。

测试重写为四个用例:未变更时覆盖、任务创建后被改则放弃、创建时无关联
但执行中被抢先关联则放弃、常规无关联路径正常写入。原
TestAutoLinkImageSearchDoesNotOverwriteManualAssociation 断言的是被本次
取代的旧契约,已移除。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 15:23:34 +08:00
QiuSWandClaude Opus 5 93a175d04f fix(syb): 空格分隔的颜色尺码按逗号同规则拆分 (#284)
#274 删除 ambiguousPattern 以接受描述性颜色(黑色+白色 簡約親膚),
意图正确,但副作用是「黑色 XL」也被整条当成颜色、尺码丢失且判为
success。success 的行不进 AI 解析队列(ai_parse_batch.go 只选
uncertain/failed),因此没有任何机制会纠正它们。2026-09-15 线上有
152 条 parse_status='success' 且 target_size='' 的明细。

不恢复被删的旧闸——那会退回 #274 之前重新拒绝描述性颜色。改为把
空格当作与逗号同等的分隔符,复用逗号分支已有的规则:恰好一个空格
分隔 token 带明确尺码特征时才拆分,不构成猜测。

- 多于一个 token 命中尺码特征时不拆,对齐逗号分支「两侧均像尺码则
  无法安全识别颜色」。
- 没有尺码 token 时原样保留为颜色,#274 的意图不受影响。
- 一并修正 parse.go 中停留在旧 uncertain 描述的文档注释。

新增 parse_whitespace_split_test.go 锁住完整对照表,含 #274 描述性
颜色不被退回的回归。

`[必须]` app/goauto/sybimport 存在 12 个先于本工单的失败用例,来自
#272/#274 改变契约后未更新的旧断言,已记录为 #285。本次提交前后失败
集合逐条比对完全一致,未新增。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 14:23:02 +08:00
QiuSWandClaude Opus 5 c30b8c4624 fix(#277): 调度垫底改由服务端排序实现,并记录真机判据核对结果
客户端 TaskDispatchPolicy 无法实现「采集 > 图搜」:图搜任务就是
source='image_search' 的普通采集任务,由同一个 /tasks/next 端点下发,
Agent 调用时服务端已经选好了行。原先新增的 hasImageSearchWaiting 参数
和 CHECK_IMAGE_SEARCH 分支从未被传入 true,是不可达的死代码,且注释
声称的优先级与代码顺序相反。

改为在 task.Service.Next 的两处 ORDER BY 加入来源降级:

- 设备专属队列:来源降级作为主键。
- 共享候选池:来源降级排在 device_id 之后——指派设备是人的显式选择,
  必须继续优先于来源降级,否则未指派的采集任务会抢在运营明确路由到
  本机的图搜任务前面。

另外:

- #280 的批次上限去掉 env 覆盖。确认对话框要在提交前禁用按钮,前端
  必然持有同一个数字,env 覆盖只会让两侧静默不一致。
- #277 判据常量按 2026-09-15 真机 dump 核对:首页入口、图搜页四段文案、
  最近项目网格几何、结果页判据全部命中;已记录「唯一可点」过滤为必需
  (个人中心根节点带同一 content-desc 且覆盖全屏)、取景提示文案与移植
  来源不符、相册网格含视频、以及重试弹窗未验证。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 10:51:10 +08:00
QiuSW 2ddfd52f5d Merge branch 'fix/b280-imagesearch' into integrate/imagesearch 2026-09-15 10:36:34 +08:00
QiuSWandClaude Opus 5 c2c0bae056 feat(purchase): surface image-search-linked marker in purchase views (#279)
Item 5 of #279 required bringing the image-search auto-link annotation
into the purchase-facing views, display-only, without gating anything.

- BatchPreviewItem and AdminTaskItem now carry imageSearchLinked, sourced
  read-only from shopee_product.image_search_linked (already loaded in
  the batch preview dataset; bulk-loaded for the task list/detail views).
- Batch purchase preview dialog and purchase task list/detail now show a
  "图搜未核" tag plus a restrained summary hint when applicable.
- Does not touch Eligible/ReasonCode/Reason/NextAction or the #283
  mappingTargetsValid gate; regression test confirms an ineligible row's
  outcome is unchanged when imageSearchLinked is true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 10:35:22 +08:00
QiuSWandClaude Opus 5 b7babfba7e feat(#280): cap image-search batch size and warn about device time
Server: BatchCreateImageSearch now rejects a batch whose deduplicated
task count (one task per shopee_product, after merging SYB detail
rows) exceeds imageSearchMaxBatchTasks (50), returning
IMAGE_SEARCH_BATCH_TOO_LARGE with a readable Chinese message instead
of silently truncating. The limit is overridable via
GOAUTO_IMAGE_SEARCH_MAX_BATCH_TASKS. #277's scheduling floor (采购 >
采集 > 图搜) isn't implemented yet, so this cap is the only guard
against a large image-search batch starving manually started
collection/purchase tasks.

Web: the batch confirm dialog now shows the deduplicated task count
and an estimated device-occupation time range (20-40s/task), and
disables the submit button with an explanation when the batch would
exceed the same limit, instead of letting the request fail after
submit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 10:35:21 +08:00
QiuSW a3cf98940a fix(#279): only auto-link active pdd products 2026-09-15 09:51:39 +08:00
QiuSW cda1978bc6 test: prevent image snapshot payload leakage (#278) 2026-09-15 09:37:01 +08:00
QiuSW 90c1668fe3 test: cover image search auto-link guards (#279) 2026-09-15 09:01:10 +08:00
QiuSW 36d1f82ce2 feat: filter image-search linked shopee products (#279) 2026-09-15 08:53:37 +08:00
QiuSW 1c998911be feat: auto link completed image search results (#279) 2026-09-15 08:50:19 +08:00
QiuSW 45437a9214 feat: record cross-currency image search price guard (#278) 2026-09-15 08:44:39 +08:00
QiuSW 42b5312198 feat: support image search identity handoff and safe payload (#278) 2026-09-15 08:42:11 +08:00
QiuSW 7b540f3163 feat: create and dispatch image search tasks (#278) 2026-09-15 08:41:42 +08:00
QiuSW ffa5fc0710 fix: add idempotent image snapshot column migration (#278) 2026-09-14 18:02:22 +08:00
QiuSW 66ab36c88c feat: expose image search snapshot in task payload (#278) 2026-09-14 16:49:35 +08:00
QiuSW 35b69fc25a fix: narrow image search migration (#278) 2026-09-14 16:31:07 +08:00
QiuSW b4dc7fc9ae feat: track image search links and preserve manual overrides (#279) 2026-09-14 16:08:55 +08:00
QiuSW baa9408a36 feat: allow image search collection task source (#278) 2026-09-14 16:05:01 +08:00
QiuSW c137f5072f fix: keep purchase readiness consistent with confirmed mappings (#283) 2026-09-14 14:42:11 +08:00
QiuSW b83f20c6b7 fix(syb): accept descriptive color specs (#274) 2026-09-14 10:52:02 +08:00
QiuSW e0ed05d15a fix(syb): accept reliable single spec dimension (#274) 2026-09-12 12:01:15 +08:00
QiuSW 8163dd10eb fix(ai): match size by standard token (#273) 2026-09-12 11:46:46 +08:00
QiuSW aed295c8c1 fix(syb): reduce parse status to success or failed (#272) 2026-09-12 11:36:43 +08:00
QiuSWandClaude Opus 5 c14f5f528b fix(server): keep agent diagnostics on order-check failures #271
normalizeOrderUnknownFailure replaced the agent's message with a canonical
sentence, so the diagnostic #210 added never reached the database. Across the
61 PURCHASE_ORDER_PAYMENT_REPEATED failures on production between 2026-09-07
and 09-11, not one carried paymentBackAttempts or
consecutivePaymentSamplesAfterBack, which left three very different causes
indistinguishable: the Back never left the payment activity, the page rendered
slower than the ~1.1s window the agent allows, or the page was already the
order page and no marker matched. Those need different fixes, and there was no
way to tell which one to make.

The canonical sentence stays authoritative and leads the message — agent text
is free-form and the wording per error code has to stay stable — but the agent
evidence is now appended behind it, flattened to one line (control characters
and U+FEFF removed, whitespace collapsed) and truncated to the column's 1000
runes with the canonical part kept whole. The sibling "failed" branch already
stored agent text verbatim, so this adds no new trust assumption.

That branch also only wrote the failure onto the task, never the attempt, so
every attempt it produced carried a NULL error_code and was invisible to
attempt-level statistics — the spec-panel failures on production are exactly
that. It now writes both.

This restores observability only; it does not reduce how often
PAYMENT_REPEATED happens. Tuning the agent's sampling window is deliberately
left out until the next production failure shows real values.

Wiki updated first: Android-Agent-API-Contract@ba259178.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-12 11:13:02 +08:00
QiuSW 8293b27c19 fix(syb): retry drifting sync page once (#270) 2026-09-12 09:34:44 +08:00
QiuSWandClaude Opus 5 503b3cbbc5 fix(goauto): create syb_product_filter on existing databases #269
Three defects that only a real MySQL run could show.

The table had no version-local migration. Adding the model to
migrations.Migrate covers databases built from scratch, but every existing
one already has the older versions recorded in sys_migration, so that list
never runs again there. cmd/migrate's post-migration table check caught it:
"迁移后仍缺少表:syb_product_filter". The new version file creates the table
and seeds the same eight rules with FirstOrCreate on the normalized key, so
running both paths against one database cannot duplicate them.

Keyword and NormalizedKeyword were typed text, which MySQL refuses to put in
a key specification without a prefix length (Error 1170), so the unique index
could not be built at all. They are sized 200 now, matching how SYBShop sizes
its own matching key. SQLite accepts text in an index, which is why the whole
test suite passed while the real migration failed.

The 只看启用 filter is removed rather than kept: hiding a disabled structural
rule would conceal the one state on this page that matters most, since a
disabled "#" means roughly 65% of 档口 rows start entering the system again.
Keyword search stays.

Verified against the local MySQL database: migration applies, eight rules
present with traditional Chinese preserved (印花大學T normalizes to 印花大學t),
menu row created under 采采管理.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 17:00:11 +08:00
QiuSWandClaude Opus 5 cad9f6d185 fix(goauto): add the missing menu migration for SYB product filters #269
Registering a module in access.GoAutoModules() does not make its page
reachable on an existing database. sys_menu rows are written by the
versioned migration that first created them, and that version is already
recorded in sys_migration everywhere — it never runs again. Startup only
reconciles the casbin API matrix, not menus.

So on a fresh database the page appeared and on every real one it did not:
the table, the API and the route were all present with no way to navigate
there. This adds a migration that upserts the entry under 采采管理 beside
「SYB 店铺」 and binds it to both 管理员 and 采购员.

Verified by deleting the row from an already-migrated database and
re-running migrate: 新执行 1 个,跳过 42 个, row restored with both roles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 16:49:56 +08:00
QiuSWandClaude Opus 5 78df997fc3 test(goauto): pin SYB filter behaviour against real variationSku samples #269
The samples are the ones the production database actually holds: 档口 codes
like "新寮-雅伊阁大码女装#301" and "DD#004" must be filtered, while own
codes like "PDD0582", "my238", "1" and "引流款" must not, and an empty
variationSku is a product without a supplier code rather than a 档口 item.

The test also asserts the two structural rules record different hit counts.
That is the property the previous aggregate-per-kind write could not hold,
and the disable-confirmation dialog depends on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 16:37:43 +08:00
QiuSWandClaude Opus 5 20f59b68bd fix(goauto): record SYB filter hits per rule, not per kind #269
UpdateHits wrote one aggregate per kind, so every structural rule stored
the same number. The disable-confirmation dialog quotes that number
("停用「#」后…约 N 条明细会恢复入库"), and on real data "#" matches 8498
rows while "-" matches 6392 — the dialog would have warned with a figure
2100 rows off for one of them.

Match now returns the rule that matched instead of a char/keyword pair,
so the caller can attribute each hit to one rule, and UpdateHits writes
every rule's own count. Rules that matched nothing are written as 0 so a
previous run's number is never left behind.

The management page is reformatted to the repo's usual Vue layout and
gains the search / 只看启用 / 重置 controls the approved prototype shows.
Cancelling the disable confirmation now just reverts the switch instead
of throwing an unhandled rejection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 16:27:13 +08:00
QiuSW 652bf30f5a feat(goauto): add SYB product variation filters (#269) 2026-09-11 16:20:26 +08:00
QiuSW e4677069d7 fix(syb): allow metadata-only partial collection links #259 2026-09-10 16:07:58 +08:00
QiuSW 2b01974fe5 feat(syb): confirm one-click replacement from latest collection #258 2026-09-10 15:47:27 +08:00
QiuSW ea38ced58c fix(shopee): normalize colors before AI matching #257 2026-09-10 14:54:56 +08:00
QiuSW 2cb78ad8cd fix(collection): preserve task state during current-page reidentify #255 2026-09-10 14:26:39 +08:00
QiuSW b1594dc7bd feat(syb): link latest device collection and match specs #253 2026-09-10 11:34:22 +08:00
QiuSW 9088e6b734 fix(shopee): bound matching waits and recover drawer loading #254 2026-09-10 09:37:02 +08:00
QiuSW c6a962d522 fix(syb): persist validated pages and report partial sync success (#239) 2026-09-08 09:35:36 +08:00
QiuSW 71f7751754 fix(client-api): support HTTP and HTTPS by default (#237) 2026-09-07 17:25:55 +08:00
QiuSW 57b0f1595f feat(client-api): add scoped editable client keys (#237) 2026-09-07 16:20:46 +08:00