fix(mtconnect): infer numeric EVENT types from the probe's UPPER_SNAKE spelling (Task 3 follow-up)

This commit is contained in:
Joseph Doherty
2026-07-24 13:57:13 -04:00
parent 3ca8ddf6f7
commit 79e30d1ead
2 changed files with 142 additions and 7 deletions
@@ -38,6 +38,67 @@ public sealed class MTConnectDataTypeInferenceTests
public void Infer_maps_the_v1_table(string cat, string type, string? units, string? rep, DriverDataType expected)
=> MTConnectDataTypeInference.Infer(cat, type, units, rep).DataType.ShouldBe(expected);
// ---------------------------------------------------------------------
// The SAME golden table, spelled the way a real agent's /probe document
// spells it. MTConnect writes the same concept two ways: the Devices
// (/probe) document's `DataItem@type` is UPPER_SNAKE (PART_COUNT), while
// the Streams (/current, /sample) document's observation element name is
// PascalCase (PartCount). `DiscoverAsync` feeds the *probe* spelling, so
// an inference that only knows the PascalCase spelling is green in tests
// and wrong live. Every row below is a DataItem from Fixtures/probe.xml.
// ---------------------------------------------------------------------
[Theory]
[InlineData("EVENT", "PART_COUNT", null, DriverDataType.Int64)] // dev1_partcount
[InlineData("SAMPLE", "POSITION", "MILLIMETER", DriverDataType.Float64)] // dev1_pos
[InlineData("EVENT", "EXECUTION", null, DriverDataType.String)] // dev1_execution
[InlineData("EVENT", "PROGRAM", null, DriverDataType.String)] // dev1_program
[InlineData("EVENT", "AVAILABILITY", null, DriverDataType.String)] // dev1_avail
[InlineData("CONDITION", "SYSTEM", null, DriverDataType.String)] // dev1_system_cond
public void Infer_maps_the_probe_documents_UPPER_SNAKE_spelling(
string cat, string type, string? units, DriverDataType expected)
=> MTConnectDataTypeInference.Infer(cat, type, units, null).DataType.ShouldBe(expected);
[Fact]
public void The_probes_TIME_SERIES_item_is_a_float64_array_of_its_declared_sample_count()
{
// dev1_vibration_ts: category=SAMPLE type=PATH_FEEDRATE representation=TIME_SERIES sampleCount=10.
var r = MTConnectDataTypeInference.Infer(
"SAMPLE", "PATH_FEEDRATE", "MILLIMETER/SECOND", "TIME_SERIES", sampleCount: 10);
r.DataType.ShouldBe(DriverDataType.Float64);
r.IsArray.ShouldBeTrue();
r.ArrayDim.ShouldBe(10u);
}
[Theory]
[InlineData("PART_COUNT", "PartCount")]
[InlineData("LINE_NUMBER", "LineNumber")]
[InlineData("part_count", "partcount")]
[InlineData("PART-COUNT", "PartCount")]
[InlineData("PART_COUNT", " Part Count ")]
public void The_two_document_spellings_of_one_type_infer_identically(string probeSpelling, string streamsSpelling)
{
// The equivalence is the whole point: a tag discovered from /probe and the same tag
// named from a /current observation must land on one type, or the authored config
// stops matching the value that arrives.
var fromProbe = MTConnectDataTypeInference.Infer("EVENT", probeSpelling, null, null);
var fromStreams = MTConnectDataTypeInference.Infer("EVENT", streamsSpelling, null, null);
fromProbe.ShouldBe(fromStreams);
fromProbe.DataType.ShouldBe(DriverDataType.Int64);
}
[Theory]
[InlineData("PART_COUNTER")]
[InlineData("PARTS_COUNT")]
[InlineData("LINE_NUMBERS")]
[InlineData("_")]
public void Separator_insensitivity_does_not_widen_the_numeric_EVENT_exception_list(string type)
// Falsifiability control: ignoring separators must not turn a *different* type into a
// match. The exception list stays exactly PartCount / Line / LineNumber.
=> MTConnectDataTypeInference.Infer("EVENT", type, null, null).DataType.ShouldBe(DriverDataType.String);
[Fact]
public void TimeSeries_sample_is_a_float64_array_with_declared_count()
{