docs(design): unify Bell roster and escalation targets
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-04 14:14:06 +08:00
parent e96cc2fbb5
commit 8115f584dd
8 changed files with 208 additions and 15 deletions
+3
View File
@@ -54,10 +54,13 @@
- 预警必须有 ack;未 ack 自动升级,进程重启后能续跑。
- 升级链、超时、联系人和时段可按租户/站点配置。
- 联系人与值班排班必须统一设计并共享人员、值班组和已验证通知通道主数据,但分对象、分版本管理:联系人不承载轮换字段,排班不复制手机号;升级步骤通过类型化目标引用指定人员、值班组或排班计划,不写死号码。
- 值班排班至少覆盖站点时区、周轮换、生效日期、临时替班、空档/重叠冲突检查、当前与未来值班人预览、版本发布和审计。每次投递创建时解析当时生效的排班版本,并固化实际收件人、通道与解析版本快照;后续修改不得改写历史投递事实。
- 至少两条独立投递路径,其中一条可绕过互联网。
- 区分已发出、已送达、已看到;没有回执不能当成功。
- Alert 与 Event 保持可导航的多对多关系;被规则抑制且没有创建 Alert 的 Event 不伪装为已投递。
- 班次交接覆盖未 ack、处置中与升级中的 Alert;接班确认留痕,交接过程不暂停或重置升级链。
- 交接班只显式转移进行中 Alert 的处置责任,不静默修改未来排班;未来班次替换通过排班临时替班并发布新版本完成。
- 静默必须限时且自动恢复,单次不超过 4 小时,无永久静默。
- 业务预警与运维告警使用不同通道和值班配置。
+3
View File
@@ -89,12 +89,15 @@ Sense ── 视频流/触发信号 ──> Brain
- 高风险设备操作在本地事务内同时写期望态与审计 outbox;异步 relay 可重试、幂等投递到 Bell。T-004 只验证交互归属,不定义或实现接口契约。
- Brain 投递失败落本地队列重试,不阻塞实时推理主链路。
- Alert 先落库再投递,进程重启恢复未完成升级链。
- 值班排班发布前必须按 Site 时区校验班次空档、重叠、联系人停用和通道验证;排班以新版本和未来生效时间发布,不原地改写历史。交接班是进行中 Alert 的显式责任转移事件,不替代排班版本变更。
- 事件证据技术默认保留 30 天并按生命周期删除;客户/法务在 M3 生产上线前确认法规适用性和最终期限,技术默认值不能覆盖其结论。
## 8. 数据与契约
- Bell 核心实体:Tenant → Site → Area(含 `capture_policy`)以及 Role/Binding/Quota/Audit;Sense 核心实体:Device(含 `modality + capabilities`)→ StreamBinding/Zone,以及带 `source_version`/`synced_at` 的 SiteQuota/AreaPolicyProjection。两个 schema 以稳定逻辑 ID 关联,不跨 schema 写入。
- 业务实体:Rule → Event → Alert → DeliveryAttempt/Ack;Event 与 Alert 不合并。
- Bell 通知域分为三个聚合:Contact/Team 保存身份、成员关系和已验证通道;OnCallSchedule/ScheduleVersion/ShiftException 保存时区、轮换与例外;EscalationPolicy/Step 通过 `person / team / on_call_schedule` 类型化 `target_ref` 引用目标。三者共享逻辑 ID,不复制手机号、班次或轮换字段。
- 每个 DeliveryAttempt 创建时解析当时生效的排班版本,并保存实际收件人、通道、`schedule_version` 和解析时间快照;之后联系人或排班修改不得回写既有投递事实。
- 一个事件可触发多次预警与投递,一次预警也可聚合多个事件。
- 事件 v0.1 以 `raw/contracts/event-v0.1.schema.json` 与 `raw/contracts/README.md` 为准;未知顶层字段拒绝,只允许通过 `ext` 扩展。
- v0.1 还需代码校验时间自洽、`confidence` 当前为 null、证据文件名隐私、唯一 primary sensor 等跨字段约束。
+10 -2
View File
@@ -37,9 +37,9 @@
## US-005 配置规则与升级链
- 角色:租户管理员/站点管理员。
- 目标:按站点和设备覆盖场景模板,配置区域、时段、持续时间和联系人升级链。
- 目标:按站点和设备覆盖场景模板,配置区域、时段、持续时间和升级链;升级步骤引用指定人员、值班组或值班排班,不直接写死手机号。
- 价值:同一通用底座适配不同场景。
- 验收:继承来源清楚、变更可试运行;回滚先展示版本差异与影响,并创建新的不可变版本,不改写历史;静默不超过 4 小时且自动恢复。
- 验收:继承来源清楚、变更可试运行;升级目标可预览当前解析人和通道,投递后保留解析快照;回滚先展示版本差异与影响,并创建新的不可变版本,不改写历史;静默不超过 4 小时且自动恢复。
- 关联:RQ-C-11~RQ-C-15、RQ-C-23~RQ-C-28,IX-010~IX-012。
## US-006 事件最小权限查看
@@ -98,6 +98,14 @@
- 验收:报表展示规则版本、样本时间窗、正样本/漏检样本、通道天数、召回率和每路每天误报数;明确不提供跨场景统一准确率;导出保留筛选与口径说明。
- 关联:RQ-C-22、`02-requirements` §8,IX-022。
## US-013 管理联系人和值班排班
- 角色:租户管理员/站点管理员。
- 目标:使用同一套人员、值班组和通知通道主数据配置可发布的值班排班,并让升级策略引用排班目标。
- 价值:避免先把联系人和号码写死进升级链、后续再迁移到时段轮换;同时避免把联系人、班次和轮换混成一个难维护对象。
- 验收:联系人维护身份、角色、值班组和已验证通道,不出现排班轮换字段;排班维护时区、班次、周轮换、生效日期、临时替班和版本;发布前发现空档/重叠并阻止发布,可预览当前及未来值班人;投递固化收件人、通道与排班版本快照;交接进行中 Alert 不静默修改未来排班。
- 关联:RQ-C-24~RQ-C-27、RQ-C-33,IX-012、IX-023。
## 追溯规则
新增 P0 UI 任务必须引用至少一个 US 和一个 IX;若没有 UI,任务文件明确写“不适用”。需求变化先更新用户故事和交互清单,再改页面。
+2 -1
View File
@@ -15,7 +15,7 @@
| IX-009 | 误报反馈 | 确认动作、可选原因、提交成功反馈;只改变 outcome,不改原始事件 | US-004 | M3 |
| IX-010 | 规则编辑 | 显示租户/站点/设备继承来源,支持区域/警戒线/时段/持续时间 | US-005 | M3/M4 |
| IX-011 | 规则试运行与回滚 | 明确“未正式生效”,展示命中样本与影响范围,支持取消;回滚前展示目标版本差异与影响,确认后创建并发布等价的新版本,历史版本保持不可变;版本冲突时阻止覆盖并提供刷新/另存路径 | US-005 | M4 |
| IX-012 | 升级链/静默 | 联系人顺序、超时、双通道;静默最长 4h、显示自动恢复时间、无永久选项 | US-005 | M3/M4 |
| IX-012 | 升级链/静默 | 升级步骤通过类型化目标引用指定人员、值班组或值班排班,不写死号码;显示目标解析预览、联系人顺序、超时与双通道;每次投递固化实际收件人、通道和排班版本快照;静默最长 4h、显示自动恢复时间、无永久选项 | US-005、US-013 | M3/M4 |
| IX-013 | 权限与隐私 | 越权统一处理;无授权租户不展示人脸入口;不泄露流 URL/凭据 | US-006 | M3/M4 |
| IX-014 | 设备模态与能力 | 一级入口为“设备”;列表可按 `modality` 筛选,添加时先选模态,详情按 `capabilities` 渐进展示;能力同时显示厂商/型号/固件/认证、探测来源与时间、探测值和生效值差异;视频设备显示画面/媒体/检测区域,未交付的非视频适配器显示 `adapter_not_ready`,不用虚构遥测或禁用的视频页签占位 | US-008 | M2/M6 |
| IX-015 | 停用与收敛 | 明确区分暂停推理订阅、停用设备接入和仅踢当前会话;暂停推理必须说明“解除推理侧订阅”,并在结果中显示推理订阅、其他 reader 数与上游按需拉流状态;操作前展示对观看、录制与证据回捞的影响并二次确认;期望态立即改变、实际态经对账收敛,部分失败可逐项重试;重复请求不产生重复副作用,但每次请求均留审计结果 | US-001、US-002 | M2 |
@@ -26,6 +26,7 @@
| IX-020 | 管理上下文、RBAC 与审计归属 | 全局头部显示租户、站点、账号和角色;只读角色隐藏写操作,深链越权统一拒绝且不泄露资源是否存在;Tenant/Site/Area/RBAC/配额/全局审计由 Bell 管理,Sense 只呈现消费上下文和设备操作入口,携带 tenant/site/device/return_to 深链到 Bell 审计;Bell 移动端底部导航不超过 5 项,并通过可发现的“管理”入口访问 Site/Area 与审计 | US-006、US-009、US-010 | M2/M4 |
| IX-021 | 值班交接 | 清单覆盖未 ack、处置中和升级中的 Alert,显示交班人、接班人、备注及下一次升级时间;接班确认写审计,交接期间不暂停或重置升级链,未确认时不转移责任 | US-011 | M3 |
| IX-022 | 规则验收报表 | 按规则版本与冻结样本窗口展示召回率、每路每天误报数和计算样本量;支持保留口径的导出;不提供跨场景统一准确率 | US-012 | M4 |
| IX-023 | 联系人与值班排班 | 在“升级链”内部以升级策略、值班与排班、联系人和通道三个二级模块统一设计;联系人与排班共享人员/值班组/已验证通道主数据但分对象维护;排班覆盖站点时区、周轮换、生效日期、临时替班、空档/重叠冲突、当前/未来值班人预览和版本发布审计;交接班只转移进行中 Alert,不暗改未来排班 | US-005、US-011、US-013 | M3/M4 |
## 全局状态
+165 -8
View File
@@ -206,11 +206,35 @@
.version-diff { display: grid; gap: 8px; }
.version-diff article { padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface-2); }
.version-diff article strong { display: block; }
.chain-subnav { margin-bottom: 12px; padding: 4px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; border: 1px solid var(--line); border-radius: 11px; background: var(--surface); }
.chain-subnav button { min-height: 44px; border: 0; border-radius: 8px; background: transparent; color: var(--muted); font-weight: 750; }
.chain-subnav button.active { background: var(--primary-soft); color: var(--text); box-shadow: inset 0 0 0 1px rgba(139,124,246,.36); }
.chain-panel { display: none; }
.chain-panel.active { display: block; }
.chain-grid { grid-template-columns: minmax(0, 1.4fr) minmax(300px, .8fr); }
.chain-row { padding: 13px 14px; display: grid; grid-template-columns: 40px 1fr auto; gap: 11px; align-items: center; border-bottom: 1px solid var(--line); }
.chain-row:last-child { border-bottom: 0; }
.chain-num { width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; background: var(--primary-soft); color: #cfc8ff; font-weight: 800; }
.chain-row small { color: var(--muted); }
.target-ref { display: inline-block; margin-top: 3px; color: #b8e5ff; font: 12px/1.4 ui-monospace, SFMono-Regular, Consolas, monospace; }
.resolve-card { padding: 14px; }
.resolve-card h2 { margin: 0 0 4px; font-size: 15px; }
.resolve-card > p { margin: 0 0 12px; color: var(--muted); }
.resolve-list { display: grid; gap: 8px; }
.resolve-item { padding: 10px 11px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface-2); }
.resolve-item span, .resolve-item small { display: block; color: var(--muted); }
.resolve-item strong { display: block; margin: 2px 0; }
.roster-week { padding: 12px; display: grid; grid-template-columns: repeat(7, minmax(125px, 1fr)); gap: 8px; overflow-x: auto; }
.roster-day { min-width: 125px; padding: 10px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface-2); }
.roster-day.current { border-color: var(--primary); background: var(--primary-soft); }
.roster-day > strong { display: block; margin-bottom: 8px; }
.shift-slot { padding: 8px 0; border-top: 1px solid var(--line); }
.shift-slot:first-of-type { border-top: 0; }
.shift-slot span, .shift-slot small { display: block; color: var(--muted); font-size: 12px; }
.shift-slot strong { display: block; margin: 2px 0; font-size: 13px; }
.object-note { padding: 11px 14px; border-top: 1px solid var(--line); color: var(--muted); }
.contact-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-bottom: 12px; }
.contact-summary .stat { min-height: 96px; }
.chart-grid { grid-template-columns: 1.35fr .65fr; }
.chart { padding: 16px; }
.chart h2 { margin: 0; font-size: 15px; }
@@ -233,7 +257,7 @@
.skeleton i:nth-child(2) { width: 72%; }
@keyframes shimmer { to { background-position: -200% 0; } }
dialog { width: min(610px, calc(100% - 28px)); max-height: 88dvh; padding: 0; border: 1px solid var(--line); border-radius: 16px; background: var(--surface); color: var(--text); box-shadow: 0 24px 80px rgba(0,0,0,.5); }
#ruleDialog, #eventDialog, #handoverDialog, #rollbackDialog { width: min(760px, calc(100% - 28px)); }
#ruleDialog, #eventDialog, #handoverDialog, #rollbackDialog, #escalationDialog, #scheduleDialog, #substituteDialog, #contactDialog { width: min(760px, calc(100% - 28px)); }
dialog::backdrop { background: rgba(4,7,15,.68); backdrop-filter: blur(4px); }
.dialog-head, .dialog-foot { padding: 14px 18px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.dialog-head h2 { margin: 0; font-size: 17px; }
@@ -277,6 +301,7 @@
.disposition { border-left: 0; border-top: 1px solid var(--line); }
.stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.site-grid { grid-template-columns: 1fr; }
.roster-week { grid-template-columns: repeat(2, minmax(145px, 1fr)); }
.rule-grid { grid-template-columns: 1fr 1fr; }
.fact-grid { grid-template-columns: repeat(2, 1fr); }
.pager { align-items: flex-start; flex-direction: column; }
@@ -287,6 +312,9 @@
@media (max-width: 560px) {
.page-head { display: grid; }
.stats, .rule-grid { grid-template-columns: 1fr; }
.chain-subnav { grid-template-columns: 1fr; }
.contact-summary { grid-template-columns: 1fr; }
.roster-week { grid-template-columns: 1fr; }
.form-grid { grid-template-columns: 1fr; }
.form-field.full { grid-column: auto; }
.video-stage { margin-inline: 8px; }
@@ -414,12 +442,37 @@
</section>
<section class="view" data-page="chains">
<div class="page-head"><div><h1>升级链</h1><p>生产前至少两条独立路径;无回执不能当作成功。</p></div><div class="page-actions"><button class="btn btn-primary">编辑升级链</button></div></div>
<div class="grid chain-grid"><article class="card"><div class="card-head"><h2>校园高危事件 · 默认链</h2><span class="badge success">已发布 v4</span></div>
<div class="chain-row"><div class="chain-num">1</div><div><strong>值班室 Web + 本地声光</strong><small>立即 · 站点局域网内</small></div><span class="badge success">主路径</span></div>
<div class="chain-row"><div class="chain-num">2</div><div><strong>当班值班员短信</strong><small>立即 · 送达后等待 2 分钟</small></div><span class="badge info">独立路径</span></div>
<div class="chain-row"><div class="chain-num">3</div><div><strong>校级负责人语音</strong><small>未 ack 时升级 · 等待 3 分钟</small></div><span class="badge warning">兜底</span></div>
</article><article class="card"><div class="card-head"><h2>静默规则</h2><span class="badge info">无永久选项</span></div><div class="card-body"><label for="muteDuration" class="muted">维护窗口</label><select id="muteDuration" class="select" style="margin-top:6px"><option>30 分钟</option><option>1 小时</option><option>2 小时</option><option>4 小时(上限)</option></select><p class="muted">静默仅影响选定规则和点位,到时自动恢复;S1 事件不可静默。</p><button class="btn">创建维护窗口</button></div></article></div>
<div class="page-head"><div><h1>升级链</h1><p>联系人、值班排班和升级策略统一设计、分对象管理;策略引用动态目标,不写死手机号。</p></div><div class="page-actions"><span class="badge success">当前排班覆盖完整</span></div></div>
<div class="chain-subnav" aria-label="升级链二级模块"><button class="active" data-chain-section="strategy" aria-pressed="true">升级策略</button><button data-chain-section="roster" aria-pressed="false">值班与排班</button><button data-chain-section="contacts" aria-pressed="false">联系人和通道</button></div>
<section class="chain-panel active" data-chain-panel="strategy">
<div class="grid chain-grid"><article class="card"><div class="card-head"><div><h2>校园高危事件 · 默认链</h2><span class="muted">每一步引用类型化目标,解析后才创建投递</span></div><span class="badge success">已发布 v4</span></div>
<div class="chain-row"><div class="chain-num">1</div><div><strong>值班室 Web + 本地声光</strong><small>立即 · 站点局域网内</small><span class="target-ref">team:DUTY-ROOM</span></div><span class="badge success">固定值班组</span></div>
<div class="chain-row"><div class="chain-num">2</div><div><strong>当前排班值班员 · 短信</strong><small>立即 · 送达后等待 2 分钟</small><span class="target-ref">on_call_schedule:SCH-CAMPUS-SECURITY@v12</span></div><span class="badge info">动态排班</span></div>
<div class="chain-row"><div class="chain-num">3</div><div><strong>校级负责人 · 语音</strong><small>未 ack 时升级 · 等待 3 分钟</small><span class="target-ref">person:USR-SCHOOL-LEAD</span></div><span class="badge warning">指定人员</span></div>
<div class="object-note">升级策略只保存目标引用和通道偏好,不复制联系人号码或轮换字段。</div>
</article><article class="card resolve-card"><div class="card-head" style="padding:0 0 11px;margin-bottom:12px"><div><h2>目标解析预览</h2><span class="muted">事件时间 2026-08-04 23:14 · Asia/Shanghai</span></div><button class="btn" id="editEscalation">编辑策略</button></div><div class="resolve-list"><div class="resolve-item"><span>排班目标</span><strong>校园安保值班表 · v12</strong><small class="mono">SCH-CAMPUS-SECURITY</small></div><div class="resolve-item"><span>当前解析结果</span><strong id="resolvedPerson">王值班员 · 晚班</strong><small>短信已验证 · 语音已验证</small></div><div class="resolve-item"><span>投递快照</span><strong>23:14:13 已固化</strong><small>收件人、通道、排班 v12;后续编辑不改写本次事实</small></div></div></article></div>
<div class="grid chain-grid" style="margin-top:12px"><article class="card"><div class="card-head"><h2>解析与版本规则</h2><span class="badge info">不可变历史</span></div><div class="card-body"><p class="muted">每次投递创建时按站点时区解析当时生效的排班版本,并保存实际收件人、通道与版本快照。未来排班版本只影响其生效时间后的新投递。</p></div></article><article class="card"><div class="card-head"><h2>静默规则</h2><span class="badge info">无永久选项</span></div><div class="card-body"><label for="muteDuration" class="muted">维护窗口</label><select id="muteDuration" class="select" style="margin-top:6px"><option>30 分钟</option><option>1 小时</option><option>2 小时</option><option>4 小时(上限)</option></select><p class="muted">静默仅影响选定规则和点位,到时自动恢复;S1 事件不可静默。</p><button class="btn">创建维护窗口</button></div></article></div>
</section>
<section class="chain-panel" data-chain-panel="roster">
<div class="grid stats"><article class="card stat"><span>当前排班</span><strong id="scheduleVersion">v12</strong><span class="muted">已发布 · 08-01 生效</span></article><article class="card stat"><span>站点时区</span><strong style="font-size:18px">Asia/Shanghai</strong><span class="muted">跨午夜班次按站点时间</span></article><article class="card stat"><span>当前值班</span><strong style="font-size:18px">王值班员</strong><span class="muted">晚班 · 至明日 08:00</span></article><article class="card stat"><span>未来 14 天覆盖</span><strong>100%</strong><span class="muted">0 空档 · 0 重叠</span></article></div>
<article class="card"><div class="card-head"><div><h2>校园安保值班表</h2><span class="muted">周轮换 · 白班 08:00–20:00 / 晚班 20:00–08:00</span></div><div class="page-actions"><button class="btn" id="simulateRosterConflict">演示冲突检查</button><button class="btn" id="openSubstitute">临时替班</button><button class="btn btn-primary" id="openSchedulePublish">发布新版本</button></div></div><div class="inline-alert danger" id="rosterConflict" role="alert" hidden style="margin:12px 12px 0"></div><div class="roster-week">
<div class="roster-day current"><strong>今天 · 周二</strong><div class="shift-slot"><span>白班</span><strong>林值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong id="currentNightShift">王值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>周三</strong><div class="shift-slot"><span>白班</span><strong>赵值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong id="wednesdayNightShift">周值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>周四</strong><div class="shift-slot"><span>白班</span><strong>林值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong>王值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>周五</strong><div class="shift-slot"><span>白班</span><strong>赵值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong>周值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>周六</strong><div class="shift-slot"><span>白班</span><strong>林值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong>王值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>周日</strong><div class="shift-slot"><span>白班</span><strong>赵值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong>周值班员</strong><small>20:00–08:00</small></div></div>
<div class="roster-day"><strong>下周一</strong><div class="shift-slot"><span>白班</span><strong>林值班员</strong><small>08:00–20:00</small></div><div class="shift-slot"><span>晚班</span><strong>王值班员</strong><small>20:00–08:00</small></div></div>
</div><div class="object-note"><strong>边界:</strong>临时替班进入新排班草稿并经发布后影响未来班次;值班交接只转移进行中 Alert 的责任,不会暗改这里的轮换。</div></article>
<article class="card" style="margin-top:12px"><div class="card-head"><h2>版本与例外</h2><span class="badge info">全量审计</span></div><div class="table-wrap"><table><thead><tr><th>版本</th><th>生效时间</th><th>变更</th><th>状态</th><th>发布人</th></tr></thead><tbody><tr><td>v12</td><td>2026-08-01 00:00</td><td>暑期周轮换</td><td><span class="badge success">当前生效</span></td><td>林工</td></tr><tr><td>v11</td><td>2026-07-01 00:00</td><td>赵值班员加入白班组</td><td><span class="badge">历史</span></td><td>周管理员</td></tr></tbody></table></div></article>
</section>
<section class="chain-panel" data-chain-panel="contacts">
<div class="grid contact-summary"><article class="card stat"><span>联系人</span><strong id="contactCount">6</strong><span class="muted">4 名值班员 · 2 名负责人</span></article><article class="card stat"><span>已验证通道</span><strong>11 / 12</strong><span class="muted">1 个语音号码待验证</span></article><article class="card stat"><span>被排班引用</span><strong>4</strong><span class="muted">删除前必须解除引用</span></article></div>
<article class="card"><div class="card-head"><div><h2>联系人和通知通道</h2><span class="muted">只维护身份、角色、值班组和通道;班次与轮换在排班模块维护</span></div><button class="btn btn-primary" id="openContact">新建联系人</button></div><div class="table-wrap"><table><thead><tr><th>联系人</th><th>角色</th><th>值班组</th><th>通知通道</th><th>排班引用</th><th>状态</th><th>操作</th></tr></thead><tbody><tr><td><strong>王值班员</strong><br><span class="mono muted">USR-DUTY-WANG</span></td><td>值班员</td><td>校园安保组</td><td>短信 ✓ · 语音 ✓ · Web ✓</td><td>SCH-CAMPUS-SECURITY</td><td><span class="badge success">可用</span></td><td><button class="btn" data-edit-contact="wang">编辑</button></td></tr><tr><td><strong>林值班员</strong><br><span class="mono muted">USR-DUTY-LIN</span></td><td>值班员</td><td>校园安保组</td><td>短信 ✓ · Web ✓</td><td>SCH-CAMPUS-SECURITY</td><td><span class="badge success">可用</span></td><td><button class="btn" data-edit-contact="lin">编辑</button></td></tr><tr><td><strong>校级负责人</strong><br><span class="mono muted">USR-SCHOOL-LEAD</span></td><td>站点负责人</td><td>校级负责人组</td><td>短信 ✓ · 语音待验证</td><td>升级策略 v4</td><td><span class="badge warning">部分可用</span></td><td><button class="btn" data-edit-contact="lead">编辑</button></td></tr></tbody></table></div><div class="object-note">联系人通道变更只影响后续新投递;已创建投递保留当时的收件人和通道快照。联系人被排班或策略引用时不能直接删除。</div></article>
</section>
</section>
<section class="view" data-page="reports">
@@ -471,9 +524,17 @@
<dialog id="eventDialog" aria-labelledby="eventDialogTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="eventDialogTitle">事件详情</h2><span class="muted" id="eventDialogSubtitle">不可变事实</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭事件详情">×</button></div><div class="dialog-body"><div class="fact-grid" style="margin:0 0 14px"><div class="fact"><span>事件编号</span><strong class="mono" id="eventDetailId">—</strong></div><div class="fact"><span>点位</span><strong id="eventDetailSite">—</strong></div><div class="fact"><span>规则</span><strong id="eventDetailRule">—</strong></div><div class="fact"><span>证据</span><strong id="eventDetailEvidence">—</strong></div></div><div class="relation-panel" style="margin:0"><h3>触发的预警</h3><div class="relation-list" id="eventAlerts"></div></div></div><div class="dialog-foot"><button class="btn" value="cancel">关闭</button></div></form></dialog>
<dialog id="handoverDialog" aria-labelledby="handoverTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="handoverTitle">值班交接</h2><span class="muted">白班 · 林值班员 → 晚班</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭交接班">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="handoverTo">接班人</label><select class="select" id="handoverTo"><option value="">请选择并确认本人已在场</option><option>王值班员 · 晚班</option><option>赵值班员 · 晚班</option></select></div><div class="form-field"><label for="handoverAt">交接时间</label><input class="field" id="handoverAt" value="2026-08-04 20:00" readonly></div><div class="form-field full"><label>待交接 Alert</label><div class="relation-list"><div class="relation-item"><strong>ALT-1842 · 未 ack</strong><span>下一次升级 01:42</span></div><div class="relation-item"><strong>ALT-1841 · 处置中</strong><span>王值班员现场核查</span></div><div class="relation-item"><strong>ALT-1839 · 升级中</strong><span>短信重试 2/3</span></div></div></div><div class="form-field full"><label for="handoverNote">交接备注</label><textarea class="textarea" id="handoverNote" placeholder="记录现场状态、已采取动作和待跟进事项"></textarea></div></div><p class="metric-note">交接期间升级链继续计时,不暂停、不重置。接班人确认前,当前责任关系保持不变。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="confirmHandover">接班人确认并留痕</button></div></form></dialog>
<dialog id="handoverDialog" aria-labelledby="handoverTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="handoverTitle">值班交接</h2><span class="muted">白班 · 林值班员 → 晚班</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭交接班">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="handoverTo">接班人</label><select class="select" id="handoverTo"><option value="">请选择并确认本人已在场</option><option>王值班员 · 晚班</option><option>赵值班员 · 晚班</option></select></div><div class="form-field"><label for="handoverAt">交接时间</label><input class="field" id="handoverAt" value="2026-08-04 20:00" readonly></div><div class="form-field full"><label>待交接 Alert</label><div class="relation-list"><div class="relation-item"><strong>ALT-1842 · 未 ack</strong><span>下一次升级 01:42</span></div><div class="relation-item"><strong>ALT-1841 · 处置中</strong><span>王值班员现场核查</span></div><div class="relation-item"><strong>ALT-1839 · 升级中</strong><span>短信重试 2/3</span></div></div></div><div class="form-field full"><label for="handoverNote">交接备注</label><textarea class="textarea" id="handoverNote" placeholder="记录现场状态、已采取动作和待跟进事项"></textarea></div></div><p class="metric-note">交接期间升级链继续计时,不暂停、不重置;只转移这些进行中 Alert 的处置责任,不修改未来排班。未来班次变化请在“值班与排班”创建替班版本。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="confirmHandover">接班人确认并留痕</button></div></form></dialog>
<dialog id="rollbackDialog" aria-labelledby="rollbackTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="rollbackTitle">版本历史与回滚</h2><span class="muted">RULE-007 · 历史版本不可变</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭版本历史">×</button></div><div class="dialog-body"><div class="version-diff"><article><strong>当前 v7 · 2026-08-01 正式发布</strong><span class="muted">持续 3 秒 · 全天 · 4 个点位 · 区域 v7</span></article><article><strong>目标 v6 · 2026-07-18 历史版本</strong><span class="muted">持续 5 秒 · 夜间 22:00–06:00 · 3 个点位 · 区域 v6</span></article><article><strong>影响评估</strong><span class="muted">将减少 1 个点位并收窄时段;预计每日预警 -18%。确认后创建并发布 v8,使其配置等价于 v6,v6/v7 均保持不变。</span></article></div></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-danger" id="confirmRollback">创建并发布回滚版本 v8</button></div></form></dialog>
<dialog id="escalationDialog" aria-labelledby="escalationTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="escalationTitle">编辑升级步骤</h2><span class="muted">默认链 v5 草稿 · 不直接保存手机号</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭升级步骤编辑">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="escalationTargetType">目标类型</label><select class="select" id="escalationTargetType"><option value="schedule">值班排班</option><option value="team">固定值班组</option><option value="person">指定人员</option></select></div><div class="form-field"><label for="escalationTargetRef">目标</label><select class="select" id="escalationTargetRef"></select></div><div class="form-field"><label for="escalationChannel">首选通道</label><select class="select" id="escalationChannel"><option>短信 + 语音兜底</option><option>Web + 本地声光</option><option>语音 + 短信兜底</option></select></div><div class="form-field"><label for="escalationTimeout">等待 ack</label><select class="select" id="escalationTimeout"><option>2 分钟</option><option>3 分钟</option><option>5 分钟</option></select></div><div class="form-field full"><label>当前解析预览</label><div class="resolve-item" id="escalationPreview" aria-live="polite"></div></div></div><p class="workflow-note">保存的是类型化目标引用与通道偏好。每次投递创建时按站点时区解析生效排班,再固化收件人、通道和排班版本快照。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="saveEscalation">保存策略草稿</button></div></form></dialog>
<dialog id="scheduleDialog" aria-labelledby="scheduleTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="scheduleTitle">发布值班排班</h2><span class="muted">历史版本不可变</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭排班发布">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="scheduleName">排班计划</label><input class="field" id="scheduleName" value="校园安保值班表" readonly></div><div class="form-field"><label for="scheduleNextVersion">新版本</label><input class="field" id="scheduleNextVersion" value="v13" readonly></div><div class="form-field"><label for="scheduleTimezone">站点时区</label><select class="select" id="scheduleTimezone"><option>Asia/Shanghai</option></select></div><div class="form-field"><label for="scheduleEffective">生效时间</label><input class="field" id="scheduleEffective" type="datetime-local" value="2026-08-05T20:00"></div><div class="form-field full"><label for="scheduleReason">发布说明</label><textarea class="textarea" id="scheduleReason" placeholder="必填,例如:王值班员临时替班,覆盖周三晚班"></textarea></div></div><p class="metric-note">发布前重新检查空档、重叠、联系人停用和通道验证。新版本只影响生效时间后的投递,不回写 v12 或既有投递快照。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="confirmSchedulePublish">确认发布 v13</button></div></form></dialog>
<dialog id="substituteDialog" aria-labelledby="substituteTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="substituteTitle">创建临时替班</h2><span class="muted">保存到 v13 草稿,发布后生效</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭临时替班">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="substituteShift">被替换班次</label><select class="select" id="substituteShift"><option>周三晚班 · 周值班员 · 20:00–08:00</option><option>周四白班 · 林值班员 · 08:00–20:00</option></select></div><div class="form-field"><label for="substitutePerson">替班人</label><select class="select" id="substitutePerson"><option value="">请选择可用联系人</option><option>王值班员</option><option>赵值班员</option></select></div><div class="form-field full"><label for="substituteReason">替班原因</label><textarea class="textarea" id="substituteReason" placeholder="必填,写入排班版本审计"></textarea></div></div><p class="workflow-note">临时替班修改未来排班。若只是把正在处置的 Alert 交给下一班,请使用值班台“交接班”,不要改排班。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="saveSubstitute">保存到 v13 草稿</button></div></form></dialog>
<dialog id="contactDialog" aria-labelledby="contactTitle"><form method="dialog"><div class="dialog-head"><div><h2 id="contactTitle">新建联系人</h2><span class="muted">联系人不包含班次和轮换字段</span></div><button class="btn btn-quiet icon-btn" value="cancel" aria-label="关闭联系人编辑">×</button></div><div class="dialog-body"><div class="form-grid"><div class="form-field"><label for="contactName">姓名</label><input class="field" id="contactName" placeholder="联系人显示名"></div><div class="form-field"><label for="contactRole">角色</label><select class="select" id="contactRole"><option>值班员</option><option>站点负责人</option><option>租户管理员</option></select></div><div class="form-field"><label for="contactTeam">值班组</label><select class="select" id="contactTeam"><option>校园安保组</option><option>校级负责人组</option></select></div><div class="form-field"><label for="contactPhone">新通知号码</label><input class="field" id="contactPhone" type="tel" placeholder="输入后发送验证码;不回显旧值"></div><div class="form-field full"><label>启用通道</label><label class="radio-card"><input type="checkbox" id="contactSms" checked> 短信(需验证)</label><label class="radio-card"><input type="checkbox" id="contactVoice"> 语音(需验证)</label><label class="radio-card"><input type="checkbox" id="contactWeb" checked> Web 站内通知</label></div></div><p class="metric-note">联系人保存身份、角色、值班组与已验证通道。加入哪个班次、何时轮换由“值班与排班”模块引用联系人 ID 完成。</p></div><div class="dialog-foot"><button class="btn" value="cancel">取消</button><button type="button" class="btn btn-primary" id="saveContact">保存并发起通道验证</button></div></form></dialog>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script>
@@ -773,6 +834,101 @@
$('#handoverButton').addEventListener('click', () => $('#handoverDialog').showModal());
$('#confirmHandover').addEventListener('click', () => { if (!$('#handoverTo').value) { notify('请选择接班人并由本人确认。'); $('#handoverTo').focus(); return; } $('#handoverDialog').close(); notify('交接已由接班人确认并写入审计;3 条 Alert 的升级链继续运行。'); });
function switchChainSection(name) {
const target = ['strategy', 'roster', 'contacts'].includes(name) ? name : 'strategy';
$$('[data-chain-panel]').forEach(panel => panel.classList.toggle('active', panel.dataset.chainPanel === target));
$$('[data-chain-section]').forEach(button => {
const active = button.dataset.chainSection === target;
button.classList.toggle('active', active);
button.setAttribute('aria-pressed', String(active));
});
}
$$('[data-chain-section]').forEach(button => button.addEventListener('click', () => switchChainSection(button.dataset.chainSection)));
const escalationTargets = {
schedule: [
{value:'SCH-CAMPUS-SECURITY',label:'校园安保值班表 · v12',preview:'王值班员 · 晚班 · 短信/语音已验证'},
{value:'SCH-SCHOOL-LEAD',label:'校级负责人轮值表 · v6',preview:'陈负责人 · 当前轮值 · 语音已验证'}
],
team: [
{value:'DUTY-ROOM',label:'值班室固定组',preview:'4 人在线 · Web + 本地声光'},
{value:'SCHOOL-LEADS',label:'校级负责人组',preview:'3 名成员 · 顺序通知'}
],
person: [
{value:'USR-SCHOOL-LEAD',label:'校级负责人',preview:'指定人员 · 短信已验证 · 语音待验证'},
{value:'USR-DUTY-WANG',label:'王值班员',preview:'指定人员 · 短信/语音/Web 已验证'}
]
};
function renderEscalationTargets() {
const type = $('#escalationTargetType').value;
const select = $('#escalationTargetRef');
select.replaceChildren();
escalationTargets[type].forEach(target => {
const option = document.createElement('option');
option.value = target.value;
option.textContent = target.label;
select.append(option);
});
renderEscalationPreview();
}
function renderEscalationPreview() {
const type = $('#escalationTargetType').value;
const target = escalationTargets[type].find(item => item.value === $('#escalationTargetRef').value) || escalationTargets[type][0];
const typeLabel = type === 'schedule' ? '值班排班' : type === 'team' ? '固定值班组' : '指定人员';
$('#escalationPreview').textContent = `${typeLabel} · ${target.label} → ${target.preview}。实际投递时固化解析快照。`;
}
$('#editEscalation').addEventListener('click', () => { renderEscalationTargets(); $('#escalationDialog').showModal(); });
$('#escalationTargetType').addEventListener('change', renderEscalationTargets);
$('#escalationTargetRef').addEventListener('change', renderEscalationPreview);
$('#saveEscalation').addEventListener('click', () => { $('#escalationDialog').close(); notify('升级策略草稿已保存为类型化目标引用;未复制联系人号码。'); });
let rosterHasConflict = false;
$('#simulateRosterConflict').addEventListener('click', event => {
rosterHasConflict = !rosterHasConflict;
$('#rosterConflict').hidden = !rosterHasConflict;
$('#rosterConflict').textContent = rosterHasConflict ? '发布已阻止:周三 20:00–21:00 存在排班重叠,同时安排了周值班员和王值班员;周四 08:00–09:00 存在空档。请调整后重新检查。' : '';
event.currentTarget.textContent = rosterHasConflict ? '恢复无冲突排班' : '演示冲突检查';
$('#openSchedulePublish').disabled = rosterHasConflict;
notify(rosterHasConflict ? '发现 1 个重叠和 1 个空档,排班不能发布。' : '冲突已修复,未来 14 天覆盖完整。');
});
$('#openSchedulePublish').addEventListener('click', () => { if (rosterHasConflict) { notify('请先修复排班空档和重叠。'); $('#rosterConflict').focus(); return; } $('#scheduleDialog').showModal(); });
$('#confirmSchedulePublish').addEventListener('click', () => {
if (!$('#scheduleReason').value.trim()) { notify('请填写排班发布说明。'); $('#scheduleReason').focus(); return; }
$('#scheduleDialog').close();
$('#scheduleVersion').textContent = 'v13';
notify('排班 v13 已发布,将在设定时间生效;v12 与既有投递快照保持不变。');
});
$('#openSubstitute').addEventListener('click', () => $('#substituteDialog').showModal());
$('#saveSubstitute').addEventListener('click', () => {
if (!$('#substitutePerson').value) { notify('请选择替班人。'); $('#substitutePerson').focus(); return; }
if (!$('#substituteReason').value.trim()) { notify('请填写替班原因。'); $('#substituteReason').focus(); return; }
$('#substituteDialog').close();
$('#wednesdayNightShift').textContent = `${$('#substitutePerson').value}(替班草稿)`;
$('#scheduleVersion').textContent = 'v13 草稿';
notify('临时替班已保存到 v13 草稿,发布前不会影响实际通知。');
});
let editingContact = false;
function openContactEditor(key = '') {
editingContact = Boolean(key);
const names = {wang:'王值班员',lin:'林值班员',lead:'校级负责人'};
$('#contactTitle').textContent = editingContact ? `编辑联系人 · ${names[key]}` : '新建联系人';
$('#contactName').value = editingContact ? names[key] : '';
$('#contactPhone').value = '';
$('#contactPhone').placeholder = editingContact ? '留空则保留已验证号码;旧值不回显' : '输入后发送验证码;不回显旧值';
$('#contactDialog').showModal();
}
$('#openContact').addEventListener('click', () => openContactEditor());
$$('[data-edit-contact]').forEach(button => button.addEventListener('click', () => openContactEditor(button.dataset.editContact)));
$('#saveContact').addEventListener('click', () => {
if (!$('#contactName').value.trim()) { notify('请填写联系人姓名。'); $('#contactName').focus(); return; }
const needsPhone = $('#contactSms').checked || $('#contactVoice').checked;
if (!editingContact && needsPhone && !$('#contactPhone').value.trim()) { notify('短信或语音通道需要填写新号码并完成验证。'); $('#contactPhone').focus(); return; }
$('#contactDialog').close();
if (!editingContact) $('#contactCount').textContent = '7';
notify(editingContact ? '联系人主数据已更新;既有投递快照未改变。' : '联系人已保存,短信/语音通道进入验证流程;尚未加入任何班次。');
});
$('#mobileManage').addEventListener('click', () => $('#manageDialog').showModal());
$$('[data-manage-view]').forEach(button => button.addEventListener('click', () => { $('#manageDialog').close(); switchView(button.dataset.manageView); notify(`已进入${button.textContent.trim()}。`); }));
@@ -806,6 +962,7 @@
const routeParams = new URLSearchParams(location.search);
if (routeParams.get('view')) switchView(routeParams.get('view'));
if (routeParams.get('view') === 'chains' && routeParams.get('section')) switchChainSection(routeParams.get('section'));
if (routeParams.get('view') === 'audit' && (routeParams.has('device') || routeParams.has('site'))) {
const tenant = routeParams.get('tenant') || '当前租户';
const site = routeParams.get('site') || '当前站点';
+12
View File
@@ -43,3 +43,15 @@ Claude 指出的投递状态、Event↔Alert 关系、交接班、分页、规
4. 补事件/审计分页和移动端管理入口。
5. 补规则回滚与按规则版本的验收报表。
6. 用桌面、竖屏手机和横屏手机验证可访问性、无横向溢出及运行时安全;人工确认前 T-004 继续保持 `DOING`。
## 6. 产品裁决补充:联系人、排班与升级策略
采纳“联系人管理和排班一次设计到位”的方向,但将“同源”修正为“共享主数据、分对象建模”:
- 联系人/成员保存身份、角色、值班组和已验证通知通道,不承载班次与轮换字段。
- 值班排班引用联系人或值班组,保存站点时区、班次、周轮换、生效日期、临时替班、版本和冲突校验,不复制手机号。
- 升级策略的每一步使用 `person / team / on_call_schedule` 类型化目标,不直接写号码;界面提供当前解析人与通道预览。
- 每次投递创建时固化实际收件人、通道与排班版本快照,后续联系人或排班变更不改写历史事实。
- 交接班只转移进行中 Alert 的处置责任;未来班次变化必须走临时替班并发布新排班版本。
信息架构继续保留一级“升级链”,内部以“升级策略 / 值班与排班 / 联系人与通道”三个二级模块渐进披露。M3 原型覆盖周轮换、时区、生效日期、替班、空档/重叠冲突、值班人预览、版本发布和审计;自动排班优化、外部日历同步、工时合规与自助换班后置,不在 T-004 扩展。
+1 -1
View File
@@ -14,7 +14,7 @@
| `/alerts` | 待 ack、升级中、已结束预警 | 清楚区分已发出/已送达/已看到/ack;支持值班交接 |
| `/alerts/:alertId` | Alert 处置、投递/升级事实与关联 Event | 展示聚合原因;ack 竞争不覆盖;与 Event 双向导航 |
| `/rules` | 场景包、规则继承、区域/时段配置 | 显示继承来源、试运行、不可变版本和回滚状态 |
| `/escalations` | 联系人、通道、超时与静默 | 双路径;静默 ≤4h,无永久项 |
| `/escalations` | 升级策略、值班排班、联系人和通道 | 一个一级入口下渐进展示三个二级模块;共享人员/值班组/已验证通道主数据但分对象建模;策略使用人员/组/排班类型化目标,支持解析预览、排班版本/替班/冲突与投递快照;双路径,静默 ≤4h,无永久项 |
| `/operations` | 设备/流/分片/对账运维 | 不与业务预警混在同一队列 |
| `/audit` | 审计查询 | 只读、服务端分页、按权限脱敏;深链筛选参数仅按文本渲染 |
+12 -3
View File
@@ -35,8 +35,8 @@ Sense 与 Bell 尚无可供产品确认的页面结构。生产 UI 开工前,
## 关联需求与交互(如适用)
- 用户故事:Sense 关联 US-001、US-002、US-008、US-009,并为 US-005 提供视频设备空间配置;Bell 关联 US-003~US-006、US-010~US-012,并拥有 US-005 的业务规则工作流及 Tenant/Site/Area/RBAC/配额/全局审计真相。
- 交互清单:Sense 关联 IX-001~IX-004、IX-014~IX-020,并提供 IX-010 所需的区域/警戒线几何;Bell 关联 IX-005~IX-013、IX-016、IX-020~IX-022,负责规则继承/试运行/发布/回滚、站点与 Area 策略、值班交接、验收报表和全局审计。
- 用户故事:Sense 关联 US-001、US-002、US-008、US-009,并为 US-005 提供视频设备空间配置;Bell 关联 US-003~US-006、US-010~US-013,并拥有 US-005 的业务规则工作流及 Tenant/Site/Area/RBAC/配额/全局审计真相。
- 交互清单:Sense 关联 IX-001~IX-004、IX-014~IX-020,并提供 IX-010 所需的区域/警戒线几何;Bell 关联 IX-005~IX-013、IX-016、IX-020~IX-023,负责规则继承/试运行/发布/回滚、联系人/排班/升级策略、站点与 Area 策略、值班交接、验收报表和全局审计。
- 相关页面 / 路由:原型阶段为 `docs/design/sense/index.html` 与 `docs/design/bell/index.html`;同步补充 `docs/routes.md` 的候选页面职责,但不冻结或实现生产 URL。
## 方案
@@ -51,6 +51,7 @@ Sense 与 Bell 尚无可供产品确认的页面结构。生产 UI 开工前,
8. 全局头部展示租户、站点、账号和角色;只读角色隐藏写操作,深链越权不泄露资源是否存在。Sense 设备日志带租户/站点/设备条件跳到 Bell 全局审计并可返回。
9. 完成桌面、窄屏、横屏、键盘焦点、加载/空态/错误/依赖降级切换和 `prefers-reduced-motion` 检查,提交人工评审;产品确认前不把任务标为 `DONE`。
10. Bell 事件与审计列表必须分页;规则验收报表按规则版本和冻结样本窗显示召回率与每路每天误报数,不提供跨场景统一准确率;移动端底部主导航保持 5 项,通过顶部“管理”入口访问 Site/Area 与审计。
11. Bell 一级“升级链”内部统一设计升级策略、值班与排班、联系人和通道三个二级模块:共享人员/值班组/已验证通道主数据但分对象维护;升级步骤引用人员/组/排班类型化目标,排班支持时区、周轮换、生效日期、临时替班、冲突检查、解析预览、版本发布和审计,投递保留解析快照。
## 不可变约束
@@ -61,7 +62,7 @@ Sense 与 Bell 尚无可供产品确认的页面结构。生产 UI 开工前,
## 验收要点
- 任务相关验证:两个 HTML 可本地打开、无外部依赖;关键按钮和状态切换可用;Sense 一级导航固定 5 项且无“区域规则”,设备列表可按类型/生命周期筛选与分页,视频详情可编辑检测区域,非视频适配器未交付时不伪装在线;运维中心覆盖对账/分片/边缘/运维告警/系统状态;Bell 可演示投递事实、Event↔Alert、并发 ack、交接班、事件/审计分页、试运行、正式发布、新版本回滚、验收报表、移动端管理入口和 Site/Area 策略冲突;Sense↔Bell 规则深链保留 `camera + zone + return_to`,审计深链保留 `tenant + site + device + return_to` 且只按文本渲染参数;运行 HTML 结构检查、`git diff --check` 与 `./init.ps1`。
- 任务相关验证:两个 HTML 可本地打开、无外部依赖;关键按钮和状态切换可用;Sense 一级导航固定 5 项且无“区域规则”,设备列表可按类型/生命周期筛选与分页,视频详情可编辑检测区域,非视频适配器未交付时不伪装在线;运维中心覆盖对账/分片/边缘/运维告警/系统状态;Bell 可演示投递事实、Event↔Alert、并发 ack、交接班、事件/审计分页、试运行、正式发布、新版本回滚、验收报表、移动端管理入口、Site/Area 策略冲突,以及升级策略/排班/联系人二级模块、动态目标解析、排班发布/替班/冲突和投递快照;Sense↔Bell 规则深链保留 `camera + zone + return_to`,审计深链保留 `tenant + site + device + return_to` 且只按文本渲染参数;运行 HTML 结构检查、`git diff --check` 与 `./init.ps1`。
- 完整门禁:本任务不修改 schema/API 或生产路由实现,因此不触发契约实现门禁;候选路由职责已随 IX 更新。若实现范围变化则另立任务。
- 人工 / 设备验收:必需。项目负责人分别打开 Sense 与 Bell 原型,确认页面结构、控件集合和主要工作流;未确认前不得标记 `DONE` 或合并为最终设计。
- 构建产物:`docs/design/sense/index.html`、`docs/design/bell/index.html`;双击浏览器打开,零构建依赖。
@@ -81,6 +82,14 @@ Sense 与 Bell 尚无可供产品确认的页面结构。生产 UI 开工前,
## 执行记录
### 2026-08-04 联系人、排班与升级策略产品裁决
- 采纳“一次设计到位”的方向,并将“同源”修正为“共享主数据、分对象建模”:Contact/Team 管身份和已验证通道,OnCallSchedule/Version/Exception 管时区、轮换和替班,EscalationStep 使用人员/组/排班类型化目标;DeliveryAttempt 固化实际收件人、通道与排班版本快照。
- 正式需求、架构、US-005/US-013、IX-012/IX-023、候选路由和 `raw/14` 已同步。交接班继续只转移进行中 Alert,未来替班必须形成排班草稿并发布新版本;高级自动排班、外部日历、工时合规和自助换班后置。
- Bell 保留一级“升级链”,内部新增“升级策略 / 值班与排班 / 联系人和通道”三个二级模块。策略可切换 `person / team / on_call_schedule` 并预览解析结果;排班覆盖 Site 时区、周轮换、覆盖率、空档/重叠阻断、临时替班、v13 发布与版本审计;联系人表单不含班次/轮换字段,通道需要验证且被引用时不可直接删除。
- Node.js 内联脚本、167 个唯一 ID、114 个按钮可访问名、全部表单标签、外部依赖和危险 DOM API 检查通过。Edge/CDP 在 1440×900、375×812、812×375 验证动态目标、解析快照、排班冲突、替班草稿、版本发布、联系人边界、交接边界和二级模块深链:页面级无横向溢出,移动端二级按钮不小于 44px,运行时 0 exception;临时 QA 脚本未写入仓库。
- 自动化验收完成后 T-004 仍保持 `DOING`;需项目负责人打开 Bell 原型人工确认升级链信息架构和排班边界,确认前不合并 PR、不标记 `DONE`。
### 2026-08-04 Bell 原型评审复核与修复
- 将 Claude 评审复核为最终裁决并保存到 `raw/14`:采纳投递状态、Event↔Alert、交接班、分页、回滚和验收报表;纠正 Area 归属、A-2、并发 ack 现状、弱网失败态和重启 UI 建议;额外识别移动端管理入口与审计深链 DOM 注入。