feat(opcua): diff Equipment VirtualTags in Phase7Plan + rebuild trigger

This commit is contained in:
Joseph Doherty
2026-06-07 05:15:21 -04:00
parent c7661d0510
commit 9464c91546
4 changed files with 149 additions and 7 deletions
@@ -40,19 +40,36 @@ public sealed record Phase7Plan(
/// <inheritdoc cref="AddedEquipmentTags"/>
public IReadOnlyList<EquipmentTagDelta> ChangedEquipmentTags { get; init; } = Array.Empty<EquipmentTagDelta>();
/// <summary>
/// Equipment-namespace VirtualTag diff sets, keyed by <see cref="EquipmentVirtualTagPlan.VirtualTagId"/>.
/// The value-side analogue of <see cref="AddedEquipmentTags"/>: a VirtualTag carries an
/// <c>Expression</c> evaluated over <c>DependencyRefs</c>, so a deploy that changes ONLY
/// VirtualTags (e.g. a new computed signal or an edited formula) must still produce a
/// non-empty plan and drive a rebuild — without these the diff was blind to VirtualTags and
/// such a deploy silently no-op'd. Added as init-only members (defaulting empty) for the same
/// compile-compatibility reason as <see cref="AddedEquipmentTags"/>.
/// </summary>
public IReadOnlyList<EquipmentVirtualTagPlan> AddedEquipmentVirtualTags { get; init; } = Array.Empty<EquipmentVirtualTagPlan>();
/// <inheritdoc cref="AddedEquipmentVirtualTags"/>
public IReadOnlyList<EquipmentVirtualTagPlan> RemovedEquipmentVirtualTags { get; init; } = Array.Empty<EquipmentVirtualTagPlan>();
/// <inheritdoc cref="AddedEquipmentVirtualTags"/>
public IReadOnlyList<EquipmentVirtualTagDelta> ChangedEquipmentVirtualTags { get; init; } = Array.Empty<EquipmentVirtualTagDelta>();
/// <summary>Gets a value indicating whether the composition plan contains no changes.</summary>
public bool IsEmpty =>
AddedEquipment.Count == 0 && RemovedEquipment.Count == 0 && ChangedEquipment.Count == 0 &&
AddedDrivers.Count == 0 && RemovedDrivers.Count == 0 && ChangedDrivers.Count == 0 &&
AddedAlarms.Count == 0 && RemovedAlarms.Count == 0 && ChangedAlarms.Count == 0 &&
AddedGalaxyTags.Count == 0 && RemovedGalaxyTags.Count == 0 && ChangedGalaxyTags.Count == 0 &&
AddedEquipmentTags.Count == 0 && RemovedEquipmentTags.Count == 0 && ChangedEquipmentTags.Count == 0;
AddedEquipmentTags.Count == 0 && RemovedEquipmentTags.Count == 0 && ChangedEquipmentTags.Count == 0 &&
AddedEquipmentVirtualTags.Count == 0 && RemovedEquipmentVirtualTags.Count == 0 && ChangedEquipmentVirtualTags.Count == 0;
public sealed record EquipmentDelta(EquipmentNode Previous, EquipmentNode Current);
public sealed record DriverDelta(DriverInstancePlan Previous, DriverInstancePlan Current);
public sealed record AlarmDelta(ScriptedAlarmPlan Previous, ScriptedAlarmPlan Current);
public sealed record GalaxyTagDelta(GalaxyTagPlan Previous, GalaxyTagPlan Current);
public sealed record EquipmentTagDelta(EquipmentTagPlan Previous, EquipmentTagPlan Current);
public sealed record EquipmentVirtualTagDelta(EquipmentVirtualTagPlan Previous, EquipmentVirtualTagPlan Current);
}
public static class Phase7Planner
@@ -95,6 +112,15 @@ public static class Phase7Planner
t => t.TagId,
(a, b) => new Phase7Plan.EquipmentTagDelta(a, b));
// VirtualTags diff by VirtualTagId, mirroring the EquipmentTags pass. Element equality on
// EquipmentVirtualTagPlan compares its scalar fields (Expression/DataType/Name/FolderPath/…)
// by value; DependencyRefs (an IReadOnlyList<string>) compares by reference, so a fresh list
// instance is conservatively treated as changed — fine for a rebuild trigger.
var (addedVTags, removedVTags, changedVTags) = DiffById(
previous.EquipmentVirtualTags, next.EquipmentVirtualTags,
t => t.VirtualTagId,
(a, b) => new Phase7Plan.EquipmentVirtualTagDelta(a, b));
return new Phase7Plan(
addedEq, removedEq, changedEq,
addedDrv, removedDrv, changedDrv,
@@ -104,6 +130,9 @@ public static class Phase7Planner
AddedEquipmentTags = addedEqTags,
RemovedEquipmentTags = removedEqTags,
ChangedEquipmentTags = changedEqTags,
AddedEquipmentVirtualTags = addedVTags,
RemovedEquipmentVirtualTags = removedVTags,
ChangedEquipmentVirtualTags = changedVTags,
};
}