feat(v3-batch4-wp1): composer/planner emit both Raw + UNS subtrees
Un-darken the address-space composition for the v3 dual namespace. Composer (AddressSpaceComposer): - Raw subtree: RawContainerNode (Folder/Driver/Device/TagGroup as Object/Folder nodes, keyed s=<RawPath>, parent-before-child) + RawTagPlan (raw tag Variables keyed (realm=Raw, s=<RawPath>) carrying DataType / writable / historize+historian tagname / array shape). Native-alarm intent attaches at the RAW tag (ConditionId = RawPath) with the list of referencing equipment UNS paths (one alarm at the raw tag, not one per equipment). - UNS subtree: UnsReferenceVariable per UnsTagReference, keyed (realm=Uns, s=<Area>/<Line>/<Equipment>/<EffectiveName>), carrying its backing RawPath (Organizes UNS->Raw target + fan-out) and inheriting DataType/AccessLevel from the backing raw tag. Effective name = DisplayNameOverride else backing raw Tag.Name. - All RawPaths flow through one shared RawTopology (RawPathResolver + memoised container paths), byte-parity with EquipmentReferenceMap. - Every new node carries an AddressSpaceRealm (so WP3's applier picks the namespace at the call site). Additive/defaulted model changes only — the un-migrated AddressSpaceApplier still compiles. Planner (AddressSpacePlanner.Compute) + AddressSpacePlan: - Per-realm diff sets: RawContainers + RawTags keyed by RawPath (NodeId) so a rename = remove+add; UnsReferenceVariables keyed by the stable UnsTagReferenceId so a backing-tag rename re-points (BackingRawPath moves, NodeId stable) and a display-name-override edit is UNS-only. - PINNED: raw-tag rename = remove+add in Raw AND re-point in UNS. Classifier folds the new sets in (adds->PureAdd, removes->PureRemove, changed->Rebuild safe default). Tests: AddressSpaceComposerDualNamespaceTests (7), AddressSpacePlannerDualNamespaceTests (8, incl. the rename pin), AddressSpaceChangeClassifierDualNamespaceTests (8); reactivated EquipmentNamespaceMaterializationTests' Batch-4-pending case, re-authored to drive the composer over the seeded config (the artifact-decode seam is WP3). Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// v3 Batch 4 (B4-WP1) — the classifier folds the Raw + UNS subtree diff sets into its routing policy:
|
||||
/// raw/uns adds count as adds (PureAdd), removes as removes (PureRemove), and any Changed raw/uns delta
|
||||
/// routes to Rebuild (the safe default — WP3's applier owns surgical refinement).
|
||||
/// </summary>
|
||||
public sealed class AddressSpaceChangeClassifierDualNamespaceTests
|
||||
{
|
||||
private static readonly RawContainerNode Folder = new("Plant", null, "Plant", RawNodeKind.Folder);
|
||||
|
||||
private static RawTagPlan RawTag(string nodeId) =>
|
||||
new("t-1", nodeId, "Plant/Modbus1/PLC-A", "drv-1", "Speed", "Float",
|
||||
Writable: false, Alarm: null, ReferencingEquipmentPaths: Array.Empty<string>());
|
||||
|
||||
private static UnsReferenceVariable UnsRef(string nodeId) =>
|
||||
new("ref-1", "eq-1", nodeId, "Speed", "Plant/Modbus1/PLC-A/Speed", "Float", Writable: false);
|
||||
|
||||
private static AddressSpacePlan Empty() => new(
|
||||
Array.Empty<EquipmentNode>(), Array.Empty<EquipmentNode>(), Array.Empty<AddressSpacePlan.EquipmentDelta>(),
|
||||
Array.Empty<DriverInstancePlan>(), Array.Empty<DriverInstancePlan>(), Array.Empty<AddressSpacePlan.DriverDelta>(),
|
||||
Array.Empty<ScriptedAlarmPlan>(), Array.Empty<ScriptedAlarmPlan>(), Array.Empty<AddressSpacePlan.AlarmDelta>());
|
||||
|
||||
[Fact]
|
||||
public void Added_raw_tag_only_is_PureAdd()
|
||||
{
|
||||
var plan = Empty() with { AddedRawTags = new[] { RawTag("Plant/Modbus1/PLC-A/Speed") } };
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.PureAdd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Added_raw_container_only_is_PureAdd()
|
||||
{
|
||||
var plan = Empty() with { AddedRawContainers = new[] { Folder } };
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.PureAdd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Added_uns_reference_only_is_PureAdd()
|
||||
{
|
||||
var plan = Empty() with { AddedUnsReferenceVariables = new[] { UnsRef("filling/line-1/station-1/Speed") } };
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.PureAdd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Removed_raw_tag_only_is_PureRemove()
|
||||
{
|
||||
var plan = Empty() with { RemovedRawTags = new[] { RawTag("Plant/Modbus1/PLC-A/Speed") } };
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.PureRemove);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Removed_uns_reference_only_is_PureRemove()
|
||||
{
|
||||
var plan = Empty() with { RemovedUnsReferenceVariables = new[] { UnsRef("filling/line-1/station-1/Speed") } };
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.PureRemove);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Raw_rename_add_and_remove_is_AddRemoveMix()
|
||||
{
|
||||
var plan = Empty() with
|
||||
{
|
||||
RemovedRawTags = new[] { RawTag("Plant/Modbus1/PLC-A/Speed") },
|
||||
AddedRawTags = new[] { RawTag("Plant/Modbus1/PLC-A/Velocity") },
|
||||
};
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.AddRemoveMix);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Changed_raw_tag_is_Rebuild()
|
||||
{
|
||||
var plan = Empty() with
|
||||
{
|
||||
ChangedRawTags = new[]
|
||||
{
|
||||
new AddressSpacePlan.RawTagDelta(
|
||||
RawTag("Plant/Modbus1/PLC-A/Speed"),
|
||||
RawTag("Plant/Modbus1/PLC-A/Speed") with { IsHistorized = true }),
|
||||
},
|
||||
};
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.Rebuild);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Uns_reference_repoint_is_Rebuild()
|
||||
{
|
||||
var plan = Empty() with
|
||||
{
|
||||
ChangedUnsReferenceVariables = new[]
|
||||
{
|
||||
new AddressSpacePlan.UnsReferenceDelta(
|
||||
UnsRef("filling/line-1/station-1/Speed"),
|
||||
UnsRef("filling/line-1/station-1/Speed") with { BackingRawPath = "Plant/Modbus1/PLC-A/Velocity" }),
|
||||
},
|
||||
};
|
||||
AddressSpaceChangeClassifier.Classify(plan).ShouldBe(AddressSpaceChangeKind.Rebuild);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user