docs: import wiki at afc651f75a3a
+190
@@ -0,0 +1,190 @@
|
||||
<!-- docs-wiki-sync:docs/tasks/T-255.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||
> 同步来源:[`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=<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`:通过。
|
||||
Reference in New Issue
Block a user