feat(alarm): operator value-threshold alarms on the home (#158 Phase 2a)#240
Merged
Conversation
Implements ADR-0005 Phase 2a (maintainer decision: twin per-point thresholds, derived-on-read, stateless, no notification). The operator home now surfaces points whose current value breaches an opt-in threshold — a distinct axis from freshness (arrival) and gateway connection (#230). - Twin: opt-in per-point `bos:alarmHigh`/`alarmLow`/`warnHigh`/`warnLow` (`OxiGraphOntology` + `BuildPointSelect` projection + `Point` entity). Distinct from the control-write bounds (`bos:min/maxValue`); a point with none is not evaluated. Demo thresholds seeded on the sample twin (室温 / CO2). - Pure classifier `lib/telemetry/alarm.ts` (`classifyPointAlarm` → ok/warn/ critical/unknown; critical outer limits win over warn; boundary is a breach). - `alarm-loader.ts` reads current values via the existing batch-latest (value was already returned, just dropped — now kept) and classifies against thresholds. - `buildAttentionList` merges value alarms into the worst-first list (critical→warn→missing→stale); `activeAlarms` drops alarms on stale/missing points so an old reading is never shown as a live 「異常値」 (code-review fix). - operator-home: an 異常 summary card + value-breach rows (「異常値(上限 40)」). Thresholds flow through a documented `mapping.ts` cast until `./sync-type.bash` formalizes the aspida `Point.alarmHigh` fields (not runnable offline). loadAlarms issues a separate batch-latest from freshness (admin low-frequency; consolidating is a noted follow-up). Notification/history/ack (Phase 2b, stateful) deferred. Tests: alarm classifier + loader, aggregate merge + activeAlarms, mapping, operator-home alarm rendering; dotnet Shared 688 / ApiServer 146; e2e 11. Refs #158 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEG6csNA9GDkQzT37CwAUp Signed-off-by: Claude <noreply@anthropic.com>
21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements ADR-0005 Phase 2a (#239). Per the maintainer decision: twin per-point thresholds, derived-on-read, stateless, no notification. The operator home now surfaces points whose current value breaches an opt-in threshold — a distinct axis from freshness (arrival, #158 Phase 1) and gateway connection (#230).
bos:alarmHigh/alarmLow/warnHigh/warnLowadded toOxiGraphOntology, projected inBuildPointSelect, carried on thePointentity. Distinct from the control-write bounds (bos:min/maxValue, Close remaining gateway multi-connection test gaps from #114 #153) — these are the normal-operation value range. A point with no thresholds is simply not evaluated (unknown). Demo thresholds seeded on the sample twin (室温 30/28/10, CO2 1500/1000).lib/telemetry/alarm.ts—classifyPointAlarm→ok/warn/critical/unknown; critical (outer) limits win over warn (inner); the threshold boundary counts as a breach; no value / no thresholds →unknown. Snapshot semantics, no hysteresis (stateless).alarm-loader.ts— reads current values via the existingbatch-latest(the API already returnedvalue; the frontend was dropping it — now kept) and classifies against each point's thresholds.buildAttentionList— merges value alarms into the worst-first list (critical→warn→missing→stale).activeAlarmsdrops alarms on stale/missing points so an hour-old reading is never shown as a live 「異常値」 (a/code-reviewfinding, fixed here).Deferred (ADR-0005): notification / event history / ack (Phase 2b — stateful, per the maintainer's memory/stability concern) and per-unit rule editing (Phase 2c).
Test plan
dotnet test— Shared.Test 688, ApiServer.Test 146 (Point entity + SPARQL projection additive)yarn typecheck/yarn lint(0 errors) /yarn test(484, +7: classifier, loader, aggregate merge +activeAlarms, mapping, operator-home) /yarn buildhome+admin-flows(11, route-mocked)/code-review(high) — the stale-value-alarm finding was fixed (activeAlarms); two remaining are documented (below)Known / documented (not blockers):
bos:alarm*triples end-to-end. Covered by the unit path (MapPoint, classifier, loader) + the seeded demo twin; the live twin read runs once the stack is up.mapping.tsreads the newPoint.alarm*fields via a documented cast until./sync-type.bashruns against a live API server (not runnable offline) — the regen is a no-op that formalizes them.batch-latestreads per floor (freshness + alarms) — admin-only low-frequency; consolidating into one shared fetch is a noted follow-up.Checklist
🤖 Generated with Claude Code
Generated by Claude Code