fix(alarms): Severity runs one direction, higher = more urgent; raw priority kept (#129)
AlarmConditionState.Severity mixed two conventions: OPC UA rows carried Part 9 Severity (higher = more severe) while MxGateway native alarms and computed alarms carried a raw AVEVA-style priority (1 = most urgent). Every consumer ranks higher as worse, so Worst Severity, the severity filter/sort and the site event-log Error threshold picked the LEAST urgent MxGateway/computed alarms. - AlarmPriorityBands: the OtOpcUa GalaxyAlarmPriority table (lmxopcua#887, b61f3e05), value for value: 1-250 -> 1000, 251-500 -> 750, 501-750 -> 500, 751-999 -> 250; <= 0 clamps to Critical, > 999 to Low. - MxGatewayAlarmMapper bands the gateway severity (= AVEVA priority); AlarmConditionStateFactory.ForComputed bands PriorityLevel. OPC UA unchanged. - AlarmConditionState gains trailing Priority (int?): the raw source number, exactly the pre-#129 Severity. AlarmStateChanged.Priority and the wire AlarmStateUpdate.priority keep that meaning (no proto change); central derives Severity by kind (SeverityFromPriority), so an older site decodes correctly. - ScriptAlarm gains trailing Priority. The native MES 900-999 band and the MES API Severity/MinSeverity/MaxSeverity key off Priority; rig seed scripts moved from a.Severity to a.Priority, so MES output is unchanged. - AlarmActor raise logs Error when the banded severity >= 700 (same threshold as NativeAlarmActor): priority 1-500 Error, 501+ Warning (was raw >= 700). - NativeAlarmActor upgrades pre-#129 native_alarm_state rows on rehydrate. - Debug snapshot decode restores Condition.Priority from the row's priority. - Docs: Commons, DCL, SiteRuntime, Communication, CentralUI, InboundAPI, HighLevelReqs, MES alarm-status plan, CLAUDE.md. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -32,11 +32,12 @@ public class AlarmSummaryRenderTests : BunitContext
|
||||
/// <summary>Initializes the test class and configures the faked services and the bUnit context.</summary>
|
||||
public AlarmSummaryRenderTests()
|
||||
{
|
||||
// Two rows whose default sort (severity DESC) orders Zeta (900) before
|
||||
// Alpha (500); an ascending sort by instance name flips them.
|
||||
// Two computed rows whose default sort (severity DESC) orders Zeta before Alpha:
|
||||
// priority 100 bands to Severity 1000, priority 500 to 750 (Gitea #129 — a lower
|
||||
// priority is more urgent). An ascending sort by instance name flips them.
|
||||
var rows = new List<AlarmSummaryRow>
|
||||
{
|
||||
new("Zeta", new AlarmStateChanged("Zeta", "Z-alarm", AlarmState.Active, 900, DateTimeOffset.UtcNow)),
|
||||
new("Zeta", new AlarmStateChanged("Zeta", "Z-alarm", AlarmState.Active, 100, DateTimeOffset.UtcNow)),
|
||||
new("Alpha", new AlarmStateChanged("Alpha", "A-alarm", AlarmState.Active, 500, DateTimeOffset.UtcNow)),
|
||||
};
|
||||
|
||||
@@ -267,7 +268,7 @@ public class AlarmSummaryRenderTests : BunitContext
|
||||
.Returns(ci => Task.FromResult(new AlarmSummaryResult(
|
||||
new List<AlarmSummaryRow>
|
||||
{
|
||||
new("Zeta", new AlarmStateChanged("Zeta", "Z-alarm", AlarmState.Active, 900, DateTimeOffset.UtcNow)),
|
||||
new("Zeta", new AlarmStateChanged("Zeta", "Z-alarm", AlarmState.Active, 100, DateTimeOffset.UtcNow)),
|
||||
},
|
||||
new[] { "OfflineInstance" })));
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ public class AlarmSummaryServiceTests
|
||||
new("inst-b", NativeAlarm("inst-b", "MtrTrip", AlarmState.Active, 300, active: true, acked: false, kind: AlarmKind.NativeMxAccess)),
|
||||
// computed normal (not active) → ignored by active/unacked, severity 200 not counted
|
||||
new("inst-a", ComputedAlarm("inst-a", "PumpFault", AlarmState.Normal, 200)),
|
||||
// computed active sev 999 — active, but computed so NOT unacked
|
||||
// computed active priority 999 = AVEVA Low → Severity 250 (#129) — active, but computed so NOT unacked
|
||||
new("inst-c", ComputedAlarm("inst-c", "HiHi", AlarmState.Active, 999)),
|
||||
};
|
||||
|
||||
@@ -148,8 +148,8 @@ public class AlarmSummaryServiceTests
|
||||
|
||||
// Active = the 3 native actives + the active computed = 4
|
||||
Assert.Equal(4, rollup.TotalActive);
|
||||
// Worst severity among active = 999 (the active computed)
|
||||
Assert.Equal(999, rollup.WorstSeverity);
|
||||
// Worst severity among active = 800 (TankHi); the computed priority 999 is the LEAST urgent
|
||||
Assert.Equal(800, rollup.WorstSeverity);
|
||||
// Unacked = active && !acked && kind != Computed = TankHi + MtrTrip = 2
|
||||
Assert.Equal(2, rollup.UnackedCount);
|
||||
// Kind counts: NativeOpcUa 2, NativeMxAccess 1, Computed 2
|
||||
@@ -158,6 +158,36 @@ public class AlarmSummaryServiceTests
|
||||
Assert.Equal(2, rollup.CountsByKind[AlarmKind.Computed]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #129: "Worst Severity" is the most URGENT alarm across every kind. AVEVA priority 1 (computed or
|
||||
/// MxAccess) bands to 1000 and outranks priority 999 (250) and an OPC UA Severity of 900.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ComputeRollup_WorstSeverity_IsTheMostUrgentPriority()
|
||||
{
|
||||
var mx = new AlarmStateChanged("inst-a", "MtrTrip", AlarmState.Active, 1, Now)
|
||||
{
|
||||
Kind = AlarmKind.NativeMxAccess,
|
||||
Condition = new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false,
|
||||
AlarmPriorityBands.ToSeverity(1), Priority: 1),
|
||||
};
|
||||
var rows = new List<AlarmSummaryRow>
|
||||
{
|
||||
new("inst-a", ComputedAlarm("inst-a", "Minor", AlarmState.Active, 999)),
|
||||
new("inst-b", NativeAlarm("inst-b", "TankHi", AlarmState.Active, 900, active: true, acked: false)),
|
||||
new("inst-a", mx),
|
||||
};
|
||||
|
||||
Assert.Equal(1000, CreateSut().ComputeRollup(rows).WorstSeverity);
|
||||
|
||||
var computedOnly = new List<AlarmSummaryRow>
|
||||
{
|
||||
new("inst-a", ComputedAlarm("inst-a", "Minor", AlarmState.Active, 999)),
|
||||
new("inst-a", ComputedAlarm("inst-a", "Major", AlarmState.Active, 1)),
|
||||
};
|
||||
Assert.Equal(1000, CreateSut().ComputeRollup(computedOnly).WorstSeverity);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a rollup with no active rows has a worst severity of zero.</summary>
|
||||
[Fact]
|
||||
public void ComputeRollup_NoActiveRows_WorstSeverityZero()
|
||||
|
||||
Reference in New Issue
Block a user