Cloud-Edge Protocol: events.jsonl
PracticeMate 边端(iOS)与云端之间的数据协议规范。 events.jsonl 是连接学生练习、即时反馈、云端深度评估的唯一数据纽带。
练-评-教 三角架构
PracticeMate 的数据流形成一个三角闭环:
Student (PracticeMate 端)
│
│ events.jsonl(练习原始事件流)
▼
Cloud Examiner(云端考官)
│
│ Examiner Report(深度评估报告)
▼
Student ← 报告回传至学生端
│
│ events.jsonl 聚合(班级维度)
▼
Teacher (全智评 Web 教师后台) ← 班级仪表盘核心定位:
- PracticeMate 是学生入口——练习发生的地方,事件产生的源头
- 全智评 是教师后台——查看班级练习数据、发现共性问题、调整教学策略
- events.jsonl 是纽带——同一份数据格式,既服务学生个体评估,也支撑教师班级视图
即使 MVP 阶段不构建教师仪表盘,架构和数据格式从第一天就为此做好准备。events.jsonl 的 schema 设计确保:单个学生的事件流可以被聚合为班级维度的统计,无需回溯修改数据格式。
Instant Recap vs Examiner Report 层级分离
系统有两层反馈机制,职责清晰分离:
| 维度 | Instant Recap(即时回顾) | Examiner Report(考官报告) |
|---|---|---|
| 角色 | Coach(教练) | Examiner(考官) |
| 生成方式 | 规则聚合:按阶段统计 ✓/△/✗ 数量 | 云端 CCAE + 完整 rubric 评分 |
| 用户感知 | 温暖、不打分、即时、先说亮点 | 严谨、有分数、证据链、约 30s |
| 网络依赖 | 不需要——纯边端计算 | 需要云端模型 |
| 延迟 | 0s(练习结束即出) | 20-60s(云端处理) |
设计原则:Recap 是教练的鼓励性总结,让学生练完立刻有正反馈;Report 是考官的严谨评估,给出可信的成长证据。两者互补,不冲突。
Recap 的数据来源是 events.jsonl 中的事件计数(edge 本地即可完成),Report 的数据来源是完整 events.jsonl 上传后云端 CCAE 流水线处理。
设计原则
- Append-only:边端只追加,不修改已写入事件
- 离线优先:所有事件先落本地 jsonl,网络恢复后批量上传
- 幂等上传:相同 session_id + event_id 组合,云端去重
- 正向记录为主:优先记录学生做到了什么(详见下方"正向事件"章节)
- 诚实哲学:低置信度事件不隐藏,显式标记并延迟给考官判断
Schema 定义
每行一个 JSON 对象,字段如下:
{
"event_id": "uuid-v4", // 事件唯一 ID
"session_id": "uuid-v4", // 练习会话 ID
"ts": 0.0, // 相对于 session 开始的秒数(float)
"event_type": "string", // 事件类型(见下方枚举)
"phase": "string", // 当前阶段:preparation | operation | cleanup
"signal": "string", // 具体信号名
"value": {}, // 信号值(类型因 signal 而异)
"confidence": 0.0, // 边端模型置信度 [0, 1]
"alert_tier": "string", // "high" | "medium" | "low" — 置信度分层
"cloud_review_available": false, // 是否标记为需要云端复核
"transition_source": "string", // 可选:阶段切换来源(见下方说明)
"meta": {} // 可选:设备信息、模型版本等
}transition_source 字段
仅出现在 phase_transition 和 phase_timeout 事件中,标识阶段切换的触发来源:
| 值 | 含义 |
|---|---|
ai_detected | AI 模型自动检测到阶段完成,触发切换 |
user_manual | 用户手动点击按钮切换阶段 |
timeout | 阶段超时,系统询问后切换 |
这体现了混合切换策略:AI 自动检测为主、用户手动为辅、超时兜底。三种来源在数据层面平等记录,供云端分析学生的练习节奏。
事件类型枚举
会话生命周期
| event_type | 说明 | 典型 signal |
|---|---|---|
session_start | 练习开始 | session_begin |
session_focus | 仪式层专注引导完成 | focus_completed |
session_end | 练习结束 | session_complete |
phase_transition | 阶段切换 | preparation_to_operation, operation_to_cleanup |
phase_timeout | 阶段超时询问 | preparation_timeout, operation_timeout |
练习观察
| event_type | 说明 | 典型 signal |
|---|---|---|
posture_observation | 姿态观察 | bow_hold_check, left_hand_position |
audio_observation | 音频观察 | pitch_accuracy, rhythm_stability, tone_quality |
tempo_observation | 节奏观察 | tempo_consistency, tempo_change |
technique_observation | 技术动作观察 | vibrato_detected, shift_detected |
正向事件(Positive Events)
| event_type | 说明 | 典型 signal |
|---|---|---|
achievement | 达成某个标准 | intonation_stable_30s, bow_straight_10s |
improvement | 相比上次有进步 | pitch_improved, rhythm_more_stable |
streak | 连续正确 | correct_notes_streak_8 |
诚实标记事件
低置信度事件的处理方式——不隐藏,显式记录:
{
"event_type": "posture_observation",
"signal": "bow_hold_check",
"confidence": 0.35,
"alert_tier": "low",
"cloud_review_available": true,
"value": {
"edge_assessment": "possible_issue",
"note": "边端不确定,已标记待考官复核"
}
}诚实哲学:当边端模型对某个观察不够确定时(confidence < 0.5),不会假装没看到,而是:
- 显式记录事件,
alert_tier设为"low" cloud_review_available设为true- 即时回顾(Recap)中不展示该项(避免误导)
- 考官报告(Examiner Report)中由云端模型重新评估,给出可信结论
这确保了数据完整性——所有观察都被记录,但反馈的置信度对用户透明。
正向事件设计理念
为什么优先记录正向事件:
- 学习动机:正向反馈强化练习意愿,尤其对初学者
- 数据平衡:避免 events.jsonl 全是"错误",导致云端报告过于消极
- 教练角色:边端扮演教练,教练的首要职责是发现学生做对了什么
- 考官补充:严格的问题诊断交给云端考官,它有更强的模型和完整上下文
正向事件不是"降低标准",而是"先看到好的,再改进不足"。
事件语义三元组(Event Semantic Triple)(TD-020)
每个事件从"发生 → 观察 → 判断"经历三个语义阶段,不要混为一谈:
| 阶段 | 语义 | event_type 示例 | 说明 |
|---|---|---|---|
| check_requested | 端侧请求检查 | signal_check_requested | L2 采样到一帧,发起检查请求 |
| observation | 观察到现象 | signal_observation | 模型输出原始观察(如"瓶盖在桌上") |
| assertion | 给出判断 | signal_detected | 基于观察和 rubric 规则,给出最终断言 |
为什么要拆开?
- 可审计:考官可以看到"AI 看到了什么"和"AI 判断了什么",分别评估感知和推理的准确性
- 可打断:用户对 observation 阶段就可以纠正("我手位是对的,你没看到")
- 不确定项透明:observation 有但 assertion 置信度低 → 自然进入 Uncertainty Ledger
三元组在 events.jsonl 中的体现
// check_requested:端侧发起检查
{"event_type": "signal_check_requested", "signal_id": "hand_position_check", "perception_level": "L2", "ts": "..."}
// observation:模型观察到现象
{"event_type": "signal_observation", "signal_id": "hand_position_check", "detail": "检测到双手位置偏离胸骨中线", "confidence": 0.6, "ts": "..."}
// assertion:给出最终判断
{"event_type": "signal_detected", "signal_id": "hand_position_check", "assertion": false, "confidence": 0.6, "alert_tier": "low", "coach_line": "不太确定,先记下来", "ts": "..."}三元组不强制三条都产出——如果 assertion 置信度足够高(≥ 0.8),observation 可省略,直接从 check_requested 跳到 assertion。
上传协议
触发时机
- 练习结束时:session_end 后立即尝试上传
- 后台定时:每 5 分钟检查未上传的 session
- 网络恢复时:检测到网络从离线变为在线
上传格式
POST /api/v1/sessions/{session_id}/events
Content-Type: application/x-ndjson
{"event_id":"...","session_id":"...","ts":0.0,...}
{"event_id":"...","session_id":"...","ts":1.2,...}
...POST /sessions/:id/summary-card(TD-018)- 请求体:
{ recap_highlights, uncertainty_count, journey_progress, journey_id } - 返回:
{ card_id, teacher_view_url }— teacher_view_url 用于生成 QR 码
- 请求体:
响应
{
"status": "accepted",
"session_id": "uuid",
"events_received": 42,
"duplicates_skipped": 0
}重试策略
- 指数退避:1s → 2s → 4s → 8s → 最大 60s
- 最多重试 10 次后标记为 pending,等待下次触发
Rubric Runtime 说明
events.jsonl 协议与具体评分标准(Rubric)解耦。同一套事件格式适用于任何 Rubric:
- 更换 Rubric YAML 文件(如从"CPR/BLS"切换到"OSCE 洗手")
- 事件类型和 schema 不变
- 云端 CCAE 流水线根据不同 Rubric 解读相同事件,生成对应报告
这意味着:新增乐器或新增评分维度时,只需新增 Rubric YAML,无需修改客户端事件采集逻辑。
完整 Session 示例(3 分钟练习)
{"event_id":"e001","session_id":"s-abc-123","ts":0.0,"event_type":"session_start","phase":"preparation","signal":"session_begin","value":{"instrument":"violin","piece":"twinkle_twinkle","rubric_version":"v1.2"},"confidence":1.0,"alert_tier":"high","cloud_review_available":false,"meta":{"device":"iPhone15","model_version":"edge-v0.3"}}
{"event_id":"e002","session_id":"s-abc-123","ts":2.5,"event_type":"session_focus","phase":"preparation","signal":"focus_completed","value":{"focus_duration_s":2.5,"ritual_type":"breathing"},"confidence":1.0,"alert_tier":"high","cloud_review_available":false}
{"event_id":"e003","session_id":"s-abc-123","ts":15.0,"event_type":"posture_observation","phase":"preparation","signal":"bow_hold_check","value":{"correct":true,"details":"拇指弯曲适当,小指放松"},"confidence":0.88,"alert_tier":"high","cloud_review_available":false}
{"event_id":"e004","session_id":"s-abc-123","ts":30.0,"event_type":"achievement","phase":"preparation","signal":"preparation_posture_ready","value":{"criteria_met":["bow_hold","left_hand","standing_posture"]},"confidence":0.85,"alert_tier":"high","cloud_review_available":false}
{"event_id":"e005","session_id":"s-abc-123","ts":35.0,"event_type":"phase_transition","phase":"operation","signal":"preparation_to_operation","value":{"from":"preparation","to":"operation","preparation_duration_s":35.0},"confidence":0.92,"alert_tier":"high","cloud_review_available":false,"transition_source":"ai_detected"}
{"event_id":"e006","session_id":"s-abc-123","ts":50.0,"event_type":"audio_observation","phase":"operation","signal":"pitch_accuracy","value":{"measure":1,"accuracy":0.91,"notes_correct":10,"notes_total":11},"confidence":0.87,"alert_tier":"high","cloud_review_available":false}
{"event_id":"e007","session_id":"s-abc-123","ts":65.0,"event_type":"streak","phase":"operation","signal":"correct_notes_streak_8","value":{"streak_length":8,"start_measure":2,"end_measure":3},"confidence":0.83,"alert_tier":"medium","cloud_review_available":false}
{"event_id":"e008","session_id":"s-abc-123","ts":80.0,"event_type":"posture_observation","phase":"operation","signal":"bow_hold_check","value":{"correct":false,"details":"小指僵硬"},"confidence":0.42,"alert_tier":"low","cloud_review_available":true,"value":{"edge_assessment":"possible_issue","details":"小指可能僵硬,但角度遮挡,不确定"}}
{"event_id":"e009","session_id":"s-abc-123","ts":100.0,"event_type":"audio_observation","phase":"operation","signal":"tone_quality","value":{"quality":"good","bow_pressure":"appropriate","bow_speed":"stable"},"confidence":0.79,"alert_tier":"medium","cloud_review_available":false}
{"event_id":"e010","session_id":"s-abc-123","ts":120.0,"event_type":"improvement","phase":"operation","signal":"pitch_improved","value":{"previous_accuracy":0.82,"current_accuracy":0.91,"comparison_session":"s-prev-456"},"confidence":0.80,"alert_tier":"medium","cloud_review_available":false}
{"event_id":"e011","session_id":"s-abc-123","ts":140.0,"event_type":"phase_transition","phase":"cleanup","signal":"operation_to_cleanup","value":{"from":"operation","to":"cleanup","operation_duration_s":105.0},"confidence":0.90,"alert_tier":"high","cloud_review_available":false,"transition_source":"user_manual"}
{"event_id":"e012","session_id":"s-abc-123","ts":155.0,"event_type":"technique_observation","phase":"cleanup","signal":"self_assessment","value":{"student_rating":4,"student_comment":"感觉音准比昨天好了"},"confidence":1.0,"alert_tier":"high","cloud_review_available":false}
{"event_id":"e013","session_id":"s-abc-123","ts":180.0,"event_type":"session_end","phase":"cleanup","signal":"session_complete","value":{"total_duration_s":180,"phases":{"preparation":35,"operation":105,"cleanup":40}},"confidence":1.0,"alert_tier":"high","cloud_review_available":false}超时示例(preparation 阶段过长)
如果学生在 preparation 阶段停留过久(如超过 90s),系统会产生 phase_timeout 事件:
{"event_id":"e-timeout-1","session_id":"s-xyz-789","ts":90.0,"event_type":"phase_timeout","phase":"preparation","signal":"preparation_timeout","value":{"elapsed_s":90,"threshold_s":60,"user_response":"continue"},"confidence":1.0,"alert_tier":"medium","cloud_review_available":false,"transition_source":"timeout"}系统询问学生是否继续准备或进入演奏,学生的选择记录在 user_response 中。
考官报告示例(Examiner Report)
云端处理完成后返回的报告结构:
{
"report_id": "r-001",
"session_id": "s-abc-123",
"generated_at": "2025-01-15T10:05:30Z",
"processing_time_s": 28,
"rubric_version": "v1.2",
// 边端即时回顾(Recap)— 练习结束时立即生成,无需网络
"recap_summary": {
"generated_by": "edge",
"tone": "coach",
"highlights": [
"音准比上次进步了 9%",
"连续 8 个音符准确无误",
"准备阶段姿态一次到位"
],
"phase_summary": {
"preparation": {"positive": 2, "uncertain": 0, "issue": 0},
"operation": {"positive": 3, "uncertain": 1, "issue": 0},
"cleanup": {"positive": 1, "uncertain": 0, "issue": 0}
}
},
// 云端考官报告 — 上传后 20-60s 生成
"examiner_assessment": {
"overall_score": 78,
"dimension_scores": {
"intonation": 82,
"rhythm": 85,
"tone_quality": 75,
"posture": 70
},
"evidence_chain": [
{
"dimension": "intonation",
"score": 82,
"evidence_events": ["e006", "e010"],
"narrative": "第一小节音准 91%,相比上次提升 9 个百分点,进步明显。"
},
{
"dimension": "posture",
"score": 70,
"evidence_events": ["e003", "e008"],
"narrative": "准备阶段持弓正确;演奏中段有一次疑似小指僵硬。",
"low_confidence_items": [
{
"event_id": "e008",
"edge_confidence": 0.42,
"cloud_review_available": true,
"cloud_assessment": "经视频帧分析,确认小指在第 80s 时有短暂僵硬,但 3s 后自行恢复。",
"note": "教练标记为不确定,考官经复核确认为轻微问题"
}
]
}
],
"growth_indicators": [
{
"metric": "pitch_accuracy",
"trend": "improving",
"sessions_compared": 5,
"detail": "近 5 次练习音准从 72% 提升至 91%"
}
],
"recommendations": [
"继续保持音准练习节奏",
"注意演奏中段右手小指放松",
"下次可尝试稍快速度(♩=72 → ♩=80)"
]
}
}低置信度项在报告中的呈现
当边端标记了 cloud_review_available: true 的事件,考官报告会:
- 在
evidence_chain中显式列出该事件 - 标注
edge_confidence原始值 - 给出云端复核后的结论(
cloud_assessment) - 添加
note: "教练标记为不确定,考官经复核确认/排除"说明
这让学生和教师都能看到:哪些判断是确定的,哪些经过了二次确认,体现评估的诚实与透明。
数据量估算
单次练习(3 分钟)
- 事件数:10-20 条
- 单条事件大小:200-500 bytes
- 单次 session 文件大小:2-10 KB
日常使用(每天练习 30 分钟)
- 约 100-200 条事件
- 文件大小:20-100 KB
- 月累计:600 KB - 3 MB
存储策略
- 本地保留最近 30 天原始 events.jsonl
- 云端永久存储(用于长期成长分析)
- 超过 30 天的本地文件压缩归档或删除(云端已有备份)
第四轮重构新增字段(v0.5)
Events schema 新增字段:
| 字段 | 类型 | 说明 |
|---|---|---|
journey_id | string (uuid) | 跨会话技能旅程 ID,同一技能的多次练习共享 |
perception_level | enum: L0 / L1 / L2 | 产出该事件的感知层级 |
primitive | enum: presence / sequence / timing / quality / safety | Rubric Primitive 信号原语类型 |
Report schema 新增字段:
| 字段 | 类型 | 说明 |
|---|---|---|
journey_context | object | 技能旅程上下文(journey_id + 历史 session 摘要) |
summary_card | object | Practice Summary Card(练-评-教三角轻量闭合卡片,含 QR 码 URL) |
Event Semantic Triple(三元组事件拆分):
每个检测事件拆为三步语义:check_requested(Planner 发出检测请求)→ observation(感知层产出原始观察)→ assertion(Confidence Router 产出最终断言)。三步共享同一 correlation_id,支持审计日志回溯。
版本演进
| 版本 | 变更 |
|---|---|
| v0.1 | 初始 schema,基础事件类型 |
| v0.2 | 增加 confidence 和 alert_tier 字段 |
| v0.3 | 增加正向事件类型(achievement/improvement/streak) |
| v0.4 | 增加 session_focus、phase_timeout 事件;transition_source 字段;诚实哲学标记 |
| v0.5 | 增加 journey_id、perception_level、primitive 字段;Event Semantic Triple;report 新增 journey_context + summary_card |