1
T-255
ila edited this page 2026-08-07 16:37:01 +08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

同步来源:docs/tasks/T-255.md · commit afc651f75a3a


id: T-255 title: 为候选卡片过滤器增加逐条件拒绝计数与滚动后容器可见性观测 phase: 2 deps:

  • T-254 status: DOING created: 2026-07-30 context_ref: c488e50 work_branch: null write_paths:
  • docs/tasks/T-255.md
  • docs/current-state.md
  • android-buyer/app/src/main/**
  • android-buyer/app/src/test/**

问题 / 背景

T-254 的真机计数分布否证了三条静默跳过路径(specOpenFailed、orderConfirmRedirect 全为 0),把瓶颈定位到更上游的候选卡片识别:

轮次 cards distinct attempts scrolls resolved
1 2 1 1 4 1
2 2 1 1 4 0
3 2 1 1 1 0

事实推演:整轮只识别出一个不同签名的卡片;第一轮 5 次读取中约 4 次返回空,说明 滚动之后结果页读不到任何卡片。maxCandidateAttempts=10 只用掉 1 次——不是不想试, 是没有可试的候选。而人工在同一台设备、同一张参考图下确认结果页存在多个合格商品。

BuyerAccessibilityService 的候选卡片提取(约 :1023-1069)要求同时满足九个条件, 任一不满足即 continue 丢弃,外部完全无法区分是哪一条否决的:

# 条件
1 className 为 FrameLayout / ViewGroup
2 宽度占 recycler 的 MIN_CARD_WIDTH_PERCENT(35)–MAX_CARD_WIDTH_PERCENT(60)
3 高度 ≥ 屏高 1/8
4 可见面积 ≥ MIN_VISIBLE_CARD_PERCENT(80)
5 语义文本条数 ≥ MIN_CANDIDATE_TEXTS(2)
6 存在可见且启用的 ImageView
7 存在价格语义文本
8 找得到安全点击目标
9 PinduoduoStableProductIdentityPolicy.signature 非空

第 4 和第 9 条嫌疑最大(网格首尾行被裁切;图搜卡片标题过短导致 PinduoduoObservedTitlePolicy.select() 返回 null),但无法从代码断定。此外 recycler 容器本身的识别(MIN_MAIN_RECYCLER_NODES(20)、 MIN_MAIN_RECYCLER_WIDTH_PERCENT(90))在滚动后是否仍然成立,同样不可观测。

本任务与 T-254 同性质:只做可观测性,不调任何阈值,先拿到拒绝分布再决定改什么。 T-254 边界中「如计数分布显示签名冲突嫌疑再另开任务」的触发条件已满足。

关联需求与交互

  • 功能:F-005、F-007
  • 用户故事:US-004、US-006
  • 交互:沿用候选搜索进度与任务详情,不新增采购操作
  • 架构/API:复用 T-254 的诊断快照与事件 message 机制,后端零改动

行为契约

一、逐条件拒绝计数

候选卡片提取每次调用产出一份拒绝分布,按第一个不满足的条件归类(短路归因, 一个容器只计一次,避免重复计数):

键 对应条件
rejClass 1 className
rejWidth 2 宽度占比
rejHeight 3 最小高度
rejVisible 4 可见面积
rejTexts 5 语义文本条数
rejImage 6 无 ImageView
rejPrice 7 无价格语义
rejClick 8 无安全点击目标
rejSignature 9 签名为空

另需 containers:本次遍历到的候选容器总数(进入九条件判定前的基数)。 containers 减去九项拒绝之和,应等于本次返回的卡片数,该恒等式必须有单测保证。

二、recycler 容器可见性

增加 recyclerFound:本次提取是否成功识别出主 recycler 容器。识别失败时九项拒绝 计数全为 0 且 containers=0——这两种「零卡片」原因必须能区分。

3、诊断汇总口径

以上计数在 PinduoduoCandidateAutomation 单轮内累计,并入 T-254 的 CandidateSearchDiagnostics,按固定顺序追加到既有 11 个键之后:

containers=<n> recyclerMiss=<n> rejClass=<n> rejWidth=<n> rejHeight=<n> rejVisible=<n> rejTexts=<n> rejImage=<n> rejPrice=<n> rejClick=<n> rejSignature=<n>

recyclerMiss 为本轮内 recyclerFound == false 的读取次数。

约束沿用 T-254:全部为非负整数、缺失写 0;诊断只进 message 不进 reason; 不得写入任何商品标题、店铺名、goods_id、链接或节点原始文本——只要计数,不要内容。

四、既有行为不变

九个过滤条件的判定逻辑、阈值常量和先后顺序一律不动,只在既有 continue 前 记录归因。卡片提取的返回值、limit 语义、点击目标选择必须逐字节等价。

方案

  1. BuyerAccessibilityService 候选卡片提取:把九个条件的 continue 改为先记归因 再 continue;新增 recycler 识别成败标记;产出一份不可变拒绝分布随卡片列表返回。
  2. PinduoduoCandidateDriver / BuyerAccessibilityBridge:扩展 candidateCards 的 返回类型以携带拒绝分布,保持既有调用点语义不变。
  3. PinduoduoCandidateAutomation:在 readCandidateCards 处累计分布,reset() 清零, 并入 diagnostics() 快照。
  4. CandidateSearchDiagnostics:追加 11 个字段与 auditSuffix() 输出段,保持既有 11 个键的顺序和格式不变。
  5. 单测覆盖:九类拒绝各自归因正确、短路只计一次、恒等式成立、recycler 识别失败与 容器全被拒绝两种零卡片可区分、诊断 message 追加格式、既有 11 键输出不变。

验收要点

  • 九类拒绝原因各有单测,构造对应的节点条件后只有目标计数递增。
  • 恒等式 containers - Σrej = 返回卡片数 有单测保证。
  • recycler 识别失败与「容器都被过滤掉」两种零卡片场景产出不同的诊断分布。
  • 诊断 message 中 T-254 既有 11 个键的顺序、名称和取值逐字节不变。
  • 诊断 message 不含标题、店铺名、goods_id、链接或任何节点原始文本。
  • 九个过滤条件的阈值常量和判定顺序未改变,卡片提取返回值等价,有回归测试。
  • Android Debug/Release 单测、lintDebug、assembleDebug、assembleRelease 全部通过。
  • 真机复跑一条采购任务,从 Admin 读到三轮的完整拒绝分布并记入执行记录。

边界

只加观测,不调阈值:

  • 不改 MIN_CARD_WIDTH_PERCENT、MAX_CARD_WIDTH_PERCENT、MIN_VISIBLE_CARD_PERCENT、 MIN_CANDIDATE_TEXTS、MIN_MAIN_RECYCLER_NODES、MIN_MAIN_RECYCLER_WIDTH_PERCENT 或最小高度算式。
  • 不改 PinduoduoObservedTitlePolicy 的取词规则和 6–160 字符范围。
  • 不改 PinduoduoStableProductIdentityPolicy 的签名算法。
  • 不改滚动策略、三轮预算、去重行为或成功判定式。
  • 不改 T-254 的诊断字段语义,不改 T-256 的候选提交门槛。
  • 不改后端代码、数据库、API 契约或 Admin 模板。
  • 不启动真实下单,不进入订单提交或支付。
  • 本任务不修复识别率,只产出定位依据;修复另开任务。

执行记录

2026-07-30(DOING)

  • 修改:将任务状态设为 DOING。在 BuyerAccessibilityService 的既有九个过滤 continue 前增加短路拒绝归因,保留原判断表达式、阈值常量、先后顺序、卡片字段、 limit 截断和安全点击目标选择;新增每次读取的 containers、recyclerFound 和 九类拒绝计数。未改任何条件判断结果。
  • 修改:通过 BuyerAccessibilityBridge 和 PinduoduoCandidateDriver 传递不可变的 PinduoduoCandidateCardRead,候选卡片列表保持原顺序和内容;订单 dry-run 调用点 仅取同一列表,行为不变。
  • 修改:PinduoduoCandidateAutomation 在每次实际读取中累计容器、recycler miss 和 九类拒绝计数,reset() 清零,并将字段追加到既有 11 个诊断键之后;诊断只含计数, 未写入节点原始文本、标题、店铺名、goods_id 或链接。
  • 修改:补充诊断格式、非负计数、容器恒等式和自动化汇总相关单测;未修改 MainActivity.kt、CandidateCollectionPolicy.kt 或 backend-api。
  • 环境:Windows,工作目录 D:\chengma\cmroubao\android-buyer,Gradle wrapper, Android SDK processing warning(SDK XML v4,而当前工具最多理解 v3)仅为警告。
  • 命令与结果:
    • .\gradlew.bat testDebugUnitTest --no-daemon:首次执行失败,245 个测试中 1 个 既有期望未包含新增的 containers 值;修正测试 fixture 后重跑通过,245 个测试 全部通过。
    • .\gradlew.bat testReleaseUnitTest --no-daemon:通过。
    • .\gradlew.bat lintDebug --no-daemon:通过,生成 app/build/reports/lint-results-debug.html。
    • .\gradlew.bat assembleDebug --no-daemon:通过。
    • .\gradlew.bat assembleRelease --no-daemon:通过。
    • 未运行 init.ps1,未运行真机采购任务、未进入订单提交或支付页面。
  • 决策:拒绝计数按现有条件链的第一个失败条件归类;首个原有可见/启用/className 短路分支归入 rejClass,宽度/高度共用的原有短路分支按宽度优先、否则高度归类。 这样只增加归因记录,不改变原条件的求值和返回卡片列表。
  • Blocker / 未验证:最终验收要求的真机复跑(从 Admin 读取三轮完整拒绝分布)无法由 本次执行完成;需采购人员在已授权 PKG110 和拼多多 8.17.0 测试环境中执行并补充 三轮脱敏计数证据。因此任务保持 DOING,未标记 DONE。
  • 最终树复核:新增 3 个单测后再次执行以下原命令,Debug/Release 各 248 个测试且 failures=0;lintDebug、assembleDebug、assembleRelease 均为 BUILD SUCCESSFUL:
    • .\gradlew.bat testDebugUnitTest --no-daemon:通过,248/248。
    • .\gradlew.bat testReleaseUnitTest --no-daemon:通过,248/248。
    • .\gradlew.bat lintDebug --no-daemon:通过。
    • .\gradlew.bat assembleDebug --no-daemon:通过。
    • .\gradlew.bat assembleRelease --no-daemon:通过。