8.0 KiB
事件契约 v0.1(已冻结)
冻结日期:2026-08-03 范围:推理侧(silver_pose 及后续推理内核)→ YoVision 平台侧的唯一数据契约。 依据:《07-事件契约比对-silver_pose.md》
| 文件 | 用途 |
|---|---|
event-v0.1.schema.json |
JSON Schema(draft 2020-12),规范本体 |
event-v0.1.example-current.json |
现状基线:silver_pose 今天就能产出的形态,P1 缺口全为 null |
event-v0.1.example-target.json |
目标形态:P1 补齐后,含 keypoint 序列、clip、自动 outcome |
event-v0.1.example-radar.json |
非成像模态:卫生间纯雷达跌倒,snapshot_uris 为空数组,门磁佐证 |
三个 example 都是测试夹具,两侧仓库的单元测试都应对它们做校验。
修订记录
| 日期 | 变更 | 理由 |
|---|---|---|
| 2026-08-03 | 初次冻结 | — |
| 2026-08-03 | 新增 sensors[] 与 observation.signal_seq_uri;evidence.snapshot_uris 的 minItems 由 1 放宽为 0 |
感知系统后期接入毫米波雷达等非成像设备,原 schema 把「感知=摄像头」焊死在契约里,纯雷达事件无法表达。本次为原地修订,理由是当时零实现;首个生产者上线后,§1.2 的版本递增规则绝对生效,不再允许原地改 |
1. 冻结意味着什么
- 本目录内容在两个仓库各存一份,内容必须逐字节相同。 任何一侧修改,同一次提交内同步另一侧
schema_version为"0.1"。破坏性变更必须递增版本并保留旧版本文件,不得原地修改已冻结的 schema- 「破坏性」的定义:删除字段、收紧取值域、把可空改为必填、改变字段语义。新增可空字段与新增
kind取值不算破坏性 - 实验性字段一律放
ext,不占用顶层命名空间,也不触发版本升级
2. 三条设计决定(及其代价)
① 所有顶层键必须存在,可为 null,但不得省略。
「省略」与「显式 null」在下游无法区分,这是这类系统最常见的排查陷阱——identity_status 就是被这个问题逼出来的字段。代价是生产者要写一串 null,但生产者只有一个 mapper 函数。
② 根对象 additionalProperties: false。
未登记字段一律拒绝。防的是推理侧偷偷加字段、平台侧偷偷依赖,几个月后没人知道它是不是契约的一部分。逃生口是 ext。
③ confidence 允许为 null,且现阶段必须为 null。
几何证据 + 时间窗状态机的判定链路没有天然置信度。伪造一个常量会污染所有下游阈值调优。等换成分类模型再填。
3. kind 注册表
新增 kind 不需要升 schema 版本,但必须在此表登记。
| kind | 含义 | 默认 severity | 产出方 | 状态 |
|---|---|---|---|---|
fall |
人员摔倒确认 | high |
silver_pose FSM 进入 CONFIRMED | ✅ 已上线 |
4. silver_pose → 契约 映射表
mapper 的完整实现规格。左列出处见《07》§1。
| 契约字段 | 来源 | 变换 |
|---|---|---|
schema_version |
— | 常量 "0.1" |
id |
— | 平台侧生成 ULID,加 evt_ 前缀 |
source_event_id |
event_id |
原样。截图文件即按它命名,是回溯本地证据的唯一钥匙 |
tenant_id / site_id / device_id |
source_id |
经平台设备映射表解析。事件中不得出现 RTSP 地址或凭据 |
sensors |
source_id |
单摄像头事件为 [{device_id, "video", "primary"}];多传感器融合由推理侧列全 |
kind |
event_id 的 FALL- 前缀 |
提升为显式字段 "fall" |
severity |
— | 按 kind 查注册表 → "high" |
confidence |
— | 恒 null(见 §2③) |
detected_at |
confirmed_at_utc |
原样 |
occurred_at |
换算 | confirmed_at_utc - latency_seconds。误差为单帧级 |
latency_seconds |
latency_seconds |
原样 |
config_version |
config_version |
原样 |
rule |
— | null(推理侧无规则引擎),平台侧按 kind 反查补全 |
subject.class |
— | 常量 "person" |
subject.track_id |
track_id |
原样 |
subject.attributes |
— | {} |
subject.anon_id / identity |
— | null |
subject.identity_status |
— | 常量 "not_enabled" |
observation.* |
— | 全 null(P1 补 keypoint_seq_uri;非成像模态补 signal_seq_uri) |
evidence.snapshot_uris |
screenshot |
相对路径 → URI,单元素数组。非成像模态为 [] |
evidence.clip_uri / clip_range |
— | null(P1 补) |
dedup_key / aggregated_into |
— | null,由平台侧构造 |
outcome |
state |
CONFIRMED → "unknown";RECOVERING 稳定后回传 "subject_recovered" + outcome_source: "auto" |
diagnostics.fsm_state |
state |
原样,仅供排查 |
diagnostics.*_monotonic |
同名字段 | 原样。单调时钟跨进程无意义,不得用于任何时间计算 |
ext |
— | {} |
5. schema 拦不住的四件事(必须写代码校验)
已实测(REJECT = schema 能拦,ACCEPT = 拦不住):
schema 能拦:省略必填键 / 非法 ULID / 顶层多余字段 / outcome 越界 / config_version 空串 / 负 latency / kind 大写。snapshot_uris 空数组对非成像事件是合法值,见修订记录、雷达示例和 schema 的 minItems: 0。
schema 拦不住,需在 mapper 与平台入口各加一道断言:
| # | 校验 | 理由 |
|---|---|---|
| 1 | detected_at >= occurred_at |
跨字段约束,JSON Schema 表达不了。倒序时间会让 SLA 统计出负数 |
| 2 | |detected_at - occurred_at - latency_seconds| < 0.1s |
三者冗余,必须自洽。不一致说明 mapper 用错了时钟 |
| 3 | confidence is null(现阶段) |
schema 只能约束 0–1 区间,拦不住伪造值 |
| 4 | 证据文件名不含 IP、端口、凭据、客户名 | 只允许 事件ID + 日期目录。文件名会出现在日志、URL 与工单里 —— 这是 silver_pose alerts.py 头注释的既有纪律,此处升格为强制校验 |
| 5 | sensors 中恰有一个 role == "primary",且其 device_id == 顶层 device_id |
数组约束,schema 表达不了。两个 primary 会让归因和去重都失去基准 |
| 6 | 若任一 sensors[].modality == "video",则该 device_id 所在区域 privacy_flag 必须为 false |
隐私区域硬约束的运行时兜底。设备录入时已拦一道,此处是第二道 |
5.1 隐私区域约束的正确表述
原表述「隐私区域拒绝 device_type = camera」应改为按模态判定:
privacy_flag = true 的区域:
允许 modality ∈ {radar, contact, button, wearable}
拒绝 modality ∈ {video}
差别不是措辞。按设备类型判定,每接一种新设备就要重新问一次"它算不算摄像头";按模态判定,规则一次写死,新设备只需声明自己的 modality。《01》RQ-S1-05、《02》NFR-CMP-05、《03》§3.2 与落地清单四处的表述需同步。
6. 本地校验命令
pip install 'jsonschema>=4.18'
python3 - <<'PY'
import json
from jsonschema import Draft202012Validator, FormatChecker
s = json.load(open('event-v0.1.schema.json'))
Draft202012Validator.check_schema(s)
v = Draft202012Validator(s, format_checker=FormatChecker())
for f in ('event-v0.1.example-current.json', 'event-v0.1.example-target.json'):
errs = list(v.iter_errors(json.load(open(f))))
print(f, 'OK' if not errs else [e.message for e in errs])
PY
已于 2026-08-03 执行通过:schema 合法,两个 example 均校验通过。
7. 未决项
| # | 事项 | 决策人 |
|---|---|---|
| 1 | source_id → device_id 映射表放在哪(平台 DB 表 / 边缘配置文件) |
待定 |
| 2 | 投递通道:HTTP POST 起步还是直接上 ZeroMQ | 待定,建议先 HTTP |
| 3 | outcome 回写的反向通道协议(平台 → 推理侧) |
待定 |
| 4 | silver_pose 侧本文件的落地方式(需按其 T-xxx 任务流程走) | 待定 |