feat(mtconnect): serve DATA_SET/TABLE as compact JSON strings; park EVENT enumerations (#650)

Parser captures Entry/Cell content; the observation index merges /sample deltas
into the /current baseline (resetTriggered replaces, removed deletes, UNAVAILABLE
drops). Fixture gains a TABLE item and periodic DATA_SET/TABLE churn.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 19:15:32 -04:00
parent 8801631739
commit f8f8eb9fb8
11 changed files with 537 additions and 39 deletions
@@ -167,6 +167,17 @@ public sealed record MTConnectStreamsResult(
/// 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>&lt;Entry key="…"&gt;</c> children of a <c>DATA_SET</c> / <c>TABLE</c> observation (#650), in
/// document order, or <c>null</c> when the observation had none. A <c>TABLE</c> entry carries
/// <see cref="MTConnectDataSetEntry.Cells"/>. The observation index folds these into a JSON String — see
/// <see cref="MTConnectObservationIndex"/>. <see cref="IsStructured"/> stays <c>true</c> beside them: it is the
/// wire-shape fact, this is the content.
/// </param>
/// <param name="ResetTriggered">
/// <c>true</c> when the observation carried a non-empty <c>resetTriggered</c> attribute — the Agent's statement that
/// the set was reset, so this observation <b>replaces</b> the accumulated set rather than merging into it.
/// </param>
/// <param name="Condition">
/// The CONDITION-only detail (#626) — set exactly when the observation came from a
/// <c>&lt;Condition&gt;</c> container, <c>null</c> for every Sample/Event. Its presence is what marks an
@@ -179,7 +190,25 @@ public sealed record MTConnectObservation(
bool IsStructured = false,
int? SampleCount = null,
double? SampleRate = null,
MTConnectConditionDetail? Condition = null);
MTConnectConditionDetail? Condition = null,
IReadOnlyList<MTConnectDataSetEntry>? Entries = null,
bool ResetTriggered = false);
/// <summary>One <c>&lt;Cell key="…"&gt;</c> of a <c>TABLE</c> row (#650).</summary>
public sealed record MTConnectDataSetCell(string Key, string Value);
/// <summary>
/// One <c>&lt;Entry key="…"&gt;</c> of a <c>DATA_SET</c> / <c>TABLE</c> observation (#650).
/// </summary>
/// <param name="Key">The entry's <c>key</c> attribute, verbatim.</param>
/// <param name="Value">A <c>DATA_SET</c> entry's trimmed text; <c>null</c> for a <c>TABLE</c> row or a removed entry.</param>
/// <param name="Removed"><c>true</c> when the entry carried <c>removed="true"</c>: the key is deleted from the set.</param>
/// <param name="Cells">A <c>TABLE</c> row's cells in document order; <c>null</c> for a <c>DATA_SET</c> entry.</param>
public sealed record MTConnectDataSetEntry(
string Key,
string? Value,
bool Removed,
IReadOnlyList<MTConnectDataSetCell>? Cells = null);
/// <summary>
/// What an MTConnect CONDITION observation says beyond its state word (#626). Every field is the