feat(alarms): carry AlarmTypeName + operator Comment on AlarmTransitionEvent (historian feed prep)

This commit is contained in:
Joseph Doherty
2026-06-11 11:03:00 -04:00
parent cd72f79ef4
commit 8ac3ac5be9
5 changed files with 61 additions and 4 deletions
@@ -14,6 +14,8 @@ namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts;
/// <param name="Message">Fully-rendered message text — template tokens already resolved.</param>
/// <param name="User">Operator who triggered the transition. "system" for engine-driven events.</param>
/// <param name="TimestampUtc">When the transition occurred.</param>
/// <param name="AlarmTypeName">OPC UA Part 9 condition subtype name — one of <c>LimitAlarm</c> / <c>DiscreteAlarm</c> / <c>OffNormalAlarm</c> / <c>AlarmCondition</c> (the base type, used as the default). The historian feed maps this onto the durable alarm-type column.</param>
/// <param name="Comment">Operator-supplied comment on ack / confirm / comment transitions; <c>null</c> for engine-driven transitions (Activated / Cleared / Shelved / …) that carry no comment.</param>
public sealed record AlarmTransitionEvent(
string AlarmId,
string EquipmentPath,
@@ -22,4 +24,6 @@ public sealed record AlarmTransitionEvent(
int Severity,
string Message,
string User,
DateTime TimestampUtc);
DateTime TimestampUtc,
string AlarmTypeName = "AlarmCondition",
string? Comment = null);
@@ -598,7 +598,17 @@ public sealed class ScriptedAlarmEngine : IDisposable
Message: message,
Condition: condition,
Emission: kind,
TimestampUtc: _clock());
TimestampUtc: _clock(),
// Operator comment rides along on comment-bearing transitions — the condition
// state already carries it. Engine-driven transitions (Activated/Cleared/Shelved/…)
// and shelve ops (no comment param) leave it null.
Comment: kind switch
{
EmissionKind.Acknowledged => condition.LastAckComment,
EmissionKind.Confirmed => condition.LastConfirmComment,
EmissionKind.CommentAdded => condition.Comments.Count == 0 ? null : condition.Comments[^1].Text,
_ => null,
});
}
/// <summary>
@@ -823,7 +833,8 @@ public sealed record ScriptedAlarmEvent(
string Message,
AlarmConditionState Condition,
EmissionKind Emission,
DateTime TimestampUtc);
DateTime TimestampUtc,
string? Comment = null);
/// <summary>
/// Upstream source abstraction — intentionally identical shape to the virtual-tag
@@ -294,7 +294,11 @@ public sealed class ScriptedAlarmHostActor : ReceiveActor
Severity: SeverityToInt(e.Severity),
Message: e.Message,
User: TransitionUser(e),
TimestampUtc: e.TimestampUtc);
TimestampUtc: e.TimestampUtc,
// Historian feed prep: carry the Part-9 subtype name (e.Kind.ToString() yields
// LimitAlarm/DiscreteAlarm/OffNormalAlarm/AlarmCondition) + any operator comment.
AlarmTypeName: e.Kind.ToString(),
Comment: e.Comment);
// Warm-standby dedup: only the Primary (driver-role leader) publishes the cluster-wide
// transition. Default-emit until told we are Secondary/Detached so single-node deploys + the