From fd13b0824bb0e40b06c863beeb7ea16894e0d147 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:37:01 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-255.-.md | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 T-255.-.md diff --git a/T-255.-.md b/T-255.-.md new file mode 100644 index 0000000..94edb3e --- /dev/null +++ b/T-255.-.md @@ -0,0 +1,190 @@ + +> 同步来源:[`docs/tasks/T-255.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/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= recyclerMiss= rejClass= rejWidth= rejHeight= rejVisible= rejTexts= rejImage= rejPrice= rejClick= rejSignature= +``` + +`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`:通过。