refactor(mtconnect): delete dead parsed fields (R06:T-3)
MTConnectObservation.SampleRate was parsed per observation and never read; MTConnectTagDefinition.MtType/MtSubType/Units were carried (factory blob DTO, tags[] DTO, probe inference) and only MtCategory was ever read. Deleted. Stored configs and TagConfig blobs still carrying mtType/mtSubType/units keep loading (System.Text.Json skips unmapped members) - pinned by a new test. The AdminUI tag editor reads those keys straight off the blob and is unaffected. Refs #743 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -26,27 +26,17 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
|
||||
/// </param>
|
||||
/// <param name="MtCategory">
|
||||
/// The DataItem's MTConnect <c>category</c> attribute — <c>SAMPLE</c>, <c>EVENT</c>, or
|
||||
/// <c>CONDITION</c>. Optional metadata carried through for browse / display; not consulted by
|
||||
/// the value-mapping path. Default <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="MtType">
|
||||
/// The DataItem's MTConnect <c>type</c> attribute (e.g. <c>POSITION</c>, <c>EXECUTION</c>).
|
||||
/// Optional metadata. Default <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="MtSubType">
|
||||
/// The DataItem's MTConnect <c>subType</c> attribute (e.g. <c>ACTUAL</c>, <c>COMMANDED</c>).
|
||||
/// Optional metadata. Default <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="Units">
|
||||
/// The DataItem's MTConnect <c>units</c> attribute (e.g. <c>MILLIMETER</c>,
|
||||
/// <c>REVOLUTION/MINUTE</c>). Optional metadata. Default <c>null</c>.
|
||||
/// <c>CONDITION</c>. Read by the write path (a CONDITION is never writable). Default <c>null</c>.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// Review 2026-10-21 T-3: the <c>MtType</c> / <c>MtSubType</c> / <c>Units</c> members were carried here and
|
||||
/// read by nothing — deleted. A stored blob or <c>tags[]</c> entry that still carries <c>mtType</c> /
|
||||
/// <c>mtSubType</c> / <c>units</c> keeps loading (unknown JSON keys are skipped); the AdminUI tag editor reads
|
||||
/// those keys straight off the blob for its display and type-inference advisory, not through this record.
|
||||
/// </remarks>
|
||||
public sealed record MTConnectTagDefinition(
|
||||
string FullName,
|
||||
DriverDataType DriverDataType,
|
||||
bool IsArray = false,
|
||||
int ArrayDim = 0,
|
||||
string? MtCategory = null,
|
||||
string? MtType = null,
|
||||
string? MtSubType = null,
|
||||
string? Units = null);
|
||||
string? MtCategory = null);
|
||||
|
||||
+1
-10
@@ -87,10 +87,7 @@ public static class MTConnectTagDefinitionFactory
|
||||
parsed,
|
||||
blob.IsArray ?? false,
|
||||
blob.ArrayDim ?? 0,
|
||||
blob.MtCategory,
|
||||
blob.MtType,
|
||||
blob.MtSubType,
|
||||
blob.Units);
|
||||
blob.MtCategory);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -264,11 +261,5 @@ public static class MTConnectTagDefinitionFactory
|
||||
public int? ArrayDim { get; init; }
|
||||
/// <summary>The MTConnect DataItem category.</summary>
|
||||
public string? MtCategory { get; init; }
|
||||
/// <summary>The MTConnect DataItem type.</summary>
|
||||
public string? MtType { get; init; }
|
||||
/// <summary>The MTConnect DataItem sub-type.</summary>
|
||||
public string? MtSubType { get; init; }
|
||||
/// <summary>The engineering units, when authored.</summary>
|
||||
public string? Units { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3408,10 +3408,7 @@ public sealed partial class MTConnectDriver
|
||||
: ParseEnum<DriverDataType>(dto.DriverDataType, dto.FullName, driverInstanceId, nameof(MTConnectTagDefinition.DriverDataType)),
|
||||
dto.IsArray ?? false,
|
||||
dto.ArrayDim ?? 0,
|
||||
dto.MtCategory,
|
||||
dto.MtType,
|
||||
dto.MtSubType,
|
||||
dto.Units);
|
||||
dto.MtCategory);
|
||||
}
|
||||
|
||||
// ---- v3 tag binding: RawPath ↔ dataItemId, and the definitions the index coerces against ----
|
||||
@@ -3680,10 +3677,7 @@ public sealed partial class MTConnectDriver
|
||||
inferred.DataType,
|
||||
inferred.IsArray,
|
||||
(int)(inferred.ArrayDim ?? 0),
|
||||
dataItem.Category,
|
||||
dataItem.Type,
|
||||
dataItem.SubType,
|
||||
dataItem.Units);
|
||||
dataItem.Category);
|
||||
}
|
||||
|
||||
/// <summary>Flattens a device model into a <c>dataItemId → DataItem</c> lookup; null ⇒ empty.</summary>
|
||||
@@ -3953,12 +3947,9 @@ public sealed partial class MTConnectDriver
|
||||
public int? ArrayDim { get; init; }
|
||||
/// <summary>The MTConnect DataItem category.</summary>
|
||||
public string? MtCategory { get; init; }
|
||||
/// <summary>The MTConnect DataItem type.</summary>
|
||||
public string? MtType { get; init; }
|
||||
/// <summary>The MTConnect DataItem sub-type.</summary>
|
||||
public string? MtSubType { get; init; }
|
||||
/// <summary>The engineering units.</summary>
|
||||
public string? Units { get; init; }
|
||||
|
||||
// mtType / mtSubType / units were bound here and read by nothing (review 2026-10-21 T-3); a document that
|
||||
// still carries them parses — System.Text.Json skips unmapped members.
|
||||
}
|
||||
|
||||
/// <summary>Background connectivity-probe knobs (consumed by the probe loop).</summary>
|
||||
|
||||
@@ -162,11 +162,6 @@ public sealed record MTConnectStreamsResult(
|
||||
/// The observation's own <c>sampleCount</c> attribute — the number of values a <c>TIME_SERIES</c>
|
||||
/// vector claims to carry. The index checks the parsed vector against it. <c>null</c> when absent.
|
||||
/// </param>
|
||||
/// <param name="SampleRate">
|
||||
/// The observation's own <c>sampleRate</c> attribute (samples per second) on a <c>TIME_SERIES</c>
|
||||
/// observation. Carried for the parser contract; not yet surfaced on the served node (see
|
||||
/// docs/drivers/MTConnect.md). <c>null</c> when absent.
|
||||
/// </param>
|
||||
/// <param name="Entries">
|
||||
/// The parsed <c><Entry key="…"></c> children of a <c>DATA_SET</c> / <c>TABLE</c> observation, in
|
||||
/// document order, or <c>null</c> when the observation had none. A <c>TABLE</c> entry carries
|
||||
@@ -195,7 +190,6 @@ public sealed record MTConnectObservation(
|
||||
DateTime TimestampUtc,
|
||||
bool IsStructured = false,
|
||||
int? SampleCount = null,
|
||||
double? SampleRate = null,
|
||||
MTConnectConditionDetail? Condition = null,
|
||||
IReadOnlyList<MTConnectDataSetEntry>? Entries = null,
|
||||
bool ResetTriggered = false,
|
||||
|
||||
@@ -383,7 +383,6 @@ internal static class MTConnectStreamsParser
|
||||
var timestampUtc = default(DateTime);
|
||||
string? conditionState = null;
|
||||
int? sampleCount = null;
|
||||
double? sampleRate = null;
|
||||
var resetTriggered = false;
|
||||
var hasCount = false;
|
||||
List<MTConnectDataSetEntry>? entries = null;
|
||||
@@ -421,11 +420,6 @@ internal static class MTConnectStreamsParser
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var count) && count >= 0 ? count : null;
|
||||
sampleRate = double.TryParse(
|
||||
MTConnectXml.OptionalAttribute(reader, "sampleRate"),
|
||||
NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var rate) && double.IsFinite(rate) ? rate : null;
|
||||
}
|
||||
|
||||
// The slot is reserved BEFORE the subtree is read, and filled after: an observation is
|
||||
@@ -485,7 +479,6 @@ internal static class MTConnectStreamsParser
|
||||
// away a perfectly well-determined state.
|
||||
IsStructured: !isCondition && hasElements,
|
||||
SampleCount: sampleCount,
|
||||
SampleRate: sampleRate,
|
||||
Condition: condition,
|
||||
Entries: entries is { Count: > 0 } ? entries : null,
|
||||
|
||||
|
||||
+33
-14
@@ -95,14 +95,11 @@ public sealed class MTConnectDriverOptionsTests
|
||||
var pos = new MTConnectTagDefinition(
|
||||
FullName: "dev1_pos",
|
||||
DriverDataType: ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float64,
|
||||
MtCategory: "SAMPLE",
|
||||
MtType: "POSITION",
|
||||
Units: "MILLIMETER");
|
||||
MtCategory: "SAMPLE");
|
||||
var partCount = new MTConnectTagDefinition(
|
||||
FullName: "dev1_partcount",
|
||||
DriverDataType: ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Int64,
|
||||
MtCategory: "EVENT",
|
||||
MtType: "PART_COUNT");
|
||||
MtCategory: "EVENT");
|
||||
|
||||
var o = new MTConnectDriverOptions { AgentUri = "http://agent:5000", Tags = [pos, partCount] };
|
||||
|
||||
@@ -111,25 +108,47 @@ public sealed class MTConnectDriverOptionsTests
|
||||
o.Tags[1].ShouldBe(partCount);
|
||||
}
|
||||
|
||||
/// <summary>MTConnectTagDefinition round-trips its positional fields, including MTConnect metadata.</summary>
|
||||
/// <summary>MTConnectTagDefinition round-trips its positional fields, including the category the write path reads.</summary>
|
||||
[Fact]
|
||||
public void TagDefinition_carries_dataItemId_and_mtconnect_metadata()
|
||||
public void TagDefinition_carries_dataItemId_and_mtconnect_category()
|
||||
{
|
||||
var tag = new MTConnectTagDefinition(
|
||||
FullName: "dtop_2",
|
||||
DriverDataType: ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float64,
|
||||
MtCategory: "SAMPLE",
|
||||
MtType: "POSITION",
|
||||
MtSubType: "ACTUAL",
|
||||
Units: "MILLIMETER");
|
||||
MtCategory: "SAMPLE");
|
||||
|
||||
tag.FullName.ShouldBe("dtop_2");
|
||||
tag.DriverDataType.ShouldBe(ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float64);
|
||||
tag.IsArray.ShouldBeFalse();
|
||||
tag.ArrayDim.ShouldBe(0);
|
||||
tag.MtCategory.ShouldBe("SAMPLE");
|
||||
tag.MtType.ShouldBe("POSITION");
|
||||
tag.MtSubType.ShouldBe("ACTUAL");
|
||||
tag.Units.ShouldBe("MILLIMETER");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review 2026-10-21 T-3: mtType / mtSubType / units were deleted from the definition as dead, but a stored
|
||||
/// config that still carries them — in tags[] or in a raw tag's TagConfig blob — must keep loading.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Stored_configs_carrying_the_deleted_metadata_keys_still_load()
|
||||
{
|
||||
const string config = """
|
||||
{
|
||||
"agentUri": "http://agent:5000",
|
||||
"tags": [ { "fullName": "dev1_pos", "driverDataType": "Float64", "mtCategory": "SAMPLE",
|
||||
"mtType": "POSITION", "mtSubType": "ACTUAL", "units": "MILLIMETER" } ]
|
||||
}
|
||||
""";
|
||||
|
||||
var options = MTConnectDriver.ParseOptions("mt-t3", config);
|
||||
|
||||
options.Tags.ShouldHaveSingleItem().ShouldBe(new MTConnectTagDefinition(
|
||||
"dev1_pos", ZB.MOM.WW.OtOpcUa.Core.Abstractions.DriverDataType.Float64, MtCategory: "SAMPLE"));
|
||||
|
||||
MTConnectTagDefinitionFactory.TryFromTagConfig(
|
||||
"""{ "dataItemId": "dev1_pos", "dataType": "Float64", "mtCategory": "SAMPLE", "mtType": "POSITION", "mtSubType": "ACTUAL", "units": "MILLIMETER" }""",
|
||||
out var dataItemId, out var authored)
|
||||
.ShouldBeTrue();
|
||||
dataItemId.ShouldBe("dev1_pos");
|
||||
authored.ShouldNotBeNull().MtCategory.ShouldBe("SAMPLE");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -502,14 +502,13 @@ public sealed class MTConnectObservationIndexTests
|
||||
idx.Get("dev1_vibration_ts").StatusCode.ShouldBe(BadNotSupported);
|
||||
}
|
||||
|
||||
/// <summary>The parser carries the observation's own sampleCount / sampleRate attributes.</summary>
|
||||
/// <summary>The parser carries the observation's own sampleCount (sampleRate is not parsed — T-3: nothing read it).</summary>
|
||||
[Fact]
|
||||
public void Parser_carries_time_series_sampleCount_and_sampleRate()
|
||||
public void Parser_carries_time_series_sampleCount()
|
||||
{
|
||||
var obs = ParseFixture(CurrentFixture).Observations.Single(o => o.DataItemId == "dev1_vibration_ts");
|
||||
|
||||
obs.SampleCount.ShouldBe(10);
|
||||
obs.SampleRate.ShouldBe(100.0);
|
||||
ParseFixture(CurrentFixture).Observations.Single(o => o.DataItemId == "dev1_pos").SampleCount.ShouldBeNull();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user