Skip to content

feat(alarm): operator value-threshold alarms on the home (#158 Phase 2a)#240

Merged
takashikasuya merged 1 commit into
mainfrom
claude/alarm-phase2a-158
Jul 20, 2026
Merged

feat(alarm): operator value-threshold alarms on the home (#158 Phase 2a)#240
takashikasuya merged 1 commit into
mainfrom
claude/alarm-phase2a-158

Conversation

@takashikasuya

Copy link
Copy Markdown
Contributor

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).

  • Twin (opt-in per-point)bos:alarmHigh / alarmLow / warnHigh / warnLow added to OxiGraphOntology, projected in BuildPointSelect, carried on the Point entity. 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).
  • Pure classifier lib/telemetry/alarm.tsclassifyPointAlarmok/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 existing batch-latest (the API already returned value; 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). activeAlarms drops alarms on stale/missing points so an hour-old reading is never shown as a live 「異常値」 (a /code-review finding, fixed here).
  • operator-home — an 異常 summary card + value-breach rows (「異常値(上限 40)」 / 「注意値(下限 …)」), linking to the point detail like the freshness rows.

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 testShared.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 build
  • Playwright home + 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):

  1. Not verifiable offline (no OxiGraph/Docker): the real SPARQL projection of the new 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.
  2. Aspida regen pending: mapping.ts reads the new Point.alarm* fields via a documented cast until ./sync-type.bash runs against a live API server (not runnable offline) — the regen is a no-op that formalizes them.
  3. Two batch-latest reads per floor (freshness + alarms) — admin-only low-frequency; consolidating into one shared fetch is a noted follow-up.

Checklist

  • I read CONTRIBUTING.md and followed the coding conventions in CLAUDE.md
  • I added or updated tests for the change
  • I updated relevant docs — ADR-0005 status (Phase 2a implemented + the D1/D2 decision)

🤖 Generated with Claude Code


Generated by Claude Code

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>
@takashikasuya
takashikasuya merged commit befb856 into main Jul 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants