fix(alarms): native-alarm re-statements no longer duplicate history or hide acked alarms (#656)
- AlarmTransitionKind.ActiveAcknowledged (appended): active AND acked applied as ONE projection. Galaxy ActiveAcked snapshot entries and OpcUaClient inactive->(active,acked) use it; the projector writes 'Activated' only if not already active, 'Acknowledged' only if not acked. - AlarmEventArgs.IsRestatement (optional, default false): set only by Galaxy snapshot entries and OpcUaClient first sight. A flagged Retrigger on an Active+unacked condition with the same message and severity, or a flagged Clear on an inactive condition, writes no /alerts row. - NativeAlarmProjector reports the history rows per transition; ForwardNativeAlarm writes one row per entry. Unflagged sources (FOCAS/MTConnect/AbCip, live Galaxy) are unchanged. - Tests: projector rules each with a must-write control; OpcUaClient transition table; Galaxy feed/driver mapping; Galaxy + OpcUaClient end-to-end through DriverHostActor. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+39
@@ -84,6 +84,45 @@ public sealed class GalaxyDriverAlarmEventArgsExtensionTests
|
||||
observed[0].AlarmCategory.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #656 — a snapshot ActiveAcknowledged transition reaches <see cref="AlarmEventArgs"/> as
|
||||
/// <see cref="AlarmTransitionKind.ActiveAcknowledged"/> (not Unspecified, which would keep the host's prior
|
||||
/// state) with <see cref="AlarmEventArgs.IsRestatement"/> carried through; an unflagged transition stays false.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ActiveAcknowledged_snapshot_transition_maps_kind_and_restatement_flag()
|
||||
{
|
||||
var feed = new FakeAlarmFeed();
|
||||
using var driver = NewDriver(feed);
|
||||
|
||||
await driver.SubscribeAlarmsAsync(["Tank01"], CancellationToken.None);
|
||||
var observed = new List<AlarmEventArgs>();
|
||||
driver.OnAlarmEvent += (_, args) => observed.Add(args);
|
||||
|
||||
var t = new GalaxyAlarmTransition(
|
||||
AlarmFullReference: "Tank01.Level.HiHi",
|
||||
SourceObjectReference: "Tank01",
|
||||
AlarmTypeName: "AnalogLimitAlarm.HiHi",
|
||||
TransitionKind: GalaxyAlarmTransitionKind.ActiveAcknowledged,
|
||||
SeverityBucket: AlarmSeverity.Critical,
|
||||
OpcUaSeverity: 800,
|
||||
RawMxAccessSeverity: 750,
|
||||
OriginalRaiseTimestampUtc: null,
|
||||
TransitionTimestampUtc: DateTime.UtcNow,
|
||||
OperatorUser: "alice",
|
||||
OperatorComment: "seen",
|
||||
Category: "Process",
|
||||
Description: "Tank 01 high-high level",
|
||||
IsRestatement: true);
|
||||
feed.Emit(t);
|
||||
feed.Emit(t with { TransitionKind = GalaxyAlarmTransitionKind.Retrigger, IsRestatement = false });
|
||||
|
||||
observed.Select(e => (e.Kind, e.IsRestatement)).ShouldBe([
|
||||
(AlarmTransitionKind.ActiveAcknowledged, true),
|
||||
(AlarmTransitionKind.Retrigger, false),
|
||||
]);
|
||||
}
|
||||
|
||||
private static GalaxyDriver NewDriver(IGalaxyAlarmFeed feed)
|
||||
{
|
||||
var options = new GalaxyDriverOptions(
|
||||
|
||||
Reference in New Issue
Block a user