1. 版本、边界和优先级
- 业务库:
/Users/jack/code/010-cpt/008-zhct/zhctproject/android/ZhctWeightingTableYoukate。 - 基线:master
2f759f9415b46a7166215fa670d5b4fafeef615e,2.6.0/21;AAR SHA256adabb1f0f4750081abe131a3baa6f76057ff931e05d7264c56bf6fba5d99aeec。 - 仅作兼容对照:zymy_ongoing
21a5d3d956f0a2dabec036a947e315e957a86997,2.6.2/23。Home核心相同,带价格Fragment/UI不同;本计划默认master,现场目标线确认前不发布。 - 识别介质固定二维码;重量来自菜盆;夹子可回盆/不回盆。rfidData/rfidLen保留真实API名称。
- 本合同是待用户review的技术基线;review-decision.json优先于技术READY。PENDING_REVIEW时不得运行或改业务;仅用户实际批准后可推进READY局部修复。C阶段未决物理/业务参数不以默认数字补齐,不接管LIVE。
- 不在范围:换RFID/NFC/AprilTag、替换整套UI、修改服务端接口、改历史计费规则、主动购买或联系厂家、未经授权部署。
2. UC执行切片与状态
沿用已有UC-YKSCAN-001(取证)、002(解析质量)、003(取餐会话)、004(区域内移动)、005(夹子计量)。不创建重复UC。原草案整体BLOCKED拆分为以下执行切片;完整物理业务UC仍保留对应门禁。
| Slice | 关联UC/步骤 | 角色与触发 | 主流程 | 异常/恢复 | AC/测试 | DoR |
|---|---|---|---|---|---|---|
| E00 | 全部前置 | 执行Codex开始任务 | 预检→隔离→准备既有构建栈→读测试XML | 来源不符保留差异;依赖失败归BUILD_ENV,不改版本绕过 | ENV-01 | READY |
| A01 | 001 M2;002 M3/M4/E1;003 E4 | SDK回调/错误 | 复制公开字段→分类→更新重量→按二维码质量路由→诊断 | 非法/来源错误使空候选失效,保留身份和基线;合法报空仍保留旧策略 | QR-01–07 | READY |
| A02 | 003 E5;004 A1/R1 | 查人请求返回 | 发请求时冻结代次→UI线程入口验证→才改人员/事件/告警 | 旧请求/菜品变化/同码新会话丢弃,当前失败正常反馈 | ASYNC-01–03 | READY |
| A03 | 003 A5 | A02等认领收到普通有效码 | 先清观察计数/标在位→再看inflight与去重 | 不改管理卡/A03前置路由;B未查询不写lastQueried=B | A02-01–03 | READY |
| A04 | 005 BR-W01/AC-W01 | 用户保存勺重 | 解析整数→put→回读→显示 | 空/溢出不改旧值;取消不写,不自动补偿 | TOOL-01 | READY |
| A05 | 003 R3;004 M1/M2 | 重量显示/同实例回前台 | 旧算法等价模型;仅无观测缺口时恢复既有视图/基线 | 实际onStop缺口只诊断;恢复/收尾改变待C01 | WEIGHT-01;UI-01 | 模型与无缺口UI READY;缺口恢复GATED |
| A06 | 001 M2;003/004状态准备 | 同一观察流旁观 | reducer只输出候选状态/effects→比对诊断 | 无质量/无在位不产生确定结束;效果禁止业务副作用 | SESSION-01–04 | READY_OBSERVE |
| B01 | 002 M1/M2/E1/E2/R1 | SDK候选包交付 | 冻结包→真实注入入口→正确断言回放→ABI/设备集成 | 入口未交付标WAIT_VENDOR_ENTRY,不用旧私有方法名断言质量 | SDK-01–07 | 验收准备READY;候选/设备部分GATED |
| B02 | 002完整读取链 | 厂家路线无法满足且选择旁路 | 一次性后端选择→唯一owner→原始流→新parser→统一写命令 | 协议/端口/native关闭未证实不启动;不双读/双开 | TRANSPORT-01–02 | GATED:G-SDK/G-PORT |
| C01 | 003/004完整流程 | 新会话内核要接管 | 按已批准SessionPolicy判离开/续餐/换盘 | 模糊归属待处理;中途不自动退回旧引擎结算 | LIVE-01–03 | G-POLICY必需;选在位判据时另需G-PRESENCE |
| C02 | 005 M1–M5/E1/E2 | 夹重补偿要参与计量 | 校准工具及两端状态→计算补偿或UNRESOLVED | 不知道夹子在哪就不自动补偿;湿/搭边/手压另判 | TOOL-02–03 | GATED:G-TOOL |
| C03 | 003 M4/M5/R2;004 M5 | 明确结束申请要入库/真机发布 | 单事务幂等本地写→原补传→设备动作验收 | 失败保留待处理;重启不重复写;旧API语义不改 | DB-01–03;DEVICE-01 | 本地准备READY_OBSERVE;LIVE需C01及计量合格,发布另需G-RELEASE |
性能与安全共通:A阶段只处理现有回调,不创建无界线程/队列,不记录完整人员/二维码;纯策略不sleep、不调用网络。A06诊断环形内存上限256条、单事件摘要上限512字节,禁无限日志;该数值是本计划的资源约束,不是硬件性能事实。真机P95/P99延迟、容忍失读时长和重量误差只在G-POLICY/G-RELEASE批准后作为验收门槛。
3. 唯一观察合同(A01后冻结,不允许各子任务重定义)
新增类放 app/src/main/java/com/cpt/zhct/weighting/hardware/,均为App自有类,不与厂商同名。
public final class ScaleObservation {
public enum CodeKind { VALID_QR, SDK_REPORTED_NO_CODE, INVALID_QR, SOURCE_ERROR }
public enum Presence { PRESENT, ABSENT, UNKNOWN }
public enum Freshness { UNKNOWN, RECEIVER_TIMESTAMP_ONLY, DEVICE_VERIFIED }
public final boolean weightAvailable; // 无可信重量的SOURCE_ERROR为false
public final int weightState; // 沿用现有稳定/不稳定/超重映射
public final int weightGrams; // 沿用当前SDK净重单位和符号处理
public final int sdkDeclaredCodeLength;
public final String sdkCode; // 已被SDK trim,不宣称原始串口字节
public final String normalizedCode;
public final CodeKind codeKind;
public final String reason; // 固定原因码,不拼人员数据
public final long hostSequence; // 本应用观察序,不是设备帧序
public final long callbackAtElapsedMs;
public final Long readAtElapsedMs; // 旧SDK为null
public final Long sourceSequence; // 旧SDK为null
public final Long sourceAtMs; // 旧SDK为null,不能混用时钟域
public final Freshness freshness; // 旧SDK为UNKNOWN
public final Presence presence; // 旧SDK为UNKNOWN
// 构造器按上述字段赋值;拒绝负hostSequence。对象发布后不可修改。
}
SdkScaleObservationAdapter.from(int state, int grams, int declaredLen, String sdkCode, long hostSeq, long callbackAt)是纯Java入口,weightAvailable=true。另提供fromError(String reason, long hostSeq, long callbackAt):SOURCE_ERROR、weightAvailable=false、presence/freshness UNKNOWN、源元数据null;重量int仅占位,不得消费。Android层只传真实SDK公开字段。保持当前6字符规则,不新增全数字正则、不自动补齐/截断。分类次序冻结如下:
SDK数据对象缺失/SDK onError → SOURCE_ERROR(不生成伪重量0样本)
sdkCode为空或全边界空白 且 declaredLen>0 → INVALID_QR / DECLARED_NONEMPTY_BUT_BLANK
sdkCode为空或全边界空白 且 declaredLen==0 → SDK_REPORTED_NO_CODE
declaredLen<0 或含内嵌控制字符 → INVALID_QR / FIELD_OR_CONTROL_INVALID
其它非空码:只沿用当前传输边界trim;规范化后长度非6 → INVALID_QR / CODE_LENGTH_REJECTED
其它规范化6字符 → VALID_QR(管理码仍交原UnlockCodePolicy分流)
declaredLen != normalizedCode.length()本身不能判协议错误:SDK可能已trim字节。declaredLen=8,sdkCode="123456"可保留现有有效码兼容;该场景不证明设备采样新鲜。遇declaredLen=0但非空值也记录不一致诊断;在未确认厂商含义前保持既有六字符可读性,不凭空当空盘。
重量字段与码质量分开:有可信SDK重量字段时,即使码非法仍继续称重显示;码质量不能通过旧兼容回调再次退化成空字符串送入Home。HardwareListener新增默认 onScaleObservation(ScaleObservation o);兼容旧显示listener仅在weightAvailable时调用旧onWeightData。Home必须覆盖类型化入口,只执行一次重量处理和扫码路由。同步检查WeightingActivity、DebugWeigherPop。
Home新增handleWeightObservation(ScaleObservation o):无可信重量直接返回;有重量转四参handleWeightData(state,grams,code,allowPresenceInference)。保留原普通重量更新次序,仅将原1961–1978附近A02/A03/NoTrayPickDetector在位相关分支包在allowPresenceInference判断内。INVALID/SOURCE_ERROR时不运行这些分支,且对非null的NoTrayPickDetector调用resetBaseline,使无盘取菜候选不能跨异常区间累计。VALID/SDK_REPORTED_NO_CODE仍沿旧推断,A阶段尚未解决正常报空与物理离开的区别。
INVALID_QR/SOURCE_ERROR:清除普通/A03空候选、调用新增 A02TrayClaimCodePolicy.invalidateEmptyCandidate()清该策略候选,并对非null的NoTrayPickDetector调用resetBaseline,包括weightAvailable=false的SOURCE_ERROR。不得跨没有可信重量/码证据的间隔累计无盘取菜。不清lastQueriedTrayCode、不释放报警、不换身份、不清起始重量、不保存取餐。这是证据不连续处理,不是确认餐盘仍在。 SDK_REPORTED_NO_CODE在A阶段保持旧N策略,诊断明确“SDK报空,物理在位未知”;只有C01可替换旧离盘策略。
4. 请求与会话合同
sessionEpoch属于逻辑会话,不能由条码字符串生成。同码重新开餐必须是新epoch。StaffQueryGuard.begin(epoch, code, dishUuid)生成不可变Ticket{ownerId,epoch,requestGeneration,code,dishUuid}。isCurrent(ticket,epoch,code,dishUuid)必须同时匹配owner、epoch、最新requestGeneration、code、dish;普通查人每次begin都递增generation。invalidateSession()在明确结束/换会话/owner销毁时调用,不能仅onPause就当物理结束。旧Activity销毁后的响应全部丢弃。- 网络回调先切到唯一UI/会话线程,再验Ticket,然后才允许全局人员赋值、推荐/人员事件、声音和报警。A02既有requestToken保留。
新内核入口冻结为:QrSessionReducer.reduce(SessionSnapshot old, SessionEvent event, SessionPolicy policy) -> Transition。Transition含不可变snapshot和effect列表;旁观执行器只能记录,不能执行effect。
最小阶段:IDLE、ACTIVE、GAP_PENDING、CONFLICT、FINALIZE_PENDING、CLOSED。政策包含 mode=OBSERVE|LIVE、policyVersion、approvedBoundaryMode、可空的 gapWindowMs/exitWindowMs;OBSERVE缺参数允许输出不确定状态,LIVE缺参数必须拒绝启动,不用0代替。
| 当前/输入 | 新内核约束 |
|---|---|
| ACTIVE/同二维码且连续性证据满足 | 保持epoch/起始重量;不再查人/新建记录 |
| ACTIVE/SDK报空、质量丢失、UI解绑观测中断 | GAP_PENDING;旧身份仅作历史,缺口重量待归属 |
| 任意/非法、来源错误、无新数据 | 标质量UNKNOWN,不将时间流逝当ABSENT |
| ACTIVE或GAP/B码或曾见B再回A | CONFLICT;不覆盖起始重量,不因最终再见A自动归整段重量 |
| GAP/再次A但没有完整连续性证据 | 可保留原会话,仍不自动确认缺口重量 |
| 新鲜可信在位ABSENT持续达批准规则 | 仅提出结束候选;身份/末重/夹子条件不足则待确认 |
| 明确结束候选重复、存库失败重试 | 相同finalizationId,不生成第二份本地记录 |
UI恢复和物理恢复分开。A05仅修无观测缺口的同实例返回展示;真实onStop缺口的视图/计量接管归C01。A阶段该路径保持旧行为并标为未解决,不能为了看起来连贯就恢复旧基线再自动归缺口重量。新内核不能给缺口补造身份;进程恢复依赖C03,不凭最后条码猜。
5. 夹子计量合同
设置保存与补偿独立。A04只修真实保存,不改默认20g、不替换现场实测夹重,不将其自动扣入订单。
toolState = ON_SCALE / OFF_SCALE / UNKNOWN
Adjustment evaluate(rawDelta, startState, endState, calibratedToolMass)
任一状态UNKNOWN / 校准不可用 → UNRESOLVED
已知且模型适用 → foodDelta = rawDelta - (startOn - endOn) * mass
真实应用中ON/OFF必须来自已确认传感/人工动作;不能凭重量接近夹重猜测。夹子在回位架之外,不等于在手里;可在盆里/搭边。湿夹/带菜/补菜/手压/部分承重使模型不成立时保留原始差与不确定原因。
6. 本地订单与兼容门禁
- A阶段不更改pick_record表、上传字段或worker删除/重试规则,不承诺端到端exactly-once。
- C03再做独立session_snapshot/session_finalization增量表;一笔结束申请finalizationId唯一。pick_record插入、finalization映射和snapshot终态必须同一SQLite事务,回滚失败保持待处理。
- 现有DB版本2;若走该方案,下一次迁移版本需先检查目标分支最新值再确定,不能硬写升级到3后覆盖并行迁移。
- 保留NORMAL、ANTI_ESCAPE_A02/ANTI_ESCAPE_A03及alarmId,不把A03原未绑定盘移走伪造成正常空人员订单。
- UploadPickMealRecordWorker现有code0或code1终止并删除策略保持;其他失败保留/重试。本地唯一结束申请不证明服务端重试去重。
- OBSERVE与LIVE不能同时产生订单;回退只在没有活动/待保存新会话的安全边界完成,不在中途切回旧N逻辑再次收尾。
7. 只阻断相关步骤的明确门禁
| Gate | 缺少的具体输入 | 交付者/关闭证据 | 阻断什么 | 不阻断什么 |
|---|---|---|---|---|
| G-SDK | 新SDK/公开可测入口/帧与响应协议/修复范围 | 厂家;新版本SHA、变更说明、可运行注入测试或授权串口回放台 | 新包验收完成、旁路生产parser协议确认 | A阶段、B01验收素材准备 |
| G-PORT | 实际串口配置、唯一owner、native安全close/reopen | 厂家+硬件测试;设备FD/线程与重复开关测试 | B02真实接管 | 纯解析/fake测试(选择旁路后) |
| G-PRESENCE | 独立红外/区域状态、健康、新鲜度、覆盖区域 | 厂家+测试;字段说明和移动/拿走同步记录 | 宣称可靠物理离盘 | 旧回调异常防护、OBSERVE |
| G-POLICY | 二维码单通道时结束依据,gap/exit时间,A→B→A归属,报警与模糊操作规则 | 产品+业务负责人;版本化SessionPolicy和测试oracle | C01 LIVE | A01–A06 |
| G-TOOL | 是否机械隔离,实际夹重,开始/结束承重观测,湿/带菜/搭边规则 | 产品+硬件+测试;四状态已知重量验证 | C02自动补偿及不确定重量自动最终计量 | A04保存修复 |
| G-RELEASE | 现场APK/目标分支、设备/固件、允许区域、时延/误差目标与样本量、升级回退 | 项目负责人+测试;真实动作/日志/订单验收与当次发布授权 | C03发布、声称彻底修复现场 | 全部本地准备 |
这些是决策输入,不是让执行Codex在第一步就向用户重复问所有问题。先完成READY工作,记录门禁证据缺项;到相关步骤再集中交付需要确认的具体方案。截止日期当前未约定,不捏造。