diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs index 7723c662..aa679d8f 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs @@ -171,7 +171,7 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers /// TagConfig.alarm.historizeToAveva; it is threaded onto the transition so the /// HistorianAdapterActor's is not false gate suppresses the durable AVEVA row only on an /// explicit false (mirroring the scripted-alarm opt-out). - private readonly Dictionary _alarmMetaByNodeId = + private readonly Dictionary ReferencingEquipmentPaths)> _alarmMetaByNodeId = new(StringComparer.Ordinal); /// Derives a full Part 9 condition snapshot from each native alarm transition delta, @@ -1048,7 +1048,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers if (!serviceAlertsAsPrimary) continue; var meta = _alarmMetaByNodeId.TryGetValue(nodeId, out var m) - ? m : (EquipmentId: nodeId, Name: nodeId, AlarmType: "AlarmCondition", HistorizeToAveva: (bool?)null); + ? m : (EquipmentId: nodeId, Name: nodeId, AlarmType: "AlarmCondition", HistorizeToAveva: (bool?)null, + ReferencingEquipmentPaths: (IReadOnlyList)Array.Empty()); _mediator.Tell(new Publish(ScriptedAlarmHostActor.AlertsTopic, new AlarmTransitionEvent( AlarmId: nodeId, EquipmentPath: meta.EquipmentId, @@ -1068,7 +1069,10 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers // 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))); + HistorizeToAveva: meta.HistorizeToAveva, + // WP4: the Area/Line/Equipment UNS paths that reference this raw condition — the /alerts row + // renders them as the equipment list (empty for a raw condition no equipment references yet). + ReferencingEquipmentPaths: meta.ReferencingEquipmentPaths))); } } @@ -1573,8 +1577,10 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers // Inverse 1:1 map for the inbound native-condition ack path (keyed by BARE condition NodeId). _driverRefByAlarmNodeId[t.NodeId] = key; // Per-condition metadata for the alerts fan-out. WP4 refines /alerts identity to the referencing - // equipment paths; WP3 keys the display off the RawPath. - _alarmMetaByNodeId[t.NodeId] = (t.NodeId, t.Name, t.Alarm.AlarmType, t.Alarm.HistorizeToAveva); + // equipment paths; WP3 keys the display off the RawPath. The referencing-equipment paths (the + // Area/Line/Equipment UNS folder paths carried on the RawTagPlan) ride onto every transition so + // the /alerts row lists which equipment reference this raw condition. + _alarmMetaByNodeId[t.NodeId] = (t.NodeId, t.Name, t.Alarm.AlarmType, t.Alarm.HistorizeToAveva, t.ReferencingEquipmentPaths); continue; }