QiuSW and Claude Opus 5
08b7095cf1
fix(purchase): widen SYB writeback backoff, cover CheckSession, improve message ( #330 review)
...
Address review findings on 01510a8 :
1. BLOCKER: sessionRetryBackoff summed to 30min, shorter than the up-to-
~60min gap between a session dying and the next hourly SYB sync
refreshing it. Changed to 5m/10m/15m/30m/30m (total 90min across
maxSessionRetryAttempts=6), updated the code comment to state the
~90min > one hourly sync period rationale, and added
TestSessionRetryBackoffTotalExceedsHourlySyncWindow to guard it.
2. Test gap: the CheckSession probe added inside
restoreOrderWritebackClient was only exercised through a fake
Factory, never through a real sybclient.Client. Added
httptest-backed tests that run restoreOrderWritebackClient against
an emulated /am/user/get (matching the envelope shape in
sybclient/client.go's `envelope` type): valid session returns a
client, mismatched username maps to ErrSessionInvalid, 5xx/timeout
map to a non-invalid error — each asserting the syb_session row is
left untouched. Added an end-to-end worker test using the real
Factory against the invalid-session server, asserting
failed/SYB_SESSION_UNAVAILABLE with a scheduled backoff and an
intact session row.
3. sessionUnavailableMessage: renamed the default category to
"会话恢复失败(网络/其他)" and wrapped every category in an
actionable template ("SYB会话不可用(<类别>),将自动重试;如持续
失败请恢复登录后重试"), still well under the 300-char column limit
and free of raw error text/credentials.
Tests: go vet ./app/goauto/purchase/... (clean); go test
./app/goauto/purchase/... (ok, 3.4s, includes the new httptest-backed
CheckSession coverage and the backoff-window guard).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-21 16:09:34 +08:00
QiuSW and Claude Opus 5
01510a85dc
fix(purchase): bounded auto-retry for SYB writeback session failures ( #330 )
...
SYB order-number writeback silently gave up on session-class failures
(SYB_SESSION_UNAVAILABLE), requiring manual resubmit even though the
hourly sync job refreshes the session on its own. This adds a bounded,
backoff-scheduled auto-retry for that error code only:
- restoreOrderWritebackClient now actively probes the cached cookie
jar with sybclient.CheckSession after import, so a remotely-expired
session is classified as retryable up front instead of surfacing
later as SYB_READ_FAILED. It never logs in, never triggers OCR and
never deletes the cached session.
- The dropped Factory error is now categorized into a safe message
(no cookies/tokens) and recorded in error_message.
- The worker's claim query additionally picks up failed rows with
error_code=SYB_SESSION_UNAVAILABLE once their backoff
(lease_expires_at) has elapsed and attempt_count is below
maxSessionRetryAttempts=6 (1m/2m/4m/8m/15m growing backoff, chosen
to span the hourly sync window); other failure codes are unchanged.
- CanSubmit no longer hides manual resubmit during that backoff
window; manual resubmit resets attempt_count to 0 and clears the
lease so the worker cannot double-claim the same row.
Diff is limited to the purchase package; sybimport/sybclient/
sybinnercode are untouched.
Tests: go test ./app/goauto/purchase/... (new
order_writeback_session_retry_test.go covers backoff scheduling,
reclaim timing, max-attempt cutoff, CheckSession invalid/network
classification with no session deletion, CanSubmit during backoff,
manual resubmit reset, and non-session codes being excluded).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-21 16:05:26 +08:00
QiuSW
4261a542ca
fix( #328 ): filter manual PDD association by owned devices
2026-09-21 11:08:41 +08:00
QiuSW
beec630187
fix( #329 ): record device ownership migration
2026-09-21 10:38:54 +08:00
QiuSW
2491a857f7
docs: record purchaser device ownership ( #328 )
2026-09-21 10:26:33 +08:00
QiuSW
0661b2205f
fix( #328 ): enforce purchaser device ownership
2026-09-21 10:13:23 +08:00
QiuSW
bb1a410e8a
feat( #329 ): add device purchaser ownership
2026-09-21 09:59:00 +08:00
QiuSW
8d9c3d47e0
docs: define successful SYB order writeback query filter ( #327 )
2026-09-19 15:40:52 +08:00
QiuSW
368f2c2357
feat: filter purchase tasks by successful SYB order writeback ( #327 )
2026-09-19 15:38:42 +08:00
QiuSW
f66952f640
docs: record order information completion and SYB eligibility ( #326 )
2026-09-19 15:19:27 +08:00
QiuSW
3a2472dd20
feat: complete purchase order information and simplify SYB writeback ( #326 )
2026-09-19 15:16:39 +08:00
QiuSW
741e4bf680
docs: record bounded order reading and payable result contract ( #325 )
2026-09-19 11:53:43 +08:00
QiuSW
b76fb73e12
fix: wait for unpaid order evidence and report payable total ( #325 )
2026-09-19 11:52:53 +08:00
QiuSW
10be37498d
feat: add Chrome order backfill workflow ( #316 )
2026-09-18 16:03:27 +08:00
QiuSW
65d1f34865
docs: record production release and migrations ( #305 #306 )
2026-09-18 10:34:09 +08:00
QiuSW
7e257ca153
docs: sync SYB order writeback contracts ( #305 )
2026-09-18 10:20:03 +08:00
QiuSW
e89de1a085
feat: queue and reconcile SYB purchase order numbers ( #305 )
2026-09-18 10:13:20 +08:00
QiuSW
07a3817591
test: verify optional order amount in purchase detail ( #306 )
2026-09-18 09:30:20 +08:00
QiuSW
d35d7354d6
docs: sync optional order amount contracts ( #306 )
2026-09-18 09:26:45 +08:00
QiuSW
8c01329f97
feat: capture optional PDD order amount in manual backfill ( #306 )
2026-09-18 09:22:12 +08:00
QiuSW
9194fd664f
fix(ops): allow local Admin LAN access ( #304 )
2026-09-17 17:02:35 +08:00
QiuSW and Claude Opus 5
c2426e2671
merge: #242 Agent 采购记录页回填入口与 PDD 订单扫描
...
合并 feat/242-agent-order-backfill(edd1cb1)。
Android 代码自动合并无冲突,与主线此后的 Agent 改动(#276/#277 图搜、#292
归位、#294 失败原因、#302 支付页标记)共存;全量单测 390 通过(含本单新增
16 个),assembleDebug 成功。
docs/03 镜像冲突取主线版本(2026-09-11 Wiki 同步版,已含 #242 规则)。
注意:本次合入的是 2026-09-08 的完整版(扫描 + 调用 #241 上传),不是
2026-09-10 讨论的「第一阶段只写本地」版本。页面识别判据仍未经真机验证。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 15:54:59 +08:00
QiuSW and Claude Opus 5
8c1cec4430
merge: #241 服务端按地址后缀批量回填订单号与下单时间
...
合并 feat/241-order-backfill-endpoint(290a17e、72b8b5d)。
代码自动合并无冲突;与今天 #302(PaymentPageObservedAt)、#303(原地重试)
同文件的改动经 purchase / purchasecontract / task 测试验证无语义冲突。
docs 三个镜像冲突取主线版本:主线镜像于 2026-09-11 同步 Wiki,已包含
#241/#242 说明及其后 #254、#271 内容;分支侧为 2026-09-08 旧快照。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 15:49:30 +08:00
QiuSW and Claude Opus 5
60c75261d3
fix: 采购管理批量重试改为原地重试选中任务,不再新建 ( #303 )
...
采购员勾选失败任务点重试,要的是这条任务本身再跑一次。原先 BatchRetry 一律
调用 Create 新建(CG-224 → CG-234),任务号变化,同一 SYB 明细的多次尝试分散在
多个任务上。
- BatchRetry 改为逐条调用既有 Reset:任务号不变,状态回到 pending,本次执行记入
purchase_task_attempt。Reset 的全部保护原样沿用——碰过下单边界、同一明细已有
更新任务、规格快照不完整均拒绝并返回原因,拒绝后不退回新建。
- 先识别重放再做资格预检:首次重试后任务已是 pending,先预检会把同一 requestId
的重复提交判为「只有失败任务可以重试」,破坏幂等。测试抓到后修正。
- 资格预检关闭设备占用检查,同一设备上勾选的多条可排队;真正的占用判断在 Reset
事务内,ensureDeviceFree 不把租约为空的 pending 计为占用。
- AgentRetry(替代商品已匹配、继续采购)改走 batchRetryCreate,保持新建:商品已
替换,原任务的商品与规格快照不能复用。
- 前端文案改为「已重试 / 第 N 次执行」。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 14:50:05 +08:00
QiuSW and Claude Sonnet 5
f634996dad
fix: order_result_unknown 补充"是否见过支付/待付款页"诊断标记 ( #302 )
...
order_result_unknown 目前是全有或全无:parseOrderEvidence 要求订单号、下单
时间、待付款/支付文案同时命中才算 order_created,任何一项缺失就落进同一个
order_result_unknown,无法区分"确实到过支付页只是没读全证据"和"根本没到
那一步"——前者大概率已在 PDD 建了真实订单。
Agent:readOrderResult 采样循环中,只要命中过支付页 Activity 或
unpaidContextVisible(待付款/待支付/去支付文案),记 paymentPageObserved,
与订单号是否解析成功无关,随 order_result_unknown 一起上报。
服务端:PurchaseTask 新增 PaymentPageObservedAt,仅在请求带
paymentPageObserved=true 时写入服务端当前时间;不回填既有 107 笔历史记录,
无法从历史数据反推当时是否见过支付页。
不改判定结果本身,order_created 的四项条件、批量重试逻辑均未动。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 14:25:47 +08:00
QiuSW and Claude Opus 5
57f6cec803
fix(server): 档案合并改为无条件,既有数据可自愈 ( #301 )
...
只在键发生变化时才补档案是不够的:既有数据的键早已被前一次重解析改对了,档案
却还是空的,那样永远补不上——线上 215 条全部返回 unchanged,档案一条都没补进去。
mergeParsedSpec 对已存在的值幂等,无条件合并让这条路径能自愈。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 09:44:48 +08:00
QiuSW and Claude Opus 5
0e41000f62
fix(server): 消歧后的新键补进档案 ( #301 )
...
resyncShopeeProductKeys 改了兄弟明细的 target_color,却没把新键写进档案;只有被
直接重解析的那一条走了 mergeParsedSpec。
线上 2026-09-17 的后果:明细已是 `黑色【長袖】`,档案里还是旧的 `黑色`,采购查
映射查不到,明细永远停在「规格待匹配」;而采购员点「一键匹配」匹到的是档案里
剩下的旧键,看起来成功了却没有任何明细在用它。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 09:42:15 +08:00
QiuSW and Claude Opus 5
c1bd39496d
fix(server): 规格键消歧,剥离 【...】 不再让不同商品塌缩成同一键 ( #301 )
...
`黑色【短袖】` 与 `黑色【長袖】` 剥离后都成了 `黑色`,档案里只有一个条目,两个
不同商品共用一份映射,必然有一半买错。#289 因此拦截,但人工匹配救不了——坏的是
键本身。线上 59 个塌缩键、45 个商品、344 条明细被卡住。
新增 sybspec.ResolveKeys:只在会产生歧义时保留括号内容。不塌缩的键与今天逐字
一致,线上一万四千多条明细中的绝大多数不受影响。
- 消歧需要同组全部原始规格,单条明细判断不了自己是否安全,因此在拿到
shopeeProduct 之后、写档案之前做,并回写键发生变化的兄弟明细:新明细的到来
可能让原本安全的键变成歧义,不同步会造成同组一半旧键一半新键。
- 不碰人工或 AI 已确认的明细,与 Reparse 不带 force 时的规则一致。
- 重解析同样走消歧,否则它会把键写回塌缩形式、悄悄撤销导入时的拆分。
- 键比较与产出统一去空白:SYB 对括号前的空格写法不一致,否则同一规格的两种写法
会被误判为歧义。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-17 09:19:35 +08:00
QiuSW and Claude Opus 5
87a532f3bb
fix(server): 图搜后的规格匹配脱离 Agent 请求 ctx ( #300 )
...
匹配挂在 Agent 提交采集结果那个 HTTP 请求的 context 上。AI 匹配要几十秒到几分钟,
Agent 先超时断开,ctx 被取消,匹配当场中断,采购员还得手动点一次「一键匹配」。
线上 2026-09-16 实测六个商品里四个是这样死的(#294 的日志第一次派上用场):
shopee 28111/8544/5250/5259: archive spec match failed: context canceled
shopee 9214/26680: 成功——只是 Agent 尚未超时
- 用 context.WithoutCancel 派生,保留请求携带的值(trace 不断链),只切断取消
信号,再加 10 分钟上限兜底。
- 新增 specMatchAborted,与 unavailable 分开。ctx 取消是本端调度问题不是 Provider
故障,重试用的是同一个已死的 ctx,毫无意义。此前被归成「AI 匹配服务暂时不可用」,
线上出现过 unavailable=7 而 AI 服务完全正常,会一直误导排查。
- 全档案匹配遇到 aborted 立即早停,不再刷出一串同样的失败。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 17:20:11 +08:00
QiuSW and Claude Opus 5
8f337d5248
fix(server): 取消状态的 CHECK 约束改由 version-local 迁移应用 ( #297 )
...
约束修复原本放在 goautomigrations.Migrate 里,而 Migrate 只被 version-local 下的
迁移文件调用——那些文件在既有库上都已应用、会被跳过,于是修复永远不执行。
线上 2026-09-16 发布后实测:服务起来了、新代码在跑,ck_collection_task_status
却仍只认旧五个状态,一点取消就会被数据库拒绝。verify.go 里早有同样的警告:
「只把模型加进 migrations.Migrate 对已有数据库无效」。
单测能过是因为测试库是新建的,GORM 按模型标签直接建出含 cancelled 的约束;
既有库拿不到。
新增 1789700000000_collection_task_cancelled.go,并把约束函数导出供其调用。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 16:48:41 +08:00
QiuSW and Claude Opus 5
03647dd7dd
feat(web): 采集任务页增加来源筛选与「取消未开始的任务」按钮 ( #298 )
...
页面混显三种来源却只有 goodsId 和状态两个筛选,批量取消时无法限定范围。
- 服务端 AdminList 增加 source 过滤,非法值报参数错误而非静默忽略。
- 前端增加来源筛选与来源列;状态补 cancelled(info 色,取消不是错误,不与
failed 共用红色)。
- 「取消未开始的任务」按钮带实时数量,确认框列出将被取消的任务,并写明范围是
整个筛选条件而非当前页。hasMore 时提示还有未处理的任务。
`[必须]` 取消范围包含 goodsId。少了这一维,按 goods_id 筛出两条、按钮却取消
三十几条——那正是当初放弃「两个固定按钮」、改用「筛选 + 一个按钮」想避免的事。
BatchCancel 的 goodsId 与 AdminList 用同一种匹配方式,否则两边范围会悄悄错开。
实施:sonnet 子代理;goodsId 范围一致性由复核补入。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 16:39:07 +08:00
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude Opus 5
bb097a6a15
fix(android): 图搜归位的返回动作等页面稳定,推不动时清栈回首页 ( #292 )
...
BACK 分支按完就走、固定 300ms 盲等,不够 PDD 完成一次页面切换:抓到的是切换
动画中间态,判据全不命中,被归为 OTHER_PDD 后继续按。50 次预算在 15 秒内空转
耗尽,App 可能一次完整返回都没做完(真机任务 156)。
CLICK_CAMERA 分支早有 awaitPage,BACK 是唯一一个动作后不等待的分支,同一个坑
只修了一半。
- BACK 后等页面形态真的变化再判下一步。
- 连续 3 次推不动,改用 FLAG_ACTIVITY_CLEAR_TOP 清栈回首页。
- resetPddToHome 与 launchPddToForeground 分开:后者要保留深层页面,采集当前页
的流程正需要那个行为。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 11:46:04 +08:00
QiuSW and Claude Opus 5
2f1e88ea04
fix(web): 未关联 PDD 的 SYB 明细可勾选,打通图搜采集入口 ( #291 )
...
勾选框的 isSelectableCandidate 只有采购、采集、AI 匹配三个判据,三个都要求
虾皮商品已关联 PDD。图搜采集的用途恰恰是给未关联的商品找 PDD 商品,于是
入口对它最该服务的那类商品一直不可达,按钮计数恒为 0。
增加 isImageSearchCandidate,与 imageSearchRows 的过滤条件一致(有虾皮商品
且有参考图)。只放宽勾选,三个按钮的候选集合各自的判据不变,采购门禁不受影响。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 10:59:43 +08:00
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude Opus 5
2193762cbd
fix(agent): 相册最新项判定改用经典 sortOrder,修复 Android 12 ( #277 )
...
三星 SM-G9700(Android 12 / SDK 31)上图搜任务持续以
IMAGE_SEARCH_ASSET_NOT_LATEST 失败,而设备时钟正常、READ_EXTERNAL_STORAGE
已授予、相册里最新的照片还是两天前的——我们刚写入的图确实是最新的。
根因是查询参数形式。isMostRecent 用的是 Bundle 的
QUERY_ARG_SORT_COLUMNS / QUERY_ARG_LIMIT,老版本 MediaProvider 不认:
排序被忽略后 LIMIT=5 取回任意 5 行,我们刚写入的图不在其中,maxWith
自然不等于 ownId。同一台设备上用经典 sortOrder 字符串查询排序正常
(content query --sort 验证过),OnePlus PLY110(Android 16 / SDK 36)
两种形式都认,所以此前没暴露。
改用经典 sortOrder "date_added DESC, _id DESC",按游标读前
RECENCY_QUERY_LIMIT 行。LIMIT 不能写进 sortOrder:Android 11 起
MediaProvider 会以 Invalid token LIMIT 拒绝。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 17:51:27 +08:00
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude 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
QiuSW and Claude Opus 5
b4393dac84
fix(web): SYB 商品页按钮归位为 图搜采集 | 创建采集 | 创建采购 ( #280 )
...
图搜采集原先是个游离按钮,挂在工具栏下方的提示条之后,与另外两个
批量操作分离。按用户 2026-09-15 的要求移入工具栏,并把三个批量操作
按从轻到重排列:图搜采集 -> 创建采集 -> 创建采购。
只调整按钮位置与顺序,不改动 v-if、disabled 条件、点击处理和计数。
顺带去掉图搜按钮文案与计数之间多余的空格,与相邻两个按钮一致。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 15:07:04 +08:00
QiuSW and Claude Opus 5
c1ed3750d2
fix(agent): 声明 pdd.image-search.v1 能力 ( #277 )
...
服务端 taskCompatible 对 source='image_search' 的任务按该能力过滤,
但 AgentCapabilities.supported 从未包含它。设备不声明时图搜任务
永远不会被领取,而且是静默的——任务停在 pending、不失败也不报错。
线上设备 #7「采购2」的 capabilities_json 实测确实没有这一项,装机前
核对时发现。
新增 AgentCapabilitiesTest 锁住与服务端 task.ImageSearchCapability
的一致性,并检查清单无重复。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 14:50:59 +08:00
QiuSW
1d01186b8e
merge: SYB 图搜采集 (#275~#280) 与 SYB 规格解析修复 ( #284 )
...
图搜:批量创建(按 shopee_product 去重、上限 50)、任务下发与身份回填、
自动关联与跨币种/价格兜底、image_search_linked 只读标记(含采购视图)、
采集 > 图搜 的服务端排序、Agent 相册权限与图搜执行器。
#284:空格分隔的颜色尺码按逗号同规则拆分,修复线上 152 条 success 但
尺码为空的数据质量缺陷。
app/goauto/sybimport 的 12 个失败用例先于本次合并存在(#272/#274 遗留),
已记录为 #285,本次未新增。
2026-09-15 14:23:33 +08:00
QiuSW and Claude 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
QiuSW and Claude Opus 5
12e9aa8591
docs( #278 ): 同步 SYB 商品 PDD 图搜采集契约镜像
...
线上 Wiki 页面 Android-Agent-API-Contract 已更新并回读,
revision 4d1ba4423229;本次提交为 harness sync 生成的本地镜像。
新增章节记录:批量创建接口与去重、批次上限 50(作用于去重后条数、
不可 env 覆盖及其原因)、设备能力 pdd.image-search.v1、任务载荷只下发
imageUrl/mediaType/sizeBytes/sha256、复用 identify 回填身份、自动关联的
CAS 谓词与跨币种拒绝、image_search_linked 标记只读出现在采购视图且不参与
门禁、采集 > 图搜的 ORDER BY 及其必须落在服务端的原因、管理端与 Agent
两侧错误码表、Agent 执行边界(禁 OCR/VLM、订单确认页只返回、相册最新项
须跨图片与视频比较),以及 2026-09-15 真机核对的 PDD 界面判据与已知偏差。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 11:12:17 +08:00