feat(adminui): native-alarm HistorizeToAveva opt-out

This commit is contained in:
Joseph Doherty
2026-06-16 16:27:31 -04:00
parent 72d414ada7
commit 6a8020e7e7
8 changed files with 393 additions and 15 deletions
@@ -139,9 +139,13 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
private readonly Dictionary<string, (string DriverInstanceId, string FullName)> _driverRefByAlarmNodeId =
new(StringComparer.Ordinal);
/// <summary>Condition NodeId → (EquipmentId, tag Name, OPC UA alarm type) for building the
/// AlarmTransitionEvent fan-out. Built in the same PushDesiredSubscriptions alarm branch.</summary>
private readonly Dictionary<string, (string EquipmentId, string Name, string AlarmType)> _alarmMetaByNodeId =
/// <summary>Condition NodeId → (EquipmentId, tag Name, OPC UA alarm type, HistorizeToAveva) for building
/// the AlarmTransitionEvent fan-out. Built in the same PushDesiredSubscriptions alarm branch.
/// HistorizeToAveva (bool?, null ⇒ historize) is the native per-condition opt-out parsed from
/// <c>TagConfig.alarm.historizeToAveva</c>; it is threaded onto the transition so the
/// HistorianAdapterActor's <c>is not false</c> gate suppresses the durable AVEVA row only on an
/// explicit false (mirroring the scripted-alarm opt-out).</summary>
private readonly Dictionary<string, (string EquipmentId, string Name, string AlarmType, bool? HistorizeToAveva)> _alarmMetaByNodeId =
new(StringComparer.Ordinal);
/// <summary>Derives a full Part 9 condition snapshot from each native alarm transition delta,
@@ -589,7 +593,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
if (_localRole is RedundancyRole.Secondary or RedundancyRole.Detached) continue;
var meta = _alarmMetaByNodeId.TryGetValue(nodeId, out var m)
? m : (EquipmentId: nodeId, Name: nodeId, AlarmType: "AlarmCondition");
? m : (EquipmentId: nodeId, Name: nodeId, AlarmType: "AlarmCondition", HistorizeToAveva: (bool?)null);
_mediator.Tell(new Publish(ScriptedAlarmHostActor.AlertsTopic, new AlarmTransitionEvent(
AlarmId: nodeId,
EquipmentPath: meta.EquipmentId,
@@ -605,9 +609,11 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
TimestampUtc: msg.Args.SourceTimestampUtc,
AlarmTypeName: meta.AlarmType,
Comment: msg.Args.OperatorComment,
// Native alarms always historize (no per-condition opt-out surface yet — that is a
// scripted-alarm plan flag); pass a concrete true so the historian's null-default isn't relied on.
HistorizeToAveva: true)));
// Per-condition opt-out parsed from TagConfig.alarm.historizeToAveva (bool?, null ⇒ absent ⇒
// historize). The HistorianAdapterActor gate (historizeToAveva is not false) historizes null +
// true and suppresses the durable AVEVA row only on an explicit false — the same posture as the
// scripted-alarm opt-out. null here rides through unchanged (the gate treats it as default-on).
HistorizeToAveva: meta.HistorizeToAveva)));
}
}
@@ -1020,7 +1026,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
// Capture the per-condition metadata the alerts fan-out (ForwardNativeAlarm) needs to build
// the AlarmTransitionEvent: the equipment path, the operator-visible alarm name, and the
// OPC UA Part 9 subtype. Keyed by the condition NodeId (the projection's own key).
_alarmMetaByNodeId[nodeId] = (t.EquipmentId, t.Name, t.Alarm.AlarmType);
_alarmMetaByNodeId[nodeId] = (t.EquipmentId, t.Name, t.Alarm.AlarmType, t.Alarm.HistorizeToAveva);
continue;
}
if (!_nodeIdByDriverRef.TryGetValue(key, out var set))