模拟引擎与业务状态机模式
返回 SKILL.md。下文模式提炼自官方参考实现(能源站;罐区+联锁演练+隐患/作业票)。
1. 分层:纯函数内核 + runtime 接线
points.ts 测点/设备/映射 单一事实源(initialPoints() 同时喂 connectionData 初始行与 sim 初值)
simulator.ts 物理模型:createSimState() + tickSim(state, dtSec, rng) → AlarmEvent[] ←纯函数,可单测
alarms.ts 告警状态机:applyAlarmEvents / ackAlarm / alarmRows / alarmStats / alarmTrendRows ←纯函数
<domain>.ts 领域状态机(隐患生命周期 / 作业票流转 / 演练脚本) ←纯函数
runtime.ts 接线:setInterval(tick) → ctx.setData 各表;返回控制 API 给页面按钮
纯逻辑文件禁止 import vjvpsdk 的运行时值(import type 可以)——vitest 不依赖 SDK 构建产物。
2. 随机性:注入 rng,同种子可复现
export type Rng = () => number
export function mulberry32(seed: number): Rng { /* 标准 mulberry32 实现 */ }
// tick 内全部随机来自参数 rng;测试用恒值 rng(0.5=无事件无噪声)或首拍小值 rng 定向注入故障
3. 模拟量收敛(让数据"像真的")
approach(cur, target, rate):模拟量向"运行组合决定的目标值"收敛(流量∝运行泵数、温度∝运行冷机、功率=组合求和…),叠加noise(amp)。clamp限界;输出用round1/round2收小数。- 缓慢往复量(液位)用方向变量 + 边界翻转 + 小概率随机换向。
- 事件注入:运行设备小概率故障(
faultLeft计拍自愈);演练类(入侵/泄漏)提供triggerXxx(state)手动入口供演练按钮调用。
4. 告警规则:边沿触发(raise/resolve 各一次)
// state.activeRules: Record<string, boolean> 去抖
const hit = rule.hit(state)
if (hit && !state.activeRules[key]) { state.activeRules[key] = true; events.push({ type: 'raise', ruleKey: key, level, device, content }) }
else if (!hit && state.activeRules[key]) { delete state.activeRules[key]; events.push({ type: 'resolve', ruleKey: key }) }
告警状态机(alarms.ts)职责:同 ruleKey 活动期去重入列、resolve 置「已恢复」保留记录、人工 ack、容量上限丢最旧已恢复;派生视图 alarmRows(filter)/alarmStatRows/alarmTrendRows(now) 直接是表行形状。
5. 控制闭环与联锁
- 控制 API:
setDevice(state, id, run)改状态 → 下一拍模拟量随之变化 → 组态/三维/图表全联动。runtime 里控制后立即补推一次(按钮反馈即时)。 - 联锁/演练状态机:多阶段脚本(如 泄漏演练:normal → leaking(气体爬升) → 越限自动联锁(关阀停泵 + raise 严重告警 + 副作用回调如自动开应急窗) → recovering → normal)。阶段存在 state 上,tick 里推进;对外暴露
startDrill()。副作用(开窗/切页)不写在纯函数里——tick 返回事件,runtime 侧解释执行。
6. 数据建模五件套(connections 套路)
| 表 | 用途 | 形状 |
|---|---|---|
测点长表 tbl_pts |
驱动组态/三维/地图元素(point-key-field/point-value-field 绑定,元素填 data-key) |
两列:键/值(值可数值或 JSON 串——热力/人员定位整批刷新) |
宽表快照 tbl_rt |
2D 指标文本 value-field 绑定(取首行) |
单行多列 |
| 台账表 | 表格/列表(设备/企业/风险点/作业票) | 多行业务列(表格运行时给每行注入 _index=1 基行号,作 keyField/序号列数据源) |
| 告警四表 | 告警表/等级分布/时段趋势/KPI 未处置数 | tbl_alarm+tbl_alarm_stat+tbl_alarm_trend(+并入 KPI) |
历史滚动 tbl_history |
趋势图/趋势窗口 | 时间列 + N 指标列,固定窗口(如 48 行)滚动 |
派生视图函数(toPointRows/toRtRow/toKpiRow/toHistoryRow/...)输出列名必须与 connections 字段定义一致——写一致性单测锁住(见 §8)。
7. runtime 接线与推数节流
- 主循环
setInterval(doTick, 2000);测点/快照/KPI 每拍推;告警/台账只在有事件时推;历史/慢表每 3 拍推一次并裁窗口。 - 控制 API 对象(含
setDevice/ack/setFilter/startDrill/onXxxChange订阅)由startSim(ctx)返回,build.ts 传给各页 builder。 - 视图筛选(告警等级/台账关键字):筛选状态存 runtime,改变即重推该表——"代码筛选 + 全量重推"比在组件里做过滤简单可靠。
8. 单测配方(vitest)
[ ] 平稳运行:N 拍后模拟量在合理区间(恒值 rng)
[ ] 可复现:同种子两次跑 toPointRows 深等
[ ] 控制闭环:停设备 → 量值衰减 → raise 目标告警;恢复 → resolve
[ ] 事件注入:定向 rng 首拍注入故障/入侵 → 严重告警;复位/超时 → resolve
[ ] 状态机:状态流转合法(隐患 待整改→整改中→已闭环;作业票 待审批→作业中→已完工;非法流转拒绝)
[ ] 派生视图形状:toXxxRow 列名 ⊆ connections 字段(数据层一致性)
[ ] 场景一致性:pieces 引用的 data-key ⊆ initialPoints();连线端点图元存在;联动映射表目标 uid 存在