Files
zfjia 329e27b515 初始提交:三端设计文档与 dev 代码基线
docs/:系统梳理、架构优化方案、CR-02 三端功能设计修订(含 §15 三端一致性同步与平台统计分析)、公寓端/应用端/平台端分册、公寓统计分析与排名报表、公寓端小步迭代开发计划(IT-0~IT-8)

dev/:RuoYi-Vue-master(后端)与 RuoYi-Vue2-master(前端)官方上游基线,未改动
2026-11-10 13:43:05 +08:00

46 KiB
Raw Permalink Blame History

05 关键流程

追溯:docs/10 §7.7(语音链路拓扑)、§7.9 表 7-5(事件与状态迁移)、§7.11 表 7-7(自检与巡检)、 §6.2(上报信封)、§6.3 表 6-2(幂等/重试/对账)、§5.5 表 5-5(分机配置一致性)、§3.3(人工干预边界)。 流程中的状态迁移以 03 §3.2 表 3-10 为准。 v2 修订(CR-02,2026-11-09):8 条关键流程变更以 docs/设计变更/03-三端功能设计修订(CR-02).md §7 为权威,见下文 §0;表 5-1 第 4 行的 jcsj 口径按 R-C9 同步纠正;§12 补遗(F-F 注册链路改判、F-J 人脸数据下发)以 docs/公寓端/10 为流程权威表述。

0. v2 流程变更索引(CR-02)

本节图表用独立编号 表 C-y,不占用本文件 表 5-y 序号;§1~§8 原流程继续有效,本节只列增量与改判。

表 C-1 本端流程变更落点(F-A~F-Q)

编号 流程 改动 涉及原章节
F-A 双模式与顺序派发 §1 / §2 增 call_mode=TABLET 分支;按 jcsj 生成单调 dispatch_seq,平板按游标拉取 §1、§2、§3
F-B 平板结果回写 状态机新增 TABLET_RESULT 事件源;VERIFIED / UNVERIFIED / SKIPPED 分支;只提交命令 §2
F-C 换乘 本地重算时刻 + 换房联动 + apt_sip_binding 联动 + #8 / #9 上报 §1、§7
F-D 断网本地充值 / 扣补贴 离线额度校验、本地余额 CAS、恢复后先派 outbox 再拉 #7、冲正 §6
F-E 上报历史与续报 outbox 仍为队列,新增 apt_report_history / apt_report_retry_batch,续报复用原幂等键 §6
F-F 人员注册(v1.3 由 CR-C25 改判) 签到终端 REG_APPLY → 值班台待办 → 值班员 CONFIRM(生成 ryid、入 apt_master_person)→ 并行双路:#9 上传平台 / FACE_ISSUE 下发公寓内设备;平台回执降级为同步回执,不再是生效前置 docs/公寓端/10 §4、§5
F-G 保洁 / 排班 / 考核 现场记录、班次与日终本地计算 §7
F-H 设备报警分流 设备报警独立判定并落 apt_device_alarm,与叫乘 / 离寓报警分离 §3
F-I SIP 分机注册离线告警 call_mode='SIP' 派发前按 jcid → fjh → apt_sip_binding → apt_sip_endpoint 解析房间主分机;注册态非 ONLINE(或 last_reg_at 超阈值)且持续超阈值时,按 alarm_event_key 去重写入 apt_device_alarm(alarmSource=DEVICE、alarmType=OFFLINE),推 device.alarm、#12 上报;不阻断叫乘,失败原因记 apt_call_session.hangup_cause。恢复时写新流水 + cleared_at + alarm.cleared,只追加不清零 §1、§3、§4
F-J 人脸数据下发管理(CR-02 §12) 平台增量下载入库、签到设备全字段下发、消费 / 叫乘按 gy_sb.face_scope_* 隔离、TRANSIENT 离寓自动 FACE_ERASE、LONG_TERM 独立分区长期保留;三级版本与 hash 对账 + FACE_FULL_SYNC 兜底。与 F-F 的注册链路衔接 docs/公寓端/10 §3、§5~§10
F-K 人员入住作业流程(CR-02 §13) 注册与签到 → 入住申请(房间 / 交路)→ 值班员审核与房间调整(CHECKED_IN)→ #9 CHECKIN 提报与平台运安匹配(#6 / #8 / #7)→ #7 拉取叫乘任务并复算 jcsj 进入叫乘;"已入住"为流程态 + 业务事实双重表达,不新增 gy_jcxx 状态字段 docs/公寓端/11 §2~§8、§10、本文件 §10
F-L 人员消费(CR-02 §14) 终端按 PUBLISHED 食谱选菜并显示金额 → N-49 查补贴 / 现金额度 → "补贴优先、现金补足"双额度 CAS 扣减且一笔消费只记一条 CONSUME 流水 → #10 LEDGER_OFFLINE 上报 → 平台 platform-ledger 归日并更新人员账户 → #16 日对账置 RECONCILED;合计不足全额拒绝 docs/公寓端/12 §4、§7、本文件 §6(F-D)、§11
F-M 人员线下充值(CR-02 §14) 扫公寓线下二维码(只识别公寓、非支付)→ 签到终端 N-52 提交现金充值申请(申请表不存金额)→ 管理员 N-54 二次确认 → apt.ledger.local 只增 xjye 并写 gy_ykt_xjczjl(channel=OFFLINE_CASH) → #10 同步平台;不建支付订单与回调 docs/公寓端/12 §5、§7、本文件 §6(F-D)、§11
F-N 公寓菜品管理(CR-02 §14) 公寓管理员 N-57 发布阶段性食谱(平台库 YKT_MEALS 补列、recipe_version 单调)→ #4 recipes[] 下行 → 消费终端 N-48 按 version 主动拉取并缓存 → 点击查看菜品、加入选择菜单(确认前不落库);远程点菜仅留 N-58 入口,本期恒 40940 docs/公寓端/12 §6、§7、本文件 §11

口径纠正(R-C9 / CR-C06):jcsj 及其派生时刻恒由公寓按 apt_master_rule + apt_holiday 复算,平台不产出权威值;只有要素(kcsj / jcfs)与平台不一致时才以平台为准并告警。

三条不可违反规则不变:① 只经 apt.duty.state,任何新事件源(平板结果、换乘、报警)都只提交命令;② 条件更新带期望态;③ 已结束不回写。

补遗指针(CR-02 §11 / §12 / §13 / §14 / §15):F-A / F-B 两类叫乘实现方式的端到端流程(触发条件、交互时序、状态上报机制、结果反馈路径)见本文件 §9;§9 为流程权威表述,本节索引的 F-AF-N 本端流程骨架不变。人员注册(F-F)与人脸数据下发(F-J)的流程权威表述见 **docs/公寓端/10 §4、§3§10**;注册链路按 CR-C25 改判为"签到终端申请 → 值班员确认 → 入公寓服务系统 → 分别上传平台 / 下发设备",平台回执不再是注册生效前置。人员入住作业流程(F-K)见本文件 §10 章节指针与 docs/公寓端/11 唯一权威表述。消费 / 充值 / 菜品管理(F-L / F-M / F-N)见本文件 §11 章节指针与 docs/公寓端/12 唯一权威表述;三条流程共用 §6 的 F-D 账务闭环,不新造账务闭环。 三端一致性与平台统计(CR-02 §15):表 C-1 标题按全局流程编号扩至 F-A~F-Q,其中 F-A~F-N 本端流程骨架不变,新增 F-O / F-P / F-Q 为平台侧流程(见 docs/平台端/06 §0 表 C-1),本端不感知统计、不新增上报义务;三端状态映射与数据同步机制以 CR-02 §15 表 3-66 为唯一权威,四能力业务规则 / 交互 / 权限 / 异常三端对照见 §15 表 3-65。平台新增 docs/平台端/10「公寓统计分析与排名报表」,统计结果不回写本端任何业务状态;#13 ARCHIVE 摘要仅按 payload 扩展分组计数(见 docs/公寓端/09 §0 表 C-9),不新增 bizType 与上报接口。

1. 计划下行与叫乘任务生成

sequenceDiagram
    participant P as 平台端
    participant M as apt-master-client
    participant DB as 本地库
    participant E as apt-dispatch

    P->>M: GET /platform/v1/plans/duty(gyId, from, to)
    M->>DB: 校验 apt_master_version 中 ROUTE/RULE 版本
    alt 版本过期
        M->>DB: 记 apt_master_version(FAILED, fail_reason)
        M-->>P: 拒绝接收,触发全量重拉
    else 版本一致
        M->>DB: upsert gy_syn_jcxx(plan_key 幂等)
        M->>DB: 更新 gy_syn_jcxx_status(watermark)
        E->>DB: 按 apt_master_rule 复算 jcsj/fjsj/jcbjsj
        E->>DB: 生成/更新 gy_jcxx(ryzt=0 待叫乘)
        E->>M: 复算差异(超阈值告警)
        M-->>P: POST /ack/tasks(accepted + computedCallTime)
    end

图 5-1 计划下行与任务生成

表 5-1 步骤与失败处理

步骤 动作 幂等键 失败处理 依据
1 拉取计划 gyId + planId + version 网络失败退避重试(1s/5s/30s/2min/10min) 表 6-2 重试行
2 版本校验 apt_master_version(master_type, version_no) 版本过期 → 拒绝任务 + 全量重拉 + 告警 表 5-2 第 2 行
3 落库计划快照 gy_syn_jcxx.plan_key 冲突则按 version 覆盖 docs/10 §5.2
4 本地复算 gy_jcxx.jcid(= 公寓分配的任务业务键,不是"计划侧业务键";id 为代理键) 要素(kcsj / jcfs)与平台不一致 → 以平台为准 + 告警;执行时刻 jcsj 及其派生恒以本地按规则快照复算为准(平台不产出权威 jcsj) D8'、R-C9 / R-C10、CR-02 CR-C06;表 5-2 第 6 行
5 回执 taskId + version 回执失败不影响执行(outbox 累积) 表 6-1 #8

边界:公寓端不做"计划转换为任务"的规则定义(docs/10 表 4-1 任务生成行:平台 ● / 公寓 ○ 复算校验), 只做复算与回执。

2. 叫乘执行(任务 → AMI → 会议桥 → 事件 → 状态机)

sequenceDiagram
    participant Q as Quartz(apt-dispatch)
    participant S as apt.duty.state
    participant V as apt.voice.event
    participant A as apt.voice.ami
    participant AST as Asterisk
    participant DB as 本地库

    Q->>Q: 到期扫描 gy_jcxx(ryzt=0, stoped=0, jcsj<=now)
    Q->>S: command(CALL_START, jcid)
    S->>DB: 条件更新 ryzt 0→1, jcsj_s=now(3)
    Q->>V: originate(room, console)
    V->>A: Originate PJSIP/room + PJSIP/console
    A->>AST: AMI 命令
    AST-->>A: OriginateResponse(Success/Failure)
    A-->>V: 事件
    alt Success
        AST-->>A: ConfbridgeJoin(Conf-01, PJSIP/room)
        A-->>V: 入会事件
        V->>DB: 写 apt_call_session(state=JOINED_CONF, join_at)
        V->>S: command(ANSWERED, jcid)
        S->>DB: 条件更新 ryzt→3, hjxysj, finish_jc='1'
        V->>DB: 生成 apt_tts_utterance + apt_recording 索引
    else Failure
        A-->>V: 失败原因
        V->>S: command(CALL_FAILED, jcid)
        S->>DB: 条件更新 ryzt→2, jcbjsj_s, is_jcbj='1'
        V->>DB: 写 gy_bjxx(NO_ANSWER)
    end

图 5-2 叫乘执行与状态回写

表 5-2 关键判定与口径(docs/10 §7.9)

判定 口径 落点
接通判定 用 ConfbridgeJoin,不是 Answer apt_call_session.join_at
无应答超时 30s 内无 ConfbridgeJoin 且通道不存在 ryzt→2 + gy_bjxx(TIMEOUT)
接通即断 入会后 < 3s Hangup 判异常中断,触发复叫 + 告警
会议桥不可用 ConfBridge 应用不存在或入会失败 直接转值班台兜底(/ws/duty 推送)
挂断原因 16 正常 / 17 忙 / 18 无应答 / 19 不可达 / 21 拒绝,其余记"未知"+ 原值 apt_call_session.hangup_cause
复叫 复叫判定 35s;接听后自动挂断 40s;喊话 30s gy_jcxx.fjsj_s
事件丢失兜底 每 5 分钟 core show channels concise 与在途会话比对 有库无通道 → HANGUP(UNKNOWN);有通道无库 → 孤儿通道告警

三条不可违反的规则:

  1. 只经状态机:apt.voice.event 不直接 update gy_jcxx,只提交命令给 apt.duty.state(docs/10 表 4-4)。
  2. 条件更新:所有状态更新带 where id=? and ryzt=<期望值>,避免"先查后改"脏写(表 10-1 状态机多写行)。
  3. 已结束不回写:finish_jc='1' 后迟到事件只写 apt_call_event(03 §3.2 保护语义)。

3. 设备接入(注册 / 心跳 / 指令 ACK)

sequenceDiagram
    participant D as 现场设备
    participant G as apt.device.gateway
    participant C as apt.device.command
    participant DB as 本地库

    D->>G: POST /open/v1/device/register(deviceCode + 激活码)
    G->>DB: 写入 gy_sb(gyid + deviceCode 绑定)
    G-->>D: 返回 secret(一机一密)+ serverTime

    loop 每次心跳
        D->>G: POST /open/v1/device/heartbeat(HMAC(secret, body))
        G->>G: 校验时间窗 ±5min + nonce 去重 + gyid 绑定
        G->>DB: upsert gy_sb_xt(version/ip/heartbeat_at/clock_skew_ms)
        G->>C: 取待投递指令(priority, next_send_at)
        C->>DB: state PENDING→SENT, sent_at
        G-->>D: {serverTime, commands[]}
        D->>G: POST /open/v1/device/command/ack(commandId, result)
        G->>C: state SENT→ACKED, ack_at/ack_result
    end

图 5-3 设备接入与指令闭环

表 5-3 与 docs/10 §7.2、§7.3 的对应与本期边界

能力 目标设计(docs/10) 本期实现 未实现时的行为
注册认证 一机一密,替换全局口令 否(依赖固件与协议确认) 保留口令模式开关;/open/** 仍为占位
心跳 HMAC + 时间窗 + nonce 去重 否(同上) 只落库(gy_sb_xt),不校验签名
指令 下发—ACK—重投—过期,P0 重投 5 次不过期 仅入队 apt_device_command.state=PENDING,不发送
状态采集 在线/离线、版本、故障码,按小时聚合只报摘要 是(落库 + 摘要) —
离线告警 超阈值产生"设备离线"告警进盯控列表 是(gy_bjxx) —

诚实的边界:在设备协议、固件 ACK 能力、连通性三项确认前,接口只写队列,不伪造 SENT/ACKED(04 §7 第 8 条)。 页面必须显示"已入队,待协议确认"。

4. 语音事件、录音索引与 TTS

flowchart TB
    A["AMI 事件到达"] --> B{"事件类型"}
    B -->|OriginateResponse| C["apt_call_session: CALLING→FAILED 或继续"]
    B -->|Newstate| D["CALLING→RINGING, 记 ring_at"]
    B -->|ConfbridgeJoin| E["RINGING→JOINED_CONF, 记 join_at"]
    B -->|ConfbridgeLeave| F["JOINED_CONF→LEFT, 记 leave_at"]
    B -->|Hangup| G["任意→HANGUP, 记 hangup_at/cause/duration"]
    B -->|Disconnect| H["在途会话标 UNKNOWN, apt_ast_ami_session 记断连"]
    E --> I["提交状态机命令 ANSWERED"]
    E --> J["生成 apt_tts_utterance(seq 递增)"]
    E --> K["录音:事件带 recordfile 则写 apt_recording(AVAILABLE)"]
    K --> L{"无 recordfile"}
    L -->|是| M["按文件名约定反查目录 → 有则 AVAILABLE,无则 MISSING + 告警"]
    G --> N["提交状态机命令 HANGUP"]

图 5-4 事件到业务状态与媒体索引

表 5-4 录音与 TTS 的回写规则(docs/10 §7.9 录音/TTS 表)

能力 触发时机 成功判定 失败回写 本表落点
录音 房间侧入会(Asterisk 侧策略) 事件带 recordfile 或目录存在文件 state=MISSING + 告警,不影响叫乘结论 apt_recording
TTS 入会后、播放前 Playback 成功且 PLAYING→DONE state=FAILED + 原因,可人工重播(source=TEST) apt_tts_utterance
复叫 TTS 复叫入会后 同上,seq 递增 同上 同上
清理 每日低峰(0 30 2 * * ?) 先置 PURGED 再删文件 失败重试 + 告警 apt_recording

四条设计约束:

  1. gy_jcxx.recordfile 只读兼容:新会话只写 apt_recording,不再拼逗号串(表 5-3 处理关系第 2 条)。
  2. TTS 不依赖内存槽位:改用 apt_tts_utterance 队列,避免 conftts 单槽位并发覆盖(表 10-3 TTS 行)。
  3. 录音不阻断叫乘:MISSING 只告警,不改变 gy_jcxx 状态。
  4. AMI 断开:在途会话标 UNKNOWN,由 5 分钟巡检判定终态(表 7-5 AMI Disconnect 行)。

5. 主数据下行与快照生效

flowchart LR
    A["公寓端定时拉取(下行一律拉,平台不推)"] --> B["校验格式与 checksum"]
    B --> C{"version > 本地?"}
    C -->|否| D["丢弃 + 记日志(幂等)"]
    C -->|是| E["事务:写快照 + apt_master_version(APPLIED)"]
    E --> F{"apply_result"}
    F -->|FAILED| G["保留旧快照 + 告警 + 不接收新任务"]
    F -->|APPLIED| H["更新 apt_master_version.applied_at/expires_at"]
    H --> I["通知依赖模块刷新缓存"]
    I --> J{"过期未续?"}
    J -->|是| K["仅降级:新计划不生效 + 告警(不阻断叫乘)"]

图 5-5 快照生效与过期降级

表 5-5 快照域的一致性要求(docs/10 表 5-1、表 5-2 第 2 行)

快照 版本键 生效方式 过期后果
公寓档案 (gyid, APARTMENT, source, version) 全量覆盖(单行) 告警
房间基线 (gyid, ROOM, source, version) 全量覆盖(按 room_no upsert) 新房间不生效
人员 (gyid, PERSON, source, version) 增量 upsert + deleted 标记 新人员无法叫乘(已有记录不受影响)
交路 (gyid, ROUTE, source, version) 全量覆盖 新交路不生效,已生成任务照常执行
规则/节假日 同上 全量覆盖 保持最后已知版本,仅告警

三条原则:

  1. 快照只读:业务模块不得写 apt_master_*(03 表 3-1)。
  2. 版本不一致拒绝任务,不是拒绝启动(docs/10 表 5-2 第 2 行)。
  3. 过期只降级不阻断:叫乘主链路必须能离线跑(表 7-6 第 1 行)。

6. 上报 outbox 与重试对账

sequenceDiagram
    participant B as 业务模块
    participant O as apt-report.outbox
    participant DB as 本地库
    participant P as 平台端

    B->>O: enqueue(bizType, bizId, bizVersion, payload)
    O->>DB: insert apt_report_outbox(watermark 自增, idempotencyKey)
    loop 定时派发(按 watermark 升序)
        O->>DB: 取 PENDING/FAILED 且 next_retry_at<=now(批量 ≤500)
        O->>P: POST /platform/v1/report/**
        alt 成功
            O->>DB: state=SENT, sent_at, platform_recv_at
            O->>DB: 更新 apt_report_watermark(acked_watermark)
        else 失败
            O->>DB: retry_count+1, next_retry_at=退避+抖动
            O->>DB: 连续失败 → FAILED → DEAD + 告警
        end
    end
    P-->>O: 日对账结果(count / sum / max watermark)
    O->>DB: 差异写告警,人工触发 replay

图 5-6 outbox 派发与对账

表 5-6 可靠性机制(docs/10 表 6-2)

机制 设计 落点
幂等 hash(gyid+bizType+bizId+bizVersion) 唯一 uk_outbox_idem
重试 1s / 5s / 30s / 2min / 10min,上限 1 小时 + 抖动 next_retry_at
顺序 同 bizId 按 bizVersion 单调;乱序丢弃旧版本 派发前比对 biz_version
断网 落本地库持续累积 state=PENDING
降级 积压超阈值只上报 P0 派发查询加 priority 过滤(biz_type 白名单)
对账 日比对 count、sum(amount)、max(watermark) 平台侧执行,公寓端接差异

本期边界:平台接收端未就绪时,outbox 只累积不发送(state 停在 PENDING),这也是可验证的: 联调前置条件满足后,把派发开关打开即可,不需要改业务代码。

7. 值班台人工干预

sequenceDiagram
    participant U as 值班员(duty 入口)
    participant C as apt-duty.command
    participant S as apt.duty.state
    participant DB as 本地库

    U->>C: POST /duty/call/{jcid}/commands(commandId, STOP)
    C->>DB: 校验权限点 + gyid
    C->>DB: insert duty_manual_action(action_id 幂等, ACCEPTED)
    C->>S: 提交状态机命令
    alt 命令合法
        S->>DB: 条件更新 gy_jcxx(ryzt/stoped/…)
        S->>DB: duty_manual_action → DONE
        S->>DB: duty_operation_log(谁/何时/对谁/做了什么)
        C-->>U: accepted + stateAfter
    else 状态不允许
        S-->>C: 拒绝(APT_STATE_ILLEGAL)
        C->>DB: duty_manual_action → FAILED + result
        C-->>U: 409 + 原因
    end

图 5-7 人工干预链路(docs/10 §2.5 结尾、§3.3 表 3-1)

表 5-7 干预动作与审计

动作 目标 允许的当前状态 审计 备注
STOP 停止叫乘 gy_jcxx ryzt in (1,2)、finish_ly='0' duty_operation_log + duty_manual_action —
RE_CALL 复叫 gy_jcxx stoped='1' 或 ryzt=2 同上 生成新会话
FORCE_FINISH 强制结束 gy_jcxx finish_ly='0' 同上 不置离寓
MARK_LEAVE 登记离寓 gy_jcxx + gygl_ly_event finish_ly='0' 同上 leave_type=MANUAL
HANGUP 挂断 apt_call_session state in (CALLING,RINGING,JOINED_CONF) 同上 表 6-3 #14

为什么必须两层表:duty_manual_action 记录"命令意图与结果"(可幂等、可重试、可追责), duty_operation_log 记录"审计事实"(可查询、可导出、保留期独立)。两者不合并,原因是审计表可能被归档, 而命令表需要长期保留以支持重试与幂等判断(docs/10 §2.5 结尾)。

8. 启动自检与运行期巡检

表 5-8 自检项(docs/10 表 7-7 的落地)

# 检查项 判定 失败级别 落点
1 AMI 可达与登录 TCP + login() BLOCKED apt_ast_ami_session / apt_ops_selfcheck
2 Asterisk 版本与能力 core show version DEGRADED 同上
3 会议桥存在 Conf-01 可用 BLOCKED 同上
4 拨号计划上下文 from-internal 含 1901/1701 BLOCKED 同上
5 值班台分机在册且注册 CONSOLE 类型 + 注册状态 DEGRADED apt_sip_endpoint
6 台账与 Asterisk 一致 pjsip show endpoints 比对 DEGRADED apt_ast_drift
7 录音目录可写与空间 试写 + 剩余 > 阈值 DEGRADED apt_ops_selfcheck
8 录音播放地址可达 recording.base-url 200 DEGRADED 同上
9 TTS 通道可用 试播 TEST DEGRADED apt_tts_utterance
10 串口可打开 console.port DEGRADED apt_ops_selfcheck
11 时钟偏差 NTP < 1s DEGRADED 同上
12 AMI 事件滞后 last_event_at 差 DEGRADED apt_ast_ami_session

可叫乘探针的判定:BLOCKED 项任一失败 → /apt/ops/health/call-ready 返回 callReady=false + 未就绪原因列表; DEGRADED 项失败 → 仍 callReady=true,但响应带 degraded[](对应表 6-3 要点 4:"健康探针分两个")。

9. 叫乘两类实现方式的端到端业务流程

追溯:docs/公寓端/00 表 0-3(A-06 / A-07 / A-08)、docs/10 §7.7(语音链路拓扑)、§7.9 表 7-5(事件到业务状态)、表 7-6(本地闭环)、docs/设计变更/03 §7.1 F-A / §7.2 F-B、§10 F-I、docs/公寓端/04 §0 表 C-2、docs/应用端/04 表 4-12。 定位:本节是 A-06A-08 的流程权威表述。§1(任务生成)、§2(SIP 执行与状态机)、§4(语音事件与媒体)、§6(outbox)、§7(人工干预)的骨架不变,本节把"触发条件 → 交互流程 → 状态上报 → 结果反馈"串成两条端到端链路,并补齐此前未展开的平板本地定时播放环节。本节图表沿用本文件主序号 表 5-95-21、图 5-8~5-9;§0 补遗使用独立的 表 C-y,两者不混用。

9.1 方式对照与模式选择

表 5-9 两类叫乘方式对照

维度 第一类:SIP 语音叫乘 第二类:平板叫乘
执行主体 Asterisk 主动发起呼叫(Originate),房间 SIP 终端接听 平板拉取任务后本地定时播放语音
触发 服务端 Quartz 到 jcsj 扫描,或值班员人工发起 服务端生成任务 + 平板拉取 + 平板本地到 jcsj
应答 / 完成判据 AMI ConfbridgeJoin(房间侧入会) 平板刷脸签到 faceVerifyState=VERIFIED
提示语音 入会后 Playback(tts),落 apt_tts_utterance 平板本地合成 / 播放,语音文件不出公寓服务端
状态来源 业务系统依据 AMI 事件推断 平板上报叫乘结果
结果上报路径 AMI → apt.voice.event → 状态机命令 → #8 / #9 / #24 平板 → POST /open/v1/tablet/results → apt.device.gateway → 状态机命令 → #9
关键表 apt_call_session、apt_call_event、apt_recording apt_tablet_task、apt_tablet_call_result
失败兜底 30s 无入会 → ryzt=2 + 复叫 35s;AMI 断连 5 分钟巡检比对 未刷脸 / 超时 → 复叫 + 告警;任务过期 40916 转人工
断网 本地闭环可用(docs/10 表 7-6) 已缓存任务照常播放,结果本地排队恢复后续传

表 5-10 模式选择规则

规则 口径
gy_jcxx.call_mode 由 #7 任务变更下行或本地房间配置确定,取值 SIP(默认)/ TABLET;一条任务只走一种方式,执行中不切换
回落 #1 下发 features.tabletCall=false,或平板未激活 / 离线时,TABLET 一律回落 SIP 并记录回落原因
前置校验 SIP 走 F-I 分机注册离线判定(docs/公寓端/00 §3.5),告警不阻断叫乘;TABLET 走平板在册与心跳校验
顺序派发 按 jcsj 升序生成 dispatch_seq,单公寓同一时刻只派发一条(CR-02 F-A 第 3 步)
共用 两条链路共用 apt.duty.state 状态机、gy_jcxx 状态字段、apt_report_outbox 上行通道与 /ws/duty 反馈通道

9.2 第一类:SIP 语音叫乘(Asterisk 主动呼叫)

表 5-11 触发条件(全部满足才发起)

# 条件 不满足时
1 gy_jcxx.call_mode='SIP' 且 ryzt=0、stoped=0、finish_jc='0' 已结束任务不重复发起
2 Quartz 到期扫描 jcsj <= now,或值班员 POST /duty/call/{jcid}/voice(duty:call:voice,L2 commandId) 同 commandId 只生效一次
3 jcid → fjh → apt_sip_binding(ROOM, is_primary=1, 生效区间有效) → apt_sip_endpoint 能解析到房间主分机,且 enabled=1、deleted=0、sync_state='SYNCED' 绑定缺失记 apt_ast_drift;未收敛返回 40905
4 AMI 可达、Conf-01 会议桥存在、from-internal 拨号计划就绪(表 5-8 自检项 1 / 3 / 4) BLOCKED → /apt/ops/health/call-ready 返回 callReady=false,转值班台兜底
5 分机注册态按 F-I 判定;离线只写 apt_device_alarm 并告警 不阻断,失败原因记 apt_call_session.hangup_cause

图 5-8 SIP 语音叫乘端到端时序

sequenceDiagram
    autonumber
    participant Q as Quartz / 值班员命令
    participant S as apt.duty.state
    participant V as apt.voice.endpoint / event
    participant A as apt.voice.ami
    participant AST as Asterisk
    participant T as 房间 SIP 终端
    participant DB as 本地库
    participant W as /ws/duty + outbox

    Q->>S: command(CALL_START, jcid)
    S->>DB: 条件更新 ryzt 0→1, jcsj_s=now(3)
    S-->>W: state.changed(0→1)
    V->>A: Originate PJSIP/room + PJSIP/console
    A->>AST: AMI Originate
    AST->>T: SIP INVITE(Asterisk 主动发起)
    AST-->>A: OriginateResponse(Success)
    T-->>AST: 接听并入会
    AST-->>A: ConfbridgeJoin(Conf-01, PJSIP/room)
    A-->>V: 事件(写 apt_call_event)
    V->>DB: apt_call_session(state=JOINED_CONF, join_at)
    V->>S: command(ANSWERED, jcid)
    S->>DB: ryzt→3, hjxysj, finish_jc='1'
    V->>DB: apt_tts_utterance(PENDING) + apt_recording 索引
    AST->>T: Playback(tts) 播放提示语音
    A-->>V: Playback 结果 / Hangup
    V->>DB: 更新 apt_tts_utterance(DONE/FAILED) + apt_call_session(HANGUP)
    S-->>W: state.changed(1→3) + session.changed
    W->>DB: outbox enqueue(#8 回执 / #9 DUTY_RESULT / #24 VOICE_SUMMARY)

表 5-12 SIP 语音叫乘交互流程

步 主体 动作 落表 失败 / 补偿
1 Quartz / 值班员命令 到 jcsj 触发,提交 CALL_START 命令 duty_manual_action(人工触发时) 状态不允许 → APT_STATE_ILLEGAL
2 apt.duty.state where id=? and ryzt=0 条件更新 ryzt=1、jcsj_s gy_jcxx 推 state.changed(0→1)
3 apt.voice.endpoint 解析房间主分机,双路 Originate(房间 + 值班台) apt_sip_binding、apt_call_session(CALLING) 解析失败按表 5-11 第 3 条
4 apt.voice.ami → Asterisk 发 AMI Originate,Asterisk 向房间 SIP 终端发 INVITE apt_call_event OriginateResponse.Failure → CALL_FAILED 分支(表 5-13、表 5-20)
5 房间 SIP 终端 接听并入会(ConfbridgeJoin,会议号 Conf-01) apt_call_session(RINGING → JOINED_CONF) 30s 无入会 → TIMEOUT
6 apt.voice.event 依 AMI 事件判应答,提交 ANSWERED 命令 → 状态机置 ryzt=3、finish_jc='1'、hjxysj gy_jcxx、apt_call_event 只提交命令,不直接 update gy_jcxx
7 apt.voice.media 入会后 Playback(tts) 播放提示语音,生成录音索引与 TTS 播报单元 apt_tts_utterance、apt_recording FAILED / MISSING 只告警,不改叫乘结论
8 apt.voice.event / apt-report Hangup 归档会话;终态经 #8 / #9 / #24 上行;值班台经 /ws/duty 反馈 apt_call_session、apt_report_outbox AMI 断连标 UNKNOWN,5 分钟巡检兜底

表 5-13 AMI 事件 → 业务状态 → 上报与反馈映射(沿用 docs/10 表 7-5、本文件 表 5-2)

AMI 事件 / 兜底 业务判定 状态机命令 → gy_jcxx 上行 值班台反馈
OriginateResponse.Success + Newstate 呼叫中 / 振铃 CALL_START(ryzt=1、jcsj_s) #8 回执(含 computedCallTime) session.changed + D1 起倒计时
ConfbridgeJoin(房间侧) 应答 / 完成叫乘 ANSWERED → ryzt=3、finish_jc='1'、hjxysj #9 DUTY_RESULT(ANSWERED)、#24 摘要 state.changed(1→3) + session.changed
OriginateResponse.Failure / 30s 无入会 失败 / 无应答 CALL_FAILED → ryzt=2、jcbjsj_s、is_jcbj='1' #9 + #12 ALARM alarm.raised(NO_ANSWER) + 复叫 35s
入会后 < 3s Hangup 异常中断 保持终态并触发复叫 #9 + #12 告警 + 行置红
Hangup(正常) 会话结束 不回写 gy_jcxx(已结束不回写) #24 摘要 session.changed(HANGUP)
AMI Disconnect 在途会话 UNKNOWN 不推定终态 — 5 分钟 core show channels concise 比对
录音 / TTS 失败 媒体异常 不改叫乘结论 #24 recordingMissing notice.sync / 告警

"接听后播放提示语音完成叫乘"的落地口径:接通判据是 ConfbridgeJoin(房间侧入会),不是 SIP Answer(docs/10 §7.7 第 2 条、本文件 表 5-2)——Answer 只代表摘机,不代表房间内人员能听到。入会即提交 ANSWERED 完成叫乘,随后 Playback(tts) 播放提示语音,apt_tts_utterance 的 DONE/FAILED 与 apt_recording.state 不回退、不改写叫乘终态(沿用 §4 第 3 条"录音不阻断叫乘")。 "叫乘状态由业务系统依据 AMI 事件进行上报"的落地口径:apt.voice.ami 收原始事件 → apt.voice.event 依表 5-2 / 表 7-5 判业务状态 → 提交状态机命令(唯一写入口 apt.duty.state)→ 条件更新 gy_jcxx → apt-report 经 #8 / #9 / #24 上行。AMI 事件本身既不直接写业务表,也不单独构成对外上报。

9.3 第二类:平板叫乘(拉取 + 本地定时播放 + 刷脸签到)

表 5-14 触发条件(按环节)

触发环节 条件
任务生成 gy_jcxx.call_mode='TABLET'、ryzt=0、stoped=0;#1 下发 features.tabletCall=true;按 jcsj 升序生成 dispatch_seq,写 apt_tablet_task(WAITING)
平板可拉取 apt_tablet_device.register_status=ACTIVE、心跳在窗内;GET /open/v1/tablet/tasks?deviceCode&cursor&limit(N-03)按 dispatch_seq 顺序返回
本地播放 平板提前拉取并缓存任务,按任务中的 jcsj(预定时间点)由平板本地定时器触发播放;以心跳下发的 serverTime 校准本地时钟,clockSkewMs 超阈值时重拉并告警
刷脸签到 播放后进入签到窗口(signTimeoutSec,默认 60s,Q-29);刷脸 VERIFIED 即结束叫乘
结果上报 POST /open/v1/tablet/results(N-04),resultId 幂等 + HMAC / 时间窗 / nonce

拉取 ≠ 执行:服务端只生成任务,不向平板推送呼叫指令(平台不回调公寓、设备侧一律拉取,CR-C08);"到达预定时间点后播放语音"由平板本地完成。断网时已缓存任务仍可按 jcsj 播放,结果写入平板本地队列,恢复后按 resultId 重传。

表 5-15 平板任务负载字段(N-03 响应 tasks[],沿用 CR-02 表 3-8 的 /open/v1/tablet/**)

字段 类型 说明
taskId / jcid / dispatchSeq string / string / long 任务、叫乘任务、派发序号三层键
jcsj / expireAt datetime 预定播放时刻(公寓本地复算值,CR-C06)与任务失效时刻
roomNo / ryid / personName string 房间与人员
ttsTemplate / ttsText string 提示语音模板与文本,平板本地合成 / 播放
faceVerifyRequired / signTimeoutSec boolean / int 是否必须刷脸、签到窗口
serverTime / clockSkewMs datetime / long 时钟校准(与 #14 心跳同源,见 docs/公寓端/09 表 9-11)
cursor string 拉取游标,单调递增

图 5-9 平板叫乘端到端时序

sequenceDiagram
    autonumber
    participant E as apt-dispatch
    participant DB as 本地库
    participant G as apt.device.gateway
    participant T as 平板终端
    participant U as 人员
    participant S as apt.duty.state
    participant W as /ws/duty + outbox

    E->>DB: 按 jcsj 生成 apt_tablet_task(WAITING, dispatch_seq)
    T->>G: GET /open/v1/tablet/tasks(cursor)【提前拉取】
    G-->>T: tasks[](jcsj, ttsText, faceVerifyRequired, serverTime)
    T->>G: POST /open/v1/tablet/tasks/{taskId}/ack(N-05)
    G->>DB: apt_tablet_task(PULLED, pulled_at, pulled_by)
    T->>T: 按 serverTime 校准,本地缓存任务
    Note over T: 到达 jcsj(预定时间点)
    T->>U: 本地播放提示语音
    U->>T: 刷脸确认签到
    alt 刷脸通过
        T->>T: 生成结果(ANSWERED / VERIFIED / resultId)
    else 未刷脸或超时
        T->>T: 生成结果(UNVERIFIED / NO_ANSWER / resultId)
    end
    T->>G: POST /open/v1/tablet/results(N-04,HMAC + nonce)
    G->>DB: 幂等校验 → apt_tablet_call_result(PENDING)
    G->>S: 只提交状态机命令(不直改 gy_jcxx)
    S->>DB: VERIFIED → ryzt=3 / finish_jc='1';否则 ryzt=2 + 复叫 + 告警
    S-->>W: state.changed + tablet.result
    W->>DB: outbox enqueue(#9 TABLET_RESULT)

表 5-16 平板叫乘交互流程

步 主体 动作 落表 失败 / 补偿
1 apt-dispatch 按 jcsj 生成任务 WAITING + 单调 dispatch_seq apt_tablet_task、gy_jcxx 规则快照过期 → 40903
2 平板 GET /open/v1/tablet/tasks(N-03,游标)提前拉取 — 拉取失败按退避重试,不影响本地已缓存任务
3 平板 → apt.device.gateway POST /open/v1/tablet/tasks/{taskId}/ack(N-05) apt_tablet_task(PULLED) 重复 ACK → 40916
4 平板 按 serverTime 校准本地时钟并缓存任务 平板本地 偏差超阈值重拉 + 告警
5 平板 到达 jcsj 本地播放提示语音 平板本地 无音频资源 → 本地降级提示并上报 FAILED
6 人员 在签到窗口内通过平板刷脸确认签到 平板本地 未刷脸 / 超时 → UNVERIFIED / NO_ANSWER
7 平板 生成 resultId 与结果(resultType + faceVerifyState + verifiedRyid) 平板本地 只传识别结论,不传抓拍帧 / 人脸原图 / 比对帧;比对所需特征值按 docs/公寓端/10 的设备范围下发
8 平板 → apt.device.gateway POST /open/v1/tablet/results(N-04) apt_tablet_call_result HMAC / 时间窗 / nonce 校验;同 resultId 返回首次结果
9 apt.device.gateway → apt.duty.state 校验幂等后只提交状态机命令 apt_tablet_call_result(PENDING) 不直改 gy_jcxx;校验失败丢弃并告警
10 apt.duty.state / apt-report 按表 5-20 落终态,推 tablet.result + state.changed,#9 上报 TABLET_RESULT gy_jcxx、apt_report_outbox 断网进 outbox,恢复后按原幂等键续报

表 5-17 平板侧可靠性口径

项 口径
定时 以任务 jcsj 为准由平板本地定时器触发;clockSkewMs > 1s 重拉校准(表 5-8 自检项 11)
断网 已缓存任务照常播放;结果写平板本地队列,恢复后按 resultId 重传,幂等不产生第二次终态
重放 同一 taskId 只播放一次(可配重播次数,Q-29);重复拉取 / ACK 返回 40916
过期 now > expireAt 或任务已被其他平板拉取 → 40916,置 EXPIRED 并转人工
人脸数据 平板叫乘结果只传 verifiedRyid 与 faceVerifyState,抓拍帧与人脸原图不上报、不上传平台;比对所需特征值按 docs/公寓端/10 的设备权限范围下发;人员主数据照片与特征值按 CR-C24 允许平台 ↔ 公寓双向同步(CR-02 §12)
本期边界 本地定时与断网续传依赖终端固件能力;协议未确认前(50101)只在设计层描述,不伪造成已实现(§3 表 5-3「诚实的边界」)

9.4 状态上报机制(两类方式统一)

表 5-18 状态来源 → 状态机 → 上行

环节 SIP 语音叫乘 平板叫乘 共同约束
事实来源 AMI 事件(apt.voice.ami) 平板上报 TABLET_RESULT(apt.device.gateway) 一条任务只有一种事实来源,由 gy_jcxx.call_mode 标记
业务判定 apt.voice.event 按表 5-2 / docs/10 表 7-5 映射 按 faceVerifyState + resultType 映射 都不直接 UPDATE gy_jcxx
状态落地 提交 CALL_START / ANSWERED / CALL_FAILED 命令 提交 ANSWERED / CALL_FAILED 命令 apt.duty.state 唯一写入口;where id=? and ryzt=<期望值>;已结束不回写
幂等 session_id / event_seq resultId(UK (gyid, result_id)) 迟到 / 重复结果记 IGNORED,不产生第二次终态
上行 #8 回执、#9 DUTY_RESULT、#24 VOICE_SUMMARY #9 TABLET_RESULT、#8 回执(含 dispatchSeq)、#13 归档摘要 apt_report_outbox + idempotencyKey = hash(gyid+bizType+bizId+bizVersion);断网累积、serverReceivedAt 归日
状态字段 ryzt / finish_jc / hjxysj / jcbjsj_s 同左(语义一致) 值班台与平台按同一套终态理解,不因实现方式分叉

三条不可违反规则(§2)继续适用。本节新增两条:

  1. 方式内事实来源唯一:SIP 任务不接受平板上报结果,平板任务不套用 AMI 事件推断;一条 jcid 的终态只能由一种事件源触发。
  2. 跨端上报一律带 callMode:#9 / #24 的 payload 用 SIP / TABLET 分组,平台按方式统计;不新增 bizType、不新增跨端接口编号、不改变 v2-draft 契约形态。

9.5 结果反馈路径

表 5-19 四条反馈路径

# 路径 SIP 语音叫乘 平板叫乘 约束
1 值班台(端内) /ws/duty state.changed(0→1、1→3)、session.changed;异常 alarm.raised / alarm.cleared state.changed + tablet.result;异常 alarm.raised D1 行状态、D5 会议桥占用、D6 设备状态;seq 单调 + 60s 兜底轮询(docs/应用端/04 表 4-12)
2 平台(跨端) #8 任务回执(含 computedCallTime)、#9 DUTY_RESULT、#24 语音质量摘要、#13 归档 #9 TABLET_RESULT、#8 回执(含 dispatchSeq)、#13 归档;不发 #24 语音摘要 公寓主动推、平台不回调;payload 带 callMode;断网 outbox + 续报(CR-02 F-E)
3 本地留痕 gy_jcxx、apt_call_session、apt_call_event、apt_recording、apt_tts_utterance gy_jcxx、apt_tablet_task、apt_tablet_call_result 共用 duty_operation_log 审计与 apt_report_history 上报终态
4 异常兜底 30s 无入会 → gy_bjxx(NO_ANSWER) + 复叫 35s;接通即断 → 复叫 + 告警;AMI 断连 → UNKNOWN + 5 分钟巡检;会议桥不可用 → 值班台兜底 UNVERIFIED / SKIPPED → 复叫 + 告警(Q-22);NO_ANSWER → gy_bjxx(TIMEOUT);任务过期 40916 → 转人工;平板离线 → apt_device_alarm 异常同样经状态机与 outbox 上报,不另设"人工填状态"入口

表 5-20 终态矩阵(两类方式共用一套 gy_jcxx 语义)

情形 事件 / faceVerifyState 状态机命令 gy_jcxx 上行 值班台
SIP 接通 ConfbridgeJoin ANSWERED ryzt=3、finish_jc='1'、hjxysj #9 DUTY_RESULT + #24 state.changed(1→3) + session.changed
SIP 无应答 30s 无入会 / OriginateResponse.Failure CALL_FAILED ryzt=2、jcbjsj_s、is_jcbj='1' #9 + #12 alarm.raised(NO_ANSWER) + 复叫
平板刷脸签到 ANSWERED + VERIFIED ANSWERED ryzt=3、finish_jc='1' #9 TABLET_RESULT tablet.result + state.changed
平板未刷脸 ANSWERED + UNVERIFIED / SKIPPED 保持待确认 + 复叫 ryzt 不变(超窗口转 2) #9 + #12 tablet.result + alarm.raised
平板无应答 NO_ANSWER / FAILED CALL_FAILED ryzt=2 #9 alarm.raised + 复叫
任务取消 CANCELLED STOP(值班台) stoped='1',ryzt 不变 #9 state.changed
迟到 / 重复结果 任意 IGNORED 不回写 不重复上报 只记留痕(applied_state=IGNORED)

9.6 与既有章节的关系与待决策

  1. §1 任务生成、§2 SIP 状态机、§4 语音事件与媒体、§6 outbox、§7 人工干预继续有效;本节是两条链路的串联视图,不改任何状态字段写入口,也不改 docs/10 表 7-5 的事件映射。
  2. CR-02 §7.1 F-A / §7.2 F-B 继续有效;本节补齐"平板本地定时播放"(表 5-14、5-15)与"结果反馈路径"(表 5-19、5-20)两个此前未展开的环节。
  3. 不新增跨端接口编号、bizType、WS 消息名或数据表;TABLET_RESULT、device.alarm、state.changed 等全部沿用既有契约。
  4. 本期边界:平板本地定时与断网续传依赖终端固件与设备协议确认(D6 / 50101),未确认前只作为设计定义。

表 5-21 新增决策点(沿用 docs/应用端/10 的决议记录格式)

编号 待决事项 未决默认口径 拍板时点
Q-29 平板签到窗口 signTimeoutSec、语音重播次数与 clockSkewMs 阈值 60s / 1 次 / 1s 阶段二契约冻结前

10. 人员入住作业流程(CR-02 §13 流程索引)

权威表述:docs/公寓端/11-人员入住作业流程.md(CI-01CI-05、表 11-111-35、图 11-1~11-3)。本节只给出与叫乘流程的衔接关系,不重复定义状态机、数据表与接口字段。

表 5-22 入住作业五环节与叫乘衔接

环节 主体 关键动作 与既有章节的关系
CI-01 注册与签到 签到终端 / 值班台 扫脸核验;未注册先按 docs/公寓端/10 §4 完成注册并重新核验 复用 F-F 注册链路;不新增注册入口
CI-02 入住申请 签到终端 → apt-stay 选房间、交路(可确定时)提交申请;apt_local_task 建单并预分配 jcid 申请阶段不建 gy_jcxx 行(§1 任务生成口径不变)
CI-03 值班员审核 值班台 → apt-stay 审核、房间调整(仅审核期)、办理入住;checkin_state=CHECKED_IN 并写 gygl_ly_event(CHECKIN) 房间调整复用 F-C 换房联动但不产生换乘单;状态字段仍只经 apt.duty.state
CI-04 提报与匹配 apt-report / 平台 #9 CHECKIN 提报 → #6 运安计划 → #8 回执作匹配输入 → #7 响应体 matchVerdict[] 匹配结论唯一合规载体是 #7(R-05);match_state 沿用正式五值
CI-05 叫乘环节 apt-dispatch #7 拉任务 → 按 apt_master_rule 复算 jcsj → 回 #8 → 按 jcsj + dispatch_seq 派发 派发后进入 §9 两类叫乘流程;jcsj 恒由公寓复算(CR-C06)

三条衔接约束:① applyId ↔ jcid 一对一,gy_jcxx 沿用预分配 jcid;② 只有 CHECKED_IN 且有效在寓(finish_ly='0' AND sjlysj IS NULL)才生成叫乘任务,已离寓迟到任务记 IGNORED;③ "已入住"不新增 gy_jcxx 状态字段,流程态落 apt_local_task 补列(CR-C26)。本补遗不新增业务表(59 张不变)、不新增跨端编号、不新增 bizType。


11. 消费、充值与菜品管理(CR-02 §14 流程索引)

权威表述:docs/公寓端/12-消费充值与菜品管理.md(CF-01CF-03、表 12-112-34、图 12-1~12-4)。本节只给出与既有账务闭环(§6 F-D)及主数据下行(§5)的衔接关系,不重复定义状态机、数据表与接口字段。

表 5-23 三项流程与既有章节的衔接

流程 主体 关键动作 与既有章节的关系
CF-01 人员消费 消费终端 → apt.ledger.local → apt-report 按 PUBLISHED 食谱选菜并显示金额 → N-49 查补贴 / 现金额度 → "补贴优先、现金补足" → 一条 CONSUME 流水 → #10 上报 复用 §6 F-D 六步账务闭环与 F-E 上报历史;platform-ledger 唯一写主账
CF-02 人员线下充值 签到终端 → 值班台 → apt.ledger.local 扫码只识别公寓 → N-52 申请(申请表不存金额)→ 管理员 N-54 二次确认 → 只增 xjye → #10 同步 同样复用 F-D;不接线上支付,二维码不构成资金流;断网受 #1 offlineQuota 约束(Q-21 / Q-42)
CF-03 公寓菜品管理 公寓管理员 → platform.catering → apt.catering → 消费终端 N-57 发布阶段性食谱 → #4 recipes[] 下行 → N-48 按 version 主动拉取并缓存 → 点击查看菜品、加入选择菜单 属 §5 主数据下行的增量类型;食谱缓存只在终端本地,不落公寓本地业务表

三条衔接约束:① 三条流程共用 F-D 闭环与 #10 LEDGER_OFFLINE,bizId = flowId、幂等键复用、serverReceivedAt 归日不变;② 一笔消费只产生一条流水,allowanceAmount + cashAmount = abs(amount),不额外生成 ALLOWANCE_DEDUCT 以免平台 sum(amount) 双计;③ apt.ledger.local / platform-ledger 各自单写入口不变,本补遗不新增业务表(59 张不变)、不新增跨端编号、不新增 bizType。