410 二维码改进 · 等待方案 Review

410二维码识别改进:整体方案与Codex实施计划

Review状态:PENDING_REVIEW。用户要求先审本方案,再真实运行。当前不构建/拉取镜像、不运行Java/Gradle、不修改业务代码、不安装APK。技术READY不等于用户已批准。

For agentic workers:用户review通过后,使用已安装的subagent-driven-development或executing-plans按任务推进;项目执行模式由task-execution-mode-router决定。共享HomeActivity只有一个writer。精确接口与门禁以contracts.md为准。

Goal: 修复可证实的SDK/应用错误,让二维码短失读不误作物理离盘;再在已确认的传感/业务契约下完成挪盘连续性、换盘归属及夹重计量。

Architecture: 保留二维码扫码和现有PC681 SDK入口,先在应用层保留并分类可见信息;SDK内部丢帧由厂家修包优先解决。逐步建立数值会话模型,新内核先旁观,满足物理与业务门禁后才接管结算。

Tech Stack: Docker内JDK8 + Gradle5.1.1/AGP3.4.1/Android API30;Docker内JDK17用于SDK/应用隔离审计;Java8源码、现有JUnit4.12、SQLite/MMKV/WorkManager。Mac不安装或调用任务JDK,不复用Mac版Android工具到Linux容器。

1. 哪些运行在哪个环境

对象 环境 用户能看到的结果 不能代表什么
Android源码构建、JUnit单测 zhct-qr-jdk8:20260909,Linux/amd64;容器内Linux AndroidSDK/API30/build-tools30.0.0 构建日志、JUnit XML/HTML、Debug APK及SHA 不代表真实扫码或称重已验收
SDK字节码/旧11项parser特征 zhct-qr-jdk17:20260909,Linux/amd64;旧harness使用Java17 HexFormat 每条输入、输出、断言及结果报告 不执行native串口、不证明硬件采样或JNI关闭正常
应用11项+A02/A03十项隔离审计 JDK17容器内Python+Java,读取冻结源码 21项真实类/抽取方法结果与hash 不等于完整Activity/数据库/生命周期测试
最终APK 410 Android真机的Android Runtime,项目minSdk26、ARM ABI 二维码/挪盘/夹子动作视频、界面、串口与业务记录 APK不能当Java服务直接在JDK容器里运行;普通x86模拟器不能代替本机硬件

Docker Desktop后台已可连接;本轮仅做客户端/daemon/manifest/源码静态核验。指定项目镜像尚未构建,Java/Gradle与系统尚未运行。旧版“在Mac安装JDK”的步骤被本合同覆盖。

2. 范围和实施阶段

阶段 本阶段结果 READY边界
E00 环境与基线 两个Docker环境、原系统构建/特征报告 用户review后先执行;依赖网络失败归环境问题
A01–A04 明确缺陷 异常不计空、迟到响应不串人、A02观察正确、勺重能保存 不依赖未知红外/夹子接口,可局部修复并验证
A05–A06 会话准备 数值来源统一、UI恢复保留基线、新内核旁观 旁观无订单/查询/告警副作用;不改最终物理判据
B01 SDK 冻结厂家修复要求与正确断言、新包独立验收 资料准备可做;没有候选包/入口不声称修复通过
B02 SDK替代 仅厂家方案无法满足时选唯一串口owner 端口、协议、native关闭未验证前不启用真实接管
C01–C03 接管与真机 已确认离盘/归属/工具策略,事务收尾与现场验收 条件逐项关闭才执行;不把它们作为A阶段全停理由

当前选择:先A,SDK走B01;B02不默认开发。 不创建无用的双后端,不修改服务端,不替换二维码介质,不整体重构UI。

3. E00:Docker环境、隔离基线和原系统验证

先读: START_HERE.md、contracts.md、Docker说明、源仓库根/app build.gradle、wrapper properties、AndroidManifest.xml、项目协作指南。

QR_PACK="/Users/jack/code/010-cpt/008-zhct/zhctprompt/work_android/ZhctWeightingTableYoukate/audits/2026-09-09-scan-interruption/execution"
QR_SOURCE="/Users/jack/code/010-cpt/008-zhct/zhctproject/android/ZhctWeightingTableYoukate"
python3 "$QR_PACK/scripts/preflight.py" --repo "$QR_SOURCE"
git -C "$QR_SOURCE" worktree list
git -C "$QR_SOURCE" worktree add -b codex/qr-scan-continuity-20260909 \
  /Users/jack/code/010-cpt/008-zhct/zhctworktrees/ZhctWeightingTableYoukate-qr-scan-20260909 \
  2f759f9415b46a7166215fa670d5b4fafeef615e
bash "$QR_PACK/docker/run.sh" sdkaudit --repo "$QR_SOURCE" --evidence-dir "$QR_PACK/results/baseline-sdk"
bash "$QR_PACK/docker/run.sh" appaudit --repo "$QR_SOURCE" --evidence-dir "$QR_PACK/results/baseline-app"
bash "$QR_PACK/docker/run.sh" a02audit --repo "$QR_SOURCE" --evidence-dir "$QR_PACK/results/baseline-a02"
bash "$QR_PACK/docker/run.sh" gradle --repo "$QR_SOURCE" \
  --evidence-dir "$QR_PACK/results/baseline-gradle" -- \
  :app:testDebugUnitTest :app:assembleDebug

E00完成条件: 两镜像版本可回读;原SDK/应用特征可重现;Gradle实际成功或有完整环境失败清单。环境失败只限制相应运行,不阻止计划/纯文本完善,但不能在基线不明时声称候选回归通过。

4. A01:SDK观察保真与二维码质量分类

修改: hardware/YoukateHardwareManager.java的scaleDataCallback/cleanScanCode/HardwareListener;activity/HomeActivity.java硬件监听、普通/A02/A03路由;antiescape/A02TrayClaimCodePolicy.java新增候选失效方法。同步所有HardwareListener实现。

新增: hardware/ScaleObservation.java、SdkScaleObservationAdapter.java;测试hardware/SdkScaleObservationAdapterTest.java、ScanObservationRoutingTest.java。接口严格遵守contracts §3。

// Home类型化路由的关键顺序;完整字段与方法在contracts中定义。
handleWeightObservation(observation); // 检查weightAvailable并屏蔽异常的在位推断
switch (observation.codeKind) {
    case VALID_QR:
    case SDK_REPORTED_NO_CODE:
        handleYoukateScanCode(observation.normalizedCode);
        break;
    case INVALID_QR:
    case SOURCE_ERROR:
        scanEmptyFrameCount = 0;
        mFirstEmptyScanElapsedMs = 0L;
        mA02TrayClaimCodePolicy.invalidateEmptyCandidate();
        if (mNoTrayPickDetector != null) mNoTrayPickDetector.resetBaseline();
        break;
}

新增入口按以下逻辑接线,不把异常空串喂给原三参函数:

private void handleWeightObservation(ScaleObservation o) {
    if (!o.weightAvailable) return;
    boolean allowPresenceInference =
            o.codeKind == ScaleObservation.CodeKind.VALID_QR
            || o.codeKind == ScaleObservation.CodeKind.SDK_REPORTED_NO_CODE;
    handleWeightData(o.weightState, o.weightGrams, o.normalizedCode, allowPresenceInference);
}

四参handleWeightData保留旧重量显示、序号、DataProcessor和后续重量事件的次序,仅把A02/A03/NoTrayPickDetector在位相关分支包在if(allowPresenceInference)内;否则跳过它们并对非null的mNoTrayPickDetector调用resetBaseline,防止跨异常累积无盘证据。Home必须走上述入口;三参兼容入口只给旧诊断。增加“无当前盘,1000→非法码900→合法空900”测试:异常时重量显示可变,不能触发A02或在下一空码将该100g补算为无盘取菜。SOURCE_ERROR工厂不生成可信0重量。

完成条件: QR测试通过,旧有效码/SDK报空语义保留,来源未知如实表示;A01只防可识别异常误离盘,不声称已解决真实漏扫或SDK内部丢帧。

5. A02:查人请求代次,防止晚包覆盖新会话

修改: HomeActivity.getStaffInfo(895起),handleScanResult、scanCodeResult结束分支、onDestroy。新增: session/StaffQueryGuard.java及session/StaffQueryGuardTest.java,Ticket定义见contracts §4。

final StaffQueryGuard.RequestTicket ticket = staffQueryGuard.begin(epoch, trayCode, dishUuid);
// 在现有主线程回包入口内,所有业务副作用之前:
if (!staffQueryGuard.isCurrent(ticket, activeEpoch, activeTrayCode, activeDishUuid)) {
    return; // 仅允许另行记录脱敏丢弃原因
}

完成条件: 旧包不能改变新会话任何人员/推荐/报警状态;后台API入参和A02请求控制不变。

6. A03:A02观察状态先更新,查询去重后判断

修改: A02TrayClaimCodePolicy、HomeActivity.handleA02ScanDuringAlarm/claimA02AlarmByTray;扩展原A02TrayClaimCodePolicyTest与路由测试。

public void onValidTrayObserved() { emptyFrameCount = 0; }
public void invalidateEmptyCandidate() { emptyFrameCount = 0; }

完成条件: 网络inflight不再决定传感器空计数是否清零;没有并发重复查人或多余补单。A03原盘身份规则由C01独立处理。

7. A04:勺重真实保存,计价暂不补偿

修改: SetActivity的setting_item_spoon_weight(660–678);读取MmkvUtils、SingleInputDialog。验证: TOOL-01 +实际设置页回读。

final int spoonWeight;
try {
    spoonWeight = spoonDialog.getCurrentInputValue();
} catch (NumberFormatException e) {
    // 沿用该设置页的无效输入提示方式;不保存、不dismiss。
    return;
}
MmkvUtils.getInstance().put(Constants.SPOON_WEIGHT, spoonWeight);

完成条件: 持久化和界面一致;不把保存修复报告成夹子计量已解决。

8. A05:数值等价迁移与无观测缺口的UI恢复

修改: Home的switchToOngoingFragment、scanCodeResult、onResume/onStop;两种DarkPickOngoing*Fragment.showCurrentWeight/weightChangeWithSerial。新增: session/PickingSnapshot.java、PickingWeightPolicy.java及测试。

完成条件: 等价回放一致、两样式数值一致、无缺口UI恢复不重置会话。真实后台缺口恢复、新稳定阈值与未知缺口归属未接管。

9. A06:新会话内核旁观与有界诊断

新增: session/QrSessionReducer.java、SessionSnapshot.java、SessionEvent.java、SessionEffect.java、SessionPolicy.java、Transition.java、diagnostics/ScanTraceBuffer.java;纯JUnit对应测试。修改: Home将同一不可变观察送入旁观adapter。

完成条件: 纯内核和真实Home双路无业务副作用验证通过;新引擎仍未接管订单。

10. B01:SDK厂家修包优先,不在回调后假装修parser

准备资产: SDK修复合同、SDK-01–07字节输入/正确输出、旧11项输入来源与AAR SHA。外发需当次明确授权。

现有callback没有raw/frameLen/checksum/readTime;App无法排SDK私有缓存,也不能恢复已丢帧。rfidLen>0+空的A01防护可先做,但不是SDK内部完全修复。

11. B02:唯一串口owner备用路线

只有B01无法满足且方案选择已确认后才开展;当前不默认创建这些类。拟新增Pc681FrameParser、ScaleTransport、JocatSerialTransport、ScaleCommandCodec,都在hardware目录。

完成条件: 只有协议、所有命令与生命周期都通过才可替换旧SDK;raw接管仍不能知道固件内部码值年龄,设备源序号缺失继续标UNKNOWN。

12. C01–C03:完整接管、夹重与真实演示

这些步骤保留精确门禁,不让Codex凭空选900/1200ms或猜夹子状态。C03本地存储/事务/迁移测试可在A06后以合成结束快照准备,不需要发布权限,也不自动启用LIVE;正式接入与发布分别过门禁。

C阶段完成条件: G-POLICY始终必需;选择独立在位判据才需G-PRESENCE,批准的二维码启发式不伪造presence。自动补偿需G-TOOL;机械隔离并校准可不开发补偿模块,但仍须证明计量合格。本地持久化测试不依赖发布授权;最终现场发布另需G-RELEASE。相关时延/误差/动作矩阵通过,回退不在活动会话中混用两套收尾,才可说指定设备/工况的问题已关闭。

13. 提交、验证与交接