Table of Contents
同步来源:
docs/tasks/T-256.md· commitafc651f75a3a
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 机制,不新增字段格式。
方案
MainActivity.candidateCollectionReady:判定改为「至少一个 EXACT 或 FALLBACK 的 已解析候选」。- 三轮流程收尾处:区分「有候选 → 提交并进人工确认」与「零候选 → 沿用 T-251 终态」 两条路径。
roundCandidateTarget的计算方式不变,继续按缺口驱动后续轮次。- 单测覆盖: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结束。