feat(historian): honor per-alarm HistorizeToAveva opt-out at the durable write

This commit is contained in:
Joseph Doherty
2026-06-11 12:48:13 -04:00
parent fa839d1dbf
commit 8012509584
6 changed files with 78 additions and 9 deletions
@@ -22,14 +22,16 @@ public sealed class ScriptedAlarmEngineTests
}
private static ScriptedAlarmDefinition Alarm(string id, string predicate,
string msg = "condition", AlarmSeverity sev = AlarmSeverity.High) =>
string msg = "condition", AlarmSeverity sev = AlarmSeverity.High,
bool historizeToAveva = true) =>
new(AlarmId: id,
EquipmentPath: "Plant/Line1/Reactor",
AlarmName: id,
Kind: AlarmKind.AlarmCondition,
Severity: sev,
MessageTemplate: msg,
PredicateScriptSource: predicate);
PredicateScriptSource: predicate,
HistorizeToAveva: historizeToAveva);
/// <summary>Verifies that LoadAsync compiles the alarm predicate and subscribes to all referenced upstream tags.</summary>
[Fact]
@@ -479,6 +481,38 @@ public sealed class ScriptedAlarmEngineTests
events.First(e => e.Emission == EmissionKind.CommentAdded).Comment.ShouldBe("second look");
}
/// <summary>Verifies the emitted <see cref="ScriptedAlarmEvent.HistorizeToAveva"/> carries the
/// per-alarm opt-out flag from the <see cref="ScriptedAlarmDefinition"/> through to the event so the
/// historian adapter can suppress the durable write while the live alerts fan-out is unaffected.
/// An alarm defined <c>HistorizeToAveva: false</c> emits <c>false</c>; the default (<c>true</c>)
/// emits <c>true</c>.</summary>
[Fact]
public async Task Emission_carries_HistorizeToAveva_flag_from_definition()
{
var up = new FakeUpstream();
up.Set("Temp", 50);
using var eng = Build(up, out _);
await eng.LoadAsync(
[
Alarm("OptOut", """return (int)ctx.GetTag("Temp").Value > 100;""", historizeToAveva: false),
Alarm("OptIn", """return (int)ctx.GetTag("Temp").Value > 100;""" /* default true */),
],
TestContext.Current.CancellationToken);
var events = new List<ScriptedAlarmEvent>();
eng.OnEvent += (_, e) => events.Add(e);
up.Push("Temp", 150);
await WaitForAsync(() =>
events.Any(e => e.AlarmId == "OptOut" && e.Emission == EmissionKind.Activated) &&
events.Any(e => e.AlarmId == "OptIn" && e.Emission == EmissionKind.Activated));
events.First(e => e.AlarmId == "OptOut" && e.Emission == EmissionKind.Activated)
.HistorizeToAveva.ShouldBeFalse("opt-out alarm carries HistorizeToAveva=false on its emission");
events.First(e => e.AlarmId == "OptIn" && e.Emission == EmissionKind.Activated)
.HistorizeToAveva.ShouldBeTrue("opt-in (default) alarm carries HistorizeToAveva=true on its emission");
}
// (2b) TimedShelveAsync / UnshelveAsync end-to-end through the engine.
/// <summary>Verifies that TimedShelveAsync shelves with a deadline and UnshelveAsync removes the shelve before the timer expires.</summary>
[Fact]