docs: import wiki at afc651f75a3a
+393
@@ -0,0 +1,393 @@
|
||||
<!-- docs-wiki-sync:docs/tasks/T-263.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||
> 同步来源:[`docs/tasks/T-263.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-263.md) · commit `afc651f75a3a`
|
||||
|
||||
---
|
||||
id: T-263
|
||||
title: 以页面结构判定支付边界并按页面类型收敛文本规则
|
||||
phase: 2
|
||||
deps:
|
||||
- T-262
|
||||
status: DOING
|
||||
created: 2026-07-31
|
||||
context_ref: 0c2e423
|
||||
work_branch: null
|
||||
write_paths:
|
||||
- docs/tasks/T-263.md
|
||||
- docs/current-state.md
|
||||
- android-buyer/app/src/main/**
|
||||
- android-buyer/app/src/test/**
|
||||
---
|
||||
|
||||
## 问题 / 背景
|
||||
|
||||
T-262 采集的四个真机样本证明:**现行「见到支付关键词即停」的判定,在误报和漏报两端
|
||||
同时失效。**
|
||||
|
||||
| 页面 | Activity | 节点数 | 非空文本 | WebView | `order_checkout` | paymentMarkers |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 首页 | `MainFrameActivity` | 227 | 48 | 0 | 未命中 | 无 |
|
||||
| 我的优惠券 | `NewPageActivity` | 457 | 205 | 2(占满屏) | 未命中 | 无 |
|
||||
| 商品规格弹层 | `NewPageActivity` | 147–153 | 多 | 0 | 未命中 | **2 条 → 误报** |
|
||||
| **订单确认/去支付** | `NewPageActivity` | **11** | **0** | **1(占满屏)** | 未命中 | **0 → 漏报** |
|
||||
|
||||
### 两端同时失效
|
||||
|
||||
**误报**:商品规格弹层上的「使用#微信支付,更换先用后付可0元下单」(促销横幅)和
|
||||
「提交订单 ¥14.9」(底部 CTA)被子串匹配命中。这是**正常购物页面**,自动化必须能在
|
||||
上面读取规格,却被拦在第一步,导致连续多次真机任务三轮全败。
|
||||
|
||||
**漏报**:真实结算页**一条可见文本都没有**,`order_checkout` 也未命中。现行规则
|
||||
**完全无法识别它**——之所以从未出事,是因为自动化根本走不到那一步。
|
||||
|
||||
### 根本原因:混淆了「在支付页上」与「看得见通往支付的按钮」
|
||||
|
||||
商品规格弹层上本来就有结算入口按钮,这是正常的。自动化需要停止的是**已经身处支付
|
||||
页面**,而不是**看见了支付相关字样**。文本关键词无法区分这两者。
|
||||
|
||||
### 决定性证据:WebView 本身不阻断无障碍
|
||||
|
||||
优惠券页同为占满屏 WebView,其 **205 条文本全部可读**;结算页的 WebView **一条读不
|
||||
到**。因此「结算页读不到内容」是该页面特有性质(很可能是支付页的无障碍防护),
|
||||
而非 WebView 的普遍限制。
|
||||
|
||||
据此,「**存在占满屏 WebView 且语义文本数近乎为零**」在四个样本中**唯有结算页命中**。
|
||||
|
||||
## 关联需求与交互
|
||||
|
||||
- 功能:F-005、F-007
|
||||
- 用户故事:US-004、US-006
|
||||
- 交互:不新增采购操作
|
||||
- 架构/API:沿用 T-262 归因诊断,**后端零改动**
|
||||
|
||||
## 行为契约
|
||||
|
||||
### 一、总原则:只增强,不削弱
|
||||
|
||||
新判据是**追加**,不是替换。三条判据任一成立即判 `PAYMENT_BOUNDARY`:
|
||||
|
||||
```
|
||||
结构判据(新) 或 hasCheckoutRoot(保留) 或 文本判据(收敛后保留)
|
||||
```
|
||||
|
||||
`paymentMarkers` 列表内容与顺序**不得删改**。`hasCheckoutRoot` 规则**不得删改**。
|
||||
|
||||
### 二、结构判据(新增主判据)
|
||||
|
||||
同时满足以下全部条件时判定为支付边界:
|
||||
|
||||
1. 前台包名为拼多多;
|
||||
2. 存在**占满屏**的 `WebView`(宽高各不小于屏幕的 90%);
|
||||
3. 可见语义文本数**不超过阈值**(常量定义,初值 `2`,覆盖极少量装饰性文本);
|
||||
4. **连续 `PAYMENT_STRUCTURE_CONFIRMATIONS` 次快照均满足上述条件**,初值 `3`。
|
||||
|
||||
第 4 条是硬要求。任何普通页面在 WebView 尚未渲染时都会短暂呈现「WebView + 零文本」,
|
||||
单次命中即判定会把**页面加载瞬间**误判为结算页。参照既有 `unknownPageLimit`
|
||||
(20 次 × 200 毫秒)的模式,不得单次命中即停。
|
||||
|
||||
连续计数在页面结构不再满足条件时立即清零,不得跨页面累积。
|
||||
|
||||
### 三、文本判据按页面类型收敛
|
||||
|
||||
**仅当页面未被正面识别为已知安全页面类型时**,文本关键词命中才强制停止。
|
||||
|
||||
- 页面已被分类器正面识别为商品详情、规格弹层、搜索结果、图片搜索结果或首页时,
|
||||
文本命中**不再强制停止**,但必须完整记录(见第四节)。
|
||||
- 页面为 `UNKNOWN` 或任何未正面识别的类型时,文本命中**维持现有强制停止行为**。
|
||||
|
||||
依据:在已确认的正常购物页面上出现支付字样属预期现象(促销横幅、结算入口按钮);
|
||||
在无法识别的页面上出现支付字样则是危险信号,必须保守处理。
|
||||
|
||||
**登录、验证码、风控三类安全停止的判定与优先级完全不变**,不受本节影响。
|
||||
|
||||
### 四、可审计:记录被放行的文本命中
|
||||
|
||||
新增诊断计数,追加在 T-262 五个键之后:
|
||||
|
||||
```
|
||||
payStructureHit=<0|1> payStructureStreak=<n> payTextSuppressed=<n>
|
||||
```
|
||||
|
||||
| 键 | 含义 |
|
||||
| --- | --- |
|
||||
| `payStructureHit` | 本轮是否由结构判据触发过支付边界 |
|
||||
| `payStructureStreak` | 结构条件连续命中的最大次数 |
|
||||
| `payTextSuppressed` | 因页面被正面识别而**未强制停止**的文本命中次数 |
|
||||
|
||||
`payTextSuppressed` 是本次变更的安全审计口径:**它记录了在旧规则下会停止、新规则下
|
||||
放行的次数**,用于事后评估变更影响。若真机上该值异常偏高,说明收敛过度,需回头收紧。
|
||||
|
||||
沿用既有脱敏口径:只记计数,不记页面文本或商品信息。
|
||||
|
||||
### 五、回归测试必须基于真实样本
|
||||
|
||||
必须用 T-262 记录的四个真机样本构造测试夹具,逐一断言判定结果:
|
||||
|
||||
| 样本 | 期望 |
|
||||
| --- | --- |
|
||||
| 首页(227 节点 / 48 文本 / 0 WebView / 无命中) | 不判支付边界 |
|
||||
| 优惠券页(457 / 205 / 2 WebView / 无命中) | 不判支付边界 |
|
||||
| 商品规格弹层(147 / 多文本 / 0 WebView / 2 条命中) | **不判支付边界**(本任务修复目标) |
|
||||
| 结算页(11 / 0 文本 / 1 占满屏 WebView / 无命中) | **判支付边界**(连续 3 次后) |
|
||||
|
||||
另需覆盖:结算页结构仅命中 1 次和 2 次时**不得**判定;`hasCheckoutRoot` 单独命中时
|
||||
仍判定;`UNKNOWN` 页面文本命中时仍判定;登录/验证码/风控优先级不变。
|
||||
|
||||
### 六、既有行为不变
|
||||
|
||||
- 不改登录、验证码、风控的检测与优先级。
|
||||
- 不改 `paymentMarkers` 内容与顺序、`containsAny` 实现、`hasCheckoutRoot` 规则。
|
||||
- 不改 T-262 五个归因键的顺序、名称与取值,也不改 T-254/T-255/T-257/T-260/T-261
|
||||
既有诊断键。
|
||||
- 不改候选识别、签名算法、跨轮去重、SKU 硬约束、价格解析、三轮预算或成功判定式。
|
||||
- 不改下单 dry-run、订单提交、授权价格策略或支付边界之后的处置逻辑。
|
||||
|
||||
## 方案
|
||||
|
||||
1. `PinduoduoPageClassifier` 增加结构判据计算(占满屏 WebView + 文本数阈值),
|
||||
阈值与连续次数以具名常量定义。
|
||||
2. 连续确认状态由调用方持有并传入,分类器保持无副作用的纯函数特性;结构不满足时清零。
|
||||
3. 文本判据增加「页面已正面识别」的抑制条件,抑制时记录计数而非停止。
|
||||
4. 三个新诊断键并入 `CandidateSearchDiagnostics`,追加在 T-262 键之后。
|
||||
5. 用四个真机样本构造测试夹具并逐一断言,另覆盖连续次数边界与各优先级场景。
|
||||
|
||||
## 验收要点
|
||||
|
||||
- [ ] 四个真机样本的判定结果与上表逐一相符。
|
||||
- [ ] 结算页结构连续命中不足 3 次时不判定,达到 3 次后判定。
|
||||
- [ ] 结构条件中断后连续计数清零,不跨页面累积。
|
||||
- [ ] `hasCheckoutRoot` 单独命中仍判定支付边界。
|
||||
- [ ] `UNKNOWN` 页面上文本命中仍强制停止。
|
||||
- [ ] 登录、验证码、风控的判定与优先级未变,有回归测试。
|
||||
- [ ] `paymentMarkers` 内容与顺序未删改。
|
||||
- [ ] 三个新诊断键出现且不含页面文本或商品信息。
|
||||
- [ ] T-254/T-255/T-257/T-260/T-261/T-262 既有诊断键顺序、名称、取值不变。
|
||||
- [ ] Android Debug/Release 单测、`lintDebug`、`assembleDebug`、`assembleRelease` 全部通过。
|
||||
- [ ] 真机验证:同一商品重跑采购任务**不再**被拦在 `pdd_open_app`,能进入候选采集;
|
||||
并从 Admin 读取 `payTextSuppressed` 确认放行次数在合理范围。
|
||||
|
||||
## 边界
|
||||
|
||||
- **不删除任何现有判据**:`paymentMarkers`、`hasCheckoutRoot` 全部保留。
|
||||
- 不改登录、验证码、风控的检测逻辑与优先级。
|
||||
- 不改支付边界判定成立**之后**的处置逻辑(立即停止、不清理、不点击)。
|
||||
- 不改 T-261 清理逻辑与 `isSafeForCleanup`。
|
||||
- 不改 T-256 提交门槛、T-258 后端校验、T-259 安全停止补报、T-260 节奏配置。
|
||||
- 不改候选识别、价格解析或下单相关任何逻辑。
|
||||
- 不改后端代码、数据库、API 契约或 Admin 模板。
|
||||
- 不处理订单列表页可能被自身规则阻挡的问题(见 T-262 附带发现),另行开任务。
|
||||
|
||||
## 已知局限
|
||||
|
||||
**真实结算页样本只有一个。** 结构判据基于该单一样本设计,可能存在盲区:
|
||||
|
||||
- 若某些拼多多版本或路径使用**原生结算页**(有可读文本),结构判据不会命中。此时
|
||||
仍依赖 `hasCheckoutRoot` 与 `UNKNOWN` 页面上的文本判据兜底。
|
||||
- `order_checkout` 这个 resourceId 在本次采集的结算页上**并未命中**,其在当前版本
|
||||
上的有效性存疑,但按「只增强不削弱」原则仍予保留。
|
||||
|
||||
因此本任务**不宣称覆盖全部支付页面形态**,只解决已证实的误报并补上已证实的漏报。
|
||||
后续若发现新的结算页形态,应继续追加判据而非放松现有判据。
|
||||
|
||||
## 执行记录
|
||||
|
||||
开工后记录修改、命令、结果、环境、决策和 blocker。
|
||||
|
||||
### 2026-07-31 实现与自动化验证
|
||||
|
||||
环境:WSL 无 Android SDK/JDK,全部构建与单测经
|
||||
`/mnt/c/Windows/System32/cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat <目标> --no-daemon --console=plain"`
|
||||
在 Windows 侧执行;未运行 `init.ps1`。
|
||||
|
||||
#### 改动文件
|
||||
|
||||
- `android-buyer/app/src/main/java/com/roubao/autopilot/workflow/WorkflowModels.kt`:
|
||||
`PaymentBoundaryAttribution` 追加三个字段——`structureHit`(本次
|
||||
`PAYMENT_BOUNDARY` 是否由结构判据触发)、`structureConfirmations`(产出本次
|
||||
归因时的结构连续确认次数)、`textSuppressedCount`(本次判定时因页面被正面
|
||||
识别而放行的命中文本条数);均为末尾追加、默认值 `false`/`0`/`0`,`init` 增
|
||||
加对应非负校验。既有五个字段与其含义/顺序未动。
|
||||
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoPageClassifier.kt`:
|
||||
- 新增具名常量 `PAYMENT_STRUCTURE_TEXT_THRESHOLD = 2`、
|
||||
`PAYMENT_STRUCTURE_CONFIRMATIONS = 3`、私有 `FULL_SCREEN_WEBVIEW_RATIO = 0.9`。
|
||||
- `classify()` 追加末尾可选参数 `priorStructureConfirmations: Int = 0`(调用方
|
||||
持有并传入的连续确认状态,分类器本身仍是无副作用纯函数);新增私有函数
|
||||
`hasFullScreenWebViewWithFewTexts()`:屏幕范围取当前可见元素包围盒(根容器
|
||||
通常等于屏幕大小,避免新增屏幕尺寸入参改动既有调用方签名),WebView 宽高
|
||||
各不小于该包围盒的 90% 且可见非空文本数不超过阈值时判定单次快照满足结构
|
||||
条件;`structureConfirmations = if (met) prior+1 else 0`,
|
||||
`structureHit = structureConfirmations >= PAYMENT_STRUCTURE_CONFIRMATIONS`。
|
||||
- 将原来紧跟在 `visibleTexts` 之后计算的 `page` 判定块整体上移到
|
||||
`safetyStop` 判定之前(`page` 的计算只依赖 `hasCheckoutRoot`/`normalized`/
|
||||
`visibleElements` 等既有局部变量,与 `safetyStop` 无关,纯粹重排不改变
|
||||
`page` 本身的判定逻辑),使文本判据可以引用已确定的 `page` 值。
|
||||
- `safetyStop` 表达式改为
|
||||
`structureHit || hasCheckoutRoot || (textMarkerHit && !textMarkerSuppressed)`,
|
||||
其中 `textMarkerHit = visibleTexts.containsAny(paymentMarkers)`(`containsAny`
|
||||
实现、`paymentMarkers` 内容与顺序、`hasCheckoutRoot` 规则逐字节未动);
|
||||
`textMarkerSuppressed = textMarkerHit && page in {PRODUCT_DETAIL,
|
||||
SPECIFICATION_PANEL, SEARCH_RESULTS, IMAGE_SEARCH_RESULTS, HOME}`。登录/
|
||||
验证码/风控三类判定仍在同一个 `when` 的前三个分支,优先级和触发逻辑未动;
|
||||
结构/文本判据只影响 `LoginBlocker.NONE/UNKNOWN` 分支内部。
|
||||
- `PinduoduoUiSnapshot` 追加两个字段:`structureConfirmations: Int = 0`(本
|
||||
次调用后的连续确认计数,供调用方在下一次调用时回传)、
|
||||
`textMarkerSuppressed: Boolean = false`(本次快照是否发生文本命中放行,
|
||||
与 `PAYMENT_BOUNDARY` 是否触发无关,始终计算,供调用方做会话级审计)。
|
||||
- `computePaymentBoundaryAttribution()` 追加三个入参并透传进
|
||||
`PaymentBoundaryAttribution` 的三个新字段,其余标记扫描逻辑未动。
|
||||
- `android-buyer/app/src/main/java/com/roubao/autopilot/accessibility/BuyerAccessibilityService.kt`:
|
||||
新增私有实例字段 `pinduoduoStructureConfirmations`;`classifyPinduoduoRoot()`
|
||||
(全仓库唯一的真机快照来源,其余 `snapshotPinduoduoUi()`、
|
||||
`clickPinduoduoSearchEntry()` 等数十处调用均经此函数)将该字段作为
|
||||
`priorStructureConfirmations` 传入 `classify()`,再用返回快照的
|
||||
`structureConfirmations` 回写该字段,从而让结构判据的连续确认覆盖所有轮询
|
||||
循环与一次性动作调用;`snapshotPinduoduoUi()` 在前台包名不是拼多多的分支
|
||||
显式清零该字段,确保离开拼多多前台或结构条件中断时立即清零、不跨页面累积。
|
||||
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateAutomation.kt`:
|
||||
新增两个会话内累计字段 `maxStructureConfirmations`、`textSuppressedCount`
|
||||
(`reset()` 一并清零);新增私有 `observeSnapshot()` 作为本类唯一的快照读取
|
||||
入口(原有约 11 处 `driver.snapshot()` 全部替换为该函数),每次快照后更新
|
||||
两个累计字段的最大值/计数,不改变 `driver.snapshot()` 的返回值或任何既有
|
||||
判定/重试/清理行为;`diagnostics()` 追加
|
||||
`payStructureStreak = maxStructureConfirmations`、
|
||||
`payTextSuppressed = textSuppressedCount`。
|
||||
- `android-buyer/app/src/main/java/com/roubao/autopilot/procurement/CandidateSearchDiagnostics.kt`:
|
||||
末尾追加 `payStructureHit`/`payStructureStreak`/`payTextSuppressed` 三个字段
|
||||
(默认值 `0`)及对应 `require` 校验;`withPaymentBoundaryAttribution()` 追加
|
||||
设置 `payStructureHit`(直接取自本次归因),`payStructureStreak` 改为
|
||||
`maxOf(既有值, 归因中的 structureConfirmations)`(取大合并,不覆盖候选自动化
|
||||
自身轮询中已观察到的更高计数);`payTextSuppressed` **不**在此方法中设置,
|
||||
完全由调用方(`PinduoduoCandidateAutomation.diagnostics()`)持续累积后写入,
|
||||
避免被本方法在未触发 `PAYMENT_BOUNDARY` 的常见场景下清零或覆盖(原因见下方
|
||||
「偏离与决策」第 2 条)。既有五个键与 `auditSuffix()` 中它们之前的所有键均
|
||||
逐字节未动,三个新键追加在 `payExact=` 之后。
|
||||
- 测试:
|
||||
- `PinduoduoPageClassifierTest.kt` 新增 9 个用例:基于 T-262 四个真机样本的
|
||||
首页/优惠券页/规格弹层/结算页判定(规格弹层用例直接复现 T-262 记录的误报
|
||||
文案,断言不再判定且 `paymentBoundaryAttribution` 保持默认值)、结算页
|
||||
结构连续命中 1/2 次不判定、第 3 次判定、结构中断后清零再重新计数、
|
||||
`hasCheckoutRoot` 单独命中且结构计数为 0 时仍判定、`UNKNOWN` 页面文本命中
|
||||
仍强制停止、登录/验证码/风控在已有结构连续计数时优先级不变。
|
||||
- `CandidateSearchDiagnosticsTest.kt` 更新 6 处既有精确匹配断言以包含三个新
|
||||
增尾部默认键,新增 4 个用例覆盖非法计数拒绝、结构命中归因追加、
|
||||
`payStructureStreak` 取大合并两种方向、`payTextSuppressed` 不被
|
||||
`withPaymentBoundaryAttribution` 清零。
|
||||
- `PinduoduoCandidateAutomationTest.kt` 的 `FakeCandidateDriver` 新增
|
||||
`structureConfirmations`/`textMarkerSuppressed` 两个可选构造参数并透传进
|
||||
快照;新增一个用例验证候选自动化经 `observeSnapshot()` 累积的
|
||||
`payStructureStreak`/`payTextSuppressed` 出现在 `diagnostics()` 中,且
|
||||
`reset()` 后归零。
|
||||
|
||||
#### 命令与结果
|
||||
|
||||
```
|
||||
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testDebugUnitTest --no-daemon --console=plain"
|
||||
BUILD SUCCESSFUL;Debug 单测 313 项,0 失败、0 错误(较 T-262 的 299 项新增 14 项)。
|
||||
|
||||
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testReleaseUnitTest --no-daemon --console=plain"
|
||||
BUILD SUCCESSFUL;Release 单测 313 项,0 失败、0 错误。
|
||||
|
||||
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat lintDebug --no-daemon --console=plain"
|
||||
BUILD SUCCESSFUL。
|
||||
|
||||
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleDebug --no-daemon --console=plain"
|
||||
BUILD SUCCESSFUL。
|
||||
|
||||
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleRelease --no-daemon --console=plain"
|
||||
BUILD SUCCESSFUL。
|
||||
```
|
||||
|
||||
五个目标全部通过。
|
||||
|
||||
#### 四个真机样本的断言结果(单测,非真机)
|
||||
|
||||
| 样本 | 断言 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 首页(无 WebView、46+2 条文本、无标记命中) | `page=HOME`,`safetyStopReason=null`,`structureConfirmations=0` | 通过 |
|
||||
| 优惠券页(2 个占满屏 WebView、205 条文本、无标记命中) | `safetyStopReason=null`,`structureConfirmations=0`(文本数量守住占满屏 WebView 不误判) | 通过 |
|
||||
| 商品规格弹层(0 WebView、命中“提交订单”“微信支付”2 条) | `page=SPECIFICATION_PANEL`,`safetyStopReason=null`(**本任务修复目标**),`paymentBoundaryAttribution` 为默认值,`textMarkerSuppressed=true` | 通过 |
|
||||
| 结算页(1 个占满屏 WebView、0 条文本) | 连续 1/2 次快照 `safetyStopReason=null`;第 3 次 `safetyStopReason=PAYMENT_BOUNDARY`,`paymentBoundaryAttribution.structureHit=true`、`structureConfirmations=3`、`checkoutRootHit=false`、`markerIndex=-1` | 通过 |
|
||||
|
||||
另覆盖:结构条件中断后连续计数清零、`hasCheckoutRoot` 单独命中仍判定、
|
||||
`UNKNOWN` 页面文本命中仍强制停止、登录/验证码/风控在已有结构连续计数时优先级
|
||||
不变——均通过。
|
||||
|
||||
#### 偏离任务文档之处及理由
|
||||
|
||||
1. **结构判据的“屏幕范围”不是新增入参,而是取当前可见元素包围盒。** 任务文档
|
||||
要求“宽高各不小于屏幕的 90%”,但 `classify()` 现有签名不含屏幕尺寸,
|
||||
`PinduoduoUiElement` 也不含屏幕尺寸字段。改为在可见元素集合内取
|
||||
`boundsLeft/Top/Right/Bottom` 的包围盒作为屏幕范围的近似——真机根容器
|
||||
(`FrameLayout`)的 bounds 本就等于屏幕大小(T-262 结算页 dump 亦如此:
|
||||
`FrameLayout bounds=[0,0][1080,2376]`),且无障碍节点树采集时根节点必然
|
||||
被收录(`BuyerAccessibilityService.collectNodes()` 用 BFS 且根节点最先入
|
||||
队)。这样既满足“宽高各不小于屏幕的 90%”的判定意图,又不改变 `classify()`
|
||||
/ `PinduoduoUiElement` 对既有调用方的签名,风险更低。
|
||||
2. **`payTextSuppressed` 的口径改为调用方持续累积,而非像 T-262 五个键那样只
|
||||
在 `PAYMENT_BOUNDARY` 触发时产出。** 这是本任务中分量最重的一个偏离,
|
||||
理由:文本判据被放行(`textMarkerSuppressed=true`)几乎总是意味着
|
||||
`PAYMENT_BOUNDARY` **没有**触发(正是本任务要修复的常见场景——自动化在
|
||||
规格弹层等页面继续正常浏览)。若沿用 T-262 的“只在触发时产出”模式,
|
||||
`payTextSuppressed` 在绝大多数真实场景下都会读到默认值 `0`,验收要点
|
||||
“若真机上该值异常偏高,说明收敛过度”将无法被观察到,审计口径名存实亡。
|
||||
经推演证实:`hasCheckoutRoot` 恒定强制 `page=ORDER_CONFIRMATION`(不在可
|
||||
抑制页面集合内),结构判据要求非空文本数 `<=2` 而可抑制的五类页面
|
||||
(HOME/SEARCH_RESULTS/IMAGE_SEARCH_RESULTS/PRODUCT_DETAIL/
|
||||
SPECIFICATION_PANEL)现有识别规则均需要 `>=2` 个独立于标记文本之外的
|
||||
语义文本,二者在现有启发式下几乎互斥——即“归因触发时刻”与“文本判据被
|
||||
放行”事实上不会同时发生。因此改为:`PinduoduoUiSnapshot` 新增
|
||||
`textMarkerSuppressed`(与 `structureConfirmations` 一样,每次快照都计算,
|
||||
不依赖 `PAYMENT_BOUNDARY` 是否触发);`PinduoduoCandidateAutomation` 通过
|
||||
新增的唯一快照入口 `observeSnapshot()` 在自身轮询过程中持续累积
|
||||
`textSuppressedCount`(会话内计数,`reset()` 清零),随
|
||||
`diagnostics()` 一并暴露;`CandidateSearchDiagnostics.withPaymentBoundaryAttribution()`
|
||||
不再覆盖或清零该键。
|
||||
**已知局限**:该累积目前只覆盖 `pdd_browse_candidates` 步骤(候选浏览,
|
||||
规格弹层重复出现的主要场所),不覆盖 `PinduoduoSearchAutomation`/
|
||||
`PinduoduoImageSearchAutomation` 所在的 `pdd_open_app`/`pdd_enter_query`/
|
||||
`pdd_submit_query` 等更早步骤(这两个类当前没有诊断累积基础设施,为其新增
|
||||
等价机制需要更大改动面)。原故障恰好是“在 `pdd_open_app` 单次命中即误判”,
|
||||
修复后该步骤会正常通过并进入候选浏览,绝大多数后续文本放行会发生在
|
||||
`pdd_browse_candidates` 内并被正确计数;但如果放行只发生在更早步骤且之后
|
||||
再未出现,`payTextSuppressed` 会读到偏低的值。这是本次为控制改动面而接受
|
||||
的范围边界,如需覆盖全流程应另开任务为
|
||||
`PinduoduoSearchAutomation`/`PinduoduoImageSearchAutomation` 补充等价的
|
||||
累积与暴露机制。
|
||||
3. **`payStructureStreak` 由 `withPaymentBoundaryAttribution()` 与
|
||||
`PinduoduoCandidateAutomation.diagnostics()` 共同产出,取二者较大值,而非
|
||||
单一来源。** 归因中的 `structureConfirmations` 只反映触发 `PAYMENT_BOUNDARY`
|
||||
那一刻的读数(可能来自 `pdd_open_app` 阶段,`PinduoduoCandidateAutomation`
|
||||
完全不知情);候选自动化自身累积的 `maxStructureConfirmations` 只覆盖它
|
||||
自己轮询到的快照。二者是互补而非互斥的信息源,取大合并可以同时覆盖“结构
|
||||
判据在更早步骤触发”与“候选浏览阶段观察到接近阈值但未触发”两类场景,且不
|
||||
丢失任何一方已经观察到的信息。
|
||||
4. **文本判据可抑制的页面集合按任务文档字面五类严格取值**
|
||||
(`PRODUCT_DETAIL`、`SPECIFICATION_PANEL`、`SEARCH_RESULTS`、
|
||||
`IMAGE_SEARCH_RESULTS`、`HOME`),未包含 `SEARCH_RESULTS_OTHER_QUERY`、
|
||||
`IMAGE_SEARCH`、`IMAGE_SEARCH_RETRY_DIALOG`、`ORDER_LIST` 等其余非
|
||||
`UNKNOWN` 类型。任务文档原文列举的是“商品详情、规格弹层、搜索结果、图片
|
||||
搜索结果或首页”,理解为这五个具名类型;其余非正面列举的类型按“任何未正面
|
||||
识别的页面”对待,维持强制停止,偏保守方向,符合“遇到不确定页面停止”的
|
||||
业务硬边界。
|
||||
5. **`PinduoduoOrderDryRunAutomation`/`AndroidPinduoduoOrderDryRunDriver`
|
||||
(T-217 订单 dry-run)一个字节未改**,但由于二者最终都经同一个
|
||||
`BuyerAccessibilityService.classifyPinduoduoRoot()` 获取快照,新增的结构
|
||||
判据在共享的 `classify()` 层面对它们同样生效:若 dry-run 流程在
|
||||
`expectingOrderConfirmation` 变为 `true` 之前意外连续 3 次观察到“占满屏
|
||||
WebView + 语义文本 ≤ 2”的结构(现有 `PinduoduoCollapsedCheckoutPolicy`
|
||||
本就是为识别这一结构而设计,只是原先只在该布尔量被显式置位后才转换为
|
||||
`PAYMENT_BOUNDARY`),`classify()` 会独立触发 `PAYMENT_BOUNDARY`,比
|
||||
dry-run 驱动原有的门控逻辑更早/更广地停止。这是共享判据修复误报/漏报后
|
||||
的必然连带效应,方向上只会让安全停止更早/更确定,不会绕过或削弱既有
|
||||
dry-run 停止点,且不修改 dry-run 文件本身的任何代码,因此判断为符合
|
||||
“只增强不削弱”原则、未违反“不改下单 dry-run”的边界,但作为可能影响未来
|
||||
dry-run 行为的连带效应在此明确记录,供后续如遇异常提前停止时排查参考。
|
||||
|
||||
#### 未完成项(需人工真机验证,state 保持 DOING)
|
||||
|
||||
- 真机复现同一商品重跑采购任务,确认不再被拦在 `pdd_open_app` 且能进入候选
|
||||
采集。
|
||||
- 从 Admin 读取 `payTextSuppressed`,结合上方「偏离与决策」第 2 条的范围边界
|
||||
确认放行次数在合理范围;若异常偏高需回头收紧文本判据的可抑制页面集合或
|
||||
结构判据阈值。
|
||||
- 若条件允许,在真实结算页上验证连续 3 次快照后确实触发 `PAYMENT_BOUNDARY`
|
||||
且 `payStructureHit=1`。
|
||||
Reference in New Issue
Block a user