Commit Graph
424 Commits
Author SHA1 Message Date
QiuSWandClaude 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
QiuSWandClaude Opus 5 c42e32d49d fix(#277): 首页判定必须由可点相机入口证明
真机核对(2026-09-15)发现个人中心 tab 的根节点同样带
content-desc='拍照搜索',覆盖全屏 [0,0][1080,2354] 且 clickable=false;
而 GoAutoAccessibilityService.visibleTexts() 会把 contentDescription 当
文本收集。原实现只按文案判 HOME,于是个人中心被判成首页,找不到可点入口
后走几何兜底,点到该页顶部最靠右的可点节点——实测那是「设置」按钮。

两处收紧:

- classify 增加 hasClickableCameraEntry,HOME 必须由可点入口证明。
- 几何兜底增加搜索栏证据(顶部区域 content-desc='搜索' 的节点),
  没有搜索栏就返回 null 而不是交出"最靠右的可点节点"。

补充回归测试:文案存在但无可点节点时必须判为 OTHER_PDD。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 11:04:03 +08:00
QiuSWandClaude Opus 5 95be6e7569 feat(agent): 接入 PDD 图搜自动化状态机 (#277)
- 新增 PinduoduoImageSearchAutomation:归位状态机(首页/图搜入口/结果页/
  重试弹窗/订单确认页/其它页面 -> 拍照搜索/返回/拉起应用),候选卡片挑选
  (跳过广告、容忍图片未加载),均以纯函数实现并覆盖单测;订单确认页只
  允许按返回归位,不点击任何支付/下单控件。
- 修复 PinduoduoImageSearchAssetStore.isMostRecent 只比较 jpeg 的问题:
  相册"最近项目"网格实测同时含图片和视频,现在查询 MediaStore.Files
  (图片+视频)交给 ImageSearchMediaRecencyPolicy 判定,若有更新的媒体
  排在前面则明确失败,不猜测、不往后找。
- AgentForegroundService 接入完整执行链路:下载参考图 -> 校验/放大 ->
  写入相册 -> 复核最新一项 -> 归位入口 -> 选相册第一张 -> 等结果页 ->
  跳过广告打开候选 -> 复用现有 CurrentPageIdentityRunner 分享链接识别 +
  PddProductDetailCollector 采集;任务结束(成功/失败)都清理写入的图片。
- 新增错误码 IMAGE_SEARCH_ASSET_NOT_LATEST 及对应人话文案。

真机点击序列、重试弹窗分支、结果页广告识别未经真机验证,仅纯判定逻辑
(ImageSearchRecoveryPolicy / ImageSearchCandidatePolicy /
ImageSearchMediaRecencyPolicy)有 JVM 单测覆盖。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 11:01:00 +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 ec2d86b1a8 Merge branch 'fix/c277-imagesearch' into integrate/imagesearch 2026-09-15 10:43:08 +08:00
QiuSWandClaude Opus 5 51265681c2 feat(android): 图搜任务资产准备、页面判据与调度垫底 #277
- PinduoduoImageSearchAssetStore 补全 sha256/大小/mediaType 校验之外的四点:
  写入前清理自身历史图片、最小边不足 960 时放大、写入后等待媒体库索引并做
  JPEG 头尾标记校验、isMostRecent() 判定自己是否为相册最新一张(不识别图片
  内容,只保证相册第一张即是本次图搜参考图)。纯逻辑部分拆到
  ImageSearchAssetPolicy,JVM 单测覆盖校验/放大边界。
- 新增 PinduoduoImageSearchCriteria:图搜入口页/结果页/"再试一次"弹窗/四列
  最近项目网格的纯文本判据,从同作者已在真机走通的旧项目移植常量,注释标注
  "待真机核对",集中存放便于后续按 dump 结果调整。
- TaskDispatchPolicy.decide 新增可选参数 hasImageSearchWaiting 与
  CHECK_IMAGE_SEARCH 分支,采购 > 采集 > 图搜;默认值保证既有四条分支行为
  逐字节不变,回归测试见 TaskDispatchPolicyTest。
- AgentForegroundService 的 image_search 分支:真实校验相册权限并给出
  IMAGE_SEARCH_PERMISSION_REQUIRED 等人话失败文案;点击入口/选图/等待结果页/
  打开候选的真实自动化状态机本次未接入执行路径,原因见工单——真机 dump 无
  障碍树前置门禁未执行,移植的选择器常量未经核对,不贸然接线。
- TaskHistoryFragment 采集列表行为 source == "image_search" 的任务加一行
  "来源:图搜自动匹配" 标记(#279),不改动既有 collection 布尔分叉结构。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 10:39:41 +08:00
QiuSWandClaude Opus 5 c1eb23e9b1 fix(android): 补充相册权限重新授权入口 #276
MainActivity.onCreate 里的相册权限申请是唯一入口,用户拒绝(尤其"不再询问")后
没有任何补救路径。新增 MediaPermissionPolicy(状态查询 + 跳转应用详情页),并
在"状态"页加相册权限卡片,显示当前状态并提供重新授权/跳转系统设置的入口,供
#277 图搜执行器复用同一查询。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 10:39:27 +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 a16e6920b7 docs(#277): record sanitized pdd home evidence 2026-09-15 10:07:27 +08:00
QiuSW def0a86f63 fix(#277): fail image search tasks explicitly until automation is available 2026-09-15 09:59:56 +08:00
QiuSW ba7d2a6b72 feat(#280): remember image search device 2026-09-15 09:57:17 +08:00
QiuSW a3cf98940a fix(#279): only auto-link active pdd products 2026-09-15 09:51:39 +08:00
QiuSW 56706a0b45 fix(#280): show image search association marker 2026-09-15 09:45:50 +08:00
QiuSW a4874091a8 feat: prepare validated image search media asset (#277) 2026-09-15 09:42:25 +08:00
QiuSW 9e0dcc254e feat: parse image search task metadata in agent (#277) 2026-09-15 09:40:51 +08:00
QiuSW cda1978bc6 test: prevent image snapshot payload leakage (#278) 2026-09-15 09:37:01 +08:00
QiuSW c80d0c91d5 feat: add image search batch dialog to syb products (#280) 2026-09-15 09:07:45 +08:00
QiuSW be491c7692 feat: expose image search batch API to web (#280) 2026-09-15 09:02:08 +08:00
QiuSW 90c1668fe3 test: cover image search auto-link guards (#279) 2026-09-15 09:01:10 +08:00
QiuSW 804448ede0 feat: show image search link marker in shopee detail (#279) 2026-09-15 08:59:37 +08:00
QiuSW 5a7e219230 feat: add image-search linked filter to shopee list (#279) 2026-09-15 08:58:35 +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 e01fcd9860 feat: request media permission for image search (#276) 2026-09-14 16:03:21 +08:00
QiuSW be03972779 merge: #283 purchase readiness consistency 2026-09-14 14:48:13 +08:00
QiuSW c137f5072f fix: keep purchase readiness consistent with confirmed mappings (#283) 2026-09-14 14:42:11 +08:00
QiuSW 82b4ebd406 feat(ops): add batch SYB failed reparse script (#282) 2026-09-14 11:34:36 +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 c24e8a4439 docs: refresh stale wiki mirrors picked up by the same sync run
Unrelated to #269. These three pages were changed online by other work and
the local mirror had drifted (02 and 03 from 2026-09-10, 06 from 2026-09-04).
The sync that mirrored the #269 pages regenerates every mapping, so it brought
these along; they are committed separately to keep the ticket's own commit
limited to its files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 17:15:38 +08:00
QiuSWandClaude Opus 5 40a254dc41 docs: mirror SYB product filter rules and admin API #269
Wiki updated online first, then mirrored:
- SYB-ERP-Interface-Contract@f1792b7f — filter order after shop admission,
  OR-not-AND with the 8498/6392/6386/8504 figures from the 2026-09-11
  production check, empty variationSku kept, per-rule hit counts, and the
  no-add/no-delete rule for structural criteria.
- Android-Agent-API-Contract@38869285 — the syb-product-filters endpoints,
  the server-side refusal of char add/delete, and the live lastHitCount
  requirement for the disable confirmation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 17:15:38 +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