diff --git a/T-256.-.md b/T-256.-.md new file mode 100644 index 0000000..7d381c7 --- /dev/null +++ b/T-256.-.md @@ -0,0 +1,156 @@ + +> 同步来源:[`docs/tasks/T-256.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-256.md) · commit `afc651f75a3a` + +--- +id: T-256 +title: 允许少于目标数量的有效候选进入人工确认 +phase: 2 +deps: + - T-254 +status: DOING +created: 2026-07-30 +context_ref: c488e50 +work_branch: null +write_paths: + - docs/tasks/T-256.md + - docs/current-state.md + - android-buyer/app/src/main/** + - android-buyer/app/src/test/** +--- + +## 问题 / 背景 + +T-254 的真机计数分布显示,第一轮已经采集到一个**完全合格**的候选: + +| 轮次 | resolved | target | skuMismatch | identityFail | reason | +| --- | --- | --- | --- | --- | --- | +| 1 | **1** | 2 | 0 | 0 | `CANDIDATE_TARGET_NOT_FOUND` | +| 2 | 0 | 1 | 0 | 0 | `CANDIDATE_ALL_EXCLUDED` | +| 3 | 0 | 2 | 0 | 0 | `CANDIDATE_ALL_EXCLUDED` | + +该候选颜色尺码硬约束匹配、商品身份解析成功、双证据齐全,但因为 +`PROCUREMENT_CANDIDATE_TARGET = 2` 而未达标,整条任务判 `FAILED`,候选没有提交给 +Admin,人工连看都看不到。 + +这与需求直接冲突。`docs/02-requirements.md`: + +- 范围决策:「以 SKU 颜色/尺码硬约束过滤后回传 **`0..5` 个**」 +- F-005 验收:「匹配项按评估结果排序且最多 5 个,**不足时不得凑数**」 +- 业务规则 20:「回传可以少于 5 个,禁止用弱匹配凑数」 + +需求要求的是「不足就少回传」,当前实现做成了「不足就整体失败」。这是本轮采购任务 +失败的**直接可修复原因**,且不依赖 T-255 对卡片识别率的根治。 + +## 关联需求与交互 + +- 功能:F-005、F-006、F-007 +- 用户故事:US-004、US-005 +- 交互:沿用 Admin 任务详情候选确认区,不新增页面 +- 架构/API:候选回传与人工确认接口契约不变,**后端零改动** + +## 行为契约 + +### 一、候选采集完成判定 + +`candidateCollectionReady` 改为:**存在至少一个 `hasResolvedIdentity` 为真的候选** +即视为可提交,不再要求达到 `PROCUREMENT_CANDIDATE_TARGET`。 + +`PROCUREMENT_CANDIDATE_TARGET = 2` 保留原值和原语义,继续用于计算每轮 +`roundCandidateTarget`(即「本轮还想再找几个」),但**不再作为成败门槛**。 + +### 二、三轮预算继续走满 + +有效候选数未达 `PROCUREMENT_CANDIDATE_TARGET` 时,剩余轮次**仍然要执行**,尽量补足。 +不得因为已经拿到 1 个就提前终止搜索——目标仍是尽量凑够 2 个,只是凑不够不算失败。 + +三轮跑完后: + +- 累计有效候选 **≥ 1** → 提交候选并进入 `WAITING_CONFIRMATION`,不写 `FAIL` 终态 +- 累计有效候选 **= 0** → 维持 T-251 现有的 `CANDIDATE_SEARCH_EXHAUSTED` 可重试 `FAIL` + +### 三、不得凑数 + +只有 `hasResolvedIdentity` 为真、且经过既有颜色/尺码硬约束校验的候选才能计入。 +`identityFailureCode` 非空的观测候选**不计入**这个「至少一个」的判定,其既有的 +观测保存行为不变。 + +### 四、诊断可见性 + +提交时的 search run 必须如实记录本次实际提交的候选数与 `PROCUREMENT_CANDIDATE_TARGET` +的差距,让 Admin 知道这是「不足目标但可用」而不是「完整 Top N」。沿用 T-254 的 +诊断 message 机制,不新增字段格式。 + +## 方案 + +1. `MainActivity.candidateCollectionReady`:判定改为「至少一个 EXACT 或 FALLBACK 的 + 已解析候选」。 +2. 三轮流程收尾处:区分「有候选 → 提交并进人工确认」与「零候选 → 沿用 T-251 终态」 + 两条路径。 +3. `roundCandidateTarget` 的计算方式不变,继续按缺口驱动后续轮次。 +4. 单测覆盖:1 个候选跑完三轮后提交且不写 FAIL、2 个候选行为与现状一致、0 个候选仍 + 写 `CANDIDATE_SEARCH_EXHAUSTED`、仅有未解析身份的观测候选不满足「至少一个」。 + +## 验收要点 + +- [ ] 三轮累计恰好 1 个有效候选时,任务进入 `WAITING_CONFIRMATION` 且不写 `FAIL` 终态。 +- [ ] 三轮累计 0 个有效候选时,仍写 T-251 的 `CANDIDATE_SEARCH_EXHAUSTED` 可重试终态。 +- [ ] 累计 ≥ 2 个时行为与本任务前完全一致,有回归测试证明。 +- [ ] 只有身份未解析的观测候选时不满足提交条件。 +- [ ] 已拿到 1 个候选后,剩余轮次仍然执行,不提前终止。 +- [ ] `PROCUREMENT_CANDIDATE_TARGET` 常量值未改变。 +- [ ] Android Debug/Release 单测、`lintDebug`、`assembleDebug`、`assembleRelease` 全部通过。 +- [ ] 真机复跑 T-254 那条任务:第一轮那个候选出现在 Admin 候选确认区,可查看双证据。 + +## 边界 + +- 不改 `PROCUREMENT_CANDIDATE_TARGET` 的值,也不改 `MAX_CANDIDATES_PER_PROBE`。 +- 不改 `PinduoduoImageCandidateRoundPolicy` 的三轮预算、timeout、attempts、scrolls。 +- 不改卡片识别、签名算法或跨轮去重——那是 T-255 的范围。 +- 不改 T-254 的诊断计数与失败码语义。 +- 不降低颜色、尺码、身份解析、双证据任何一项校验,不允许用弱匹配或未解析身份的 + 观测候选充数。 +- 不改后端代码、数据库、API 契约或 Admin 模板。 +- 不新增第四轮搜索。 +- 不启动真实下单,不进入订单提交或支付。 + +## 执行记录 + +开工后记录修改、命令、结果、环境、决策和 blocker。 + +### 2026-07-30 + +- 状态:开工时由 `TODO` 改为 `DOING`;按用户要求未改为 `DONE`。 +- 修改: + - `MainActivity.candidateCollectionReady` 现在只判断累计候选中是否存在 + `hasResolvedIdentity` 为真的候选;身份未解析的诊断观测不会满足提交条件。 + - 新增 `CandidateCollectionPolicy`,将“已有有效候选”“已达到原目标”和“达到第三轮 + 后允许以不足目标数量收尾”分开判定;`PROCUREMENT_CANDIDATE_TARGET = 2`、三轮门禁、 + `roundCandidateTarget`、`MAX_CANDIDATES_PER_PROBE` 和图片候选预算未修改。 + - 首轮只有一个有效候选时继续执行第二、第三轮;第三轮结束后将累计有效候选排入 + `WAITING_CONFIRMATION`,不写 `CANDIDATE_SEARCH_EXHAUSTED`。零个有效候选仍沿用 + `CANDIDATE_SEARCH_EXHAUSTED` 终态。 + - 三轮耗尽后的进程恢复也先检查持久化候选;已有有效候选回到正常提交路径,只有零个 + 有效候选才执行既有耗尽失败恢复。 + - 成功收尾和恢复收尾的 `recordCandidateSearchAttempt` 均复用 T-254 诊断 message, + 并把本次实际累计提交候选数写入 `resolved`、目标写入 `target`,以显示不足目标但可用。 + - 新增 `CandidateCollectionPolicyTest`,覆盖 1 个候选三轮收尾、2 个候选原目标行为、 + 0 个候选失败路径和仅身份未解析观测不满足提交条件。 + - 未修改 `backend-api`、API 契约、数据库、候选识别/去重、SKU 硬约束、三轮预算、下单 + 提交或支付逻辑;未运行 `init.ps1`,未执行 git commit 或 push。 +- 命令与结果(工作目录:`D:\chengma\cmroubao\android-buyer`,PowerShell): + - `.\gradlew.bat testDebugUnitTest --no-daemon`:`BUILD SUCCESSFUL`。 + - `.\gradlew.bat testReleaseUnitTest --no-daemon`:`BUILD SUCCESSFUL`。 + - `.\gradlew.bat lintDebug --no-daemon`:`BUILD SUCCESSFUL`。 + - `.\gradlew.bat assembleDebug --no-daemon`:`BUILD SUCCESSFUL`。 + - `.\gradlew.bat assembleRelease --no-daemon`:`BUILD SUCCESSFUL`。 + - 五项命令均退出码 0。 +- 环境:Windows PowerShell;JDK 17.0.13;Android SDK 34;Gradle Wrapper 8.2。构建输出 + 的 SDK XML 版本提示为既有环境警告,不影响五项目标成功。 +- 决策:保留既有“达到 2 个 exact 或 2 个 fallback 可提前收尾”的行为;只有累计有效候选 + 少于目标时才要求继续到第三轮。第三轮结束时只要有至少一个已解析身份候选即可提交, + 以满足不足时少回传而非整体失败的需求。 +- 未验证 / blocker:无法连接并操作测试真机,也无法在 Admin 任务详情确认候选双证据, + 因此 T-256 的最终 on-device verification 未完成;需要在测试设备上复跑 T-254 任务并 + 确认一个有效候选进入人工确认区。该 blocker 不影响本次五项 Android Gradle 验证。 +- 最终复核:补充进程恢复门禁后,以上五条 Gradle 命令均重新执行并再次以退出码 0 + `BUILD SUCCESSFUL` 结束。