fix(mtconnect): serve an empty string-typed value as Good "" (#824)
Owner decision 2026-11-11 (Java parity): a genuinely empty observation on a String/Reference or unauthored item is Good "" instead of BadNoCommunication. Non-string items (numeric, bool, DateTime, arrays) keep BadNoCommunication - the Agent supplied no value; BadTypeMismatch would wrongly blame the authored type. The UNAVAILABLE sentinel is unchanged, and a structured observation with no text stays BadNotSupported (structured check now precedes the empty check). Parity matrix, quality-mapping section and drivers rule updated. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -271,6 +271,18 @@ item. A `CONDITION` observation's value is taken from its **element name**
|
||||
condition element normalises onto the same `UNAVAILABLE` sentinel as every
|
||||
other category so one comparison covers all three.
|
||||
|
||||
**An empty value is not `UNAVAILABLE` (#824, owner decision 2026-11-11 — Java parity).** A
|
||||
genuinely empty observation (`<Program …></Program>`, `<Program …/>`, or whitespace-only text,
|
||||
which the parser trims to `""`) on a **string-typed** item (`String`/`Reference`, or an unauthored
|
||||
item, which coerces as String) is served **Good `""`** — an Agent can legitimately report a blank
|
||||
`Program`, `Block` or `Message`. On any **non-string** item (numeric, `Boolean`, `DateTime`, a
|
||||
`TIME_SERIES` array) `""` cannot be a value, so it stays **`BadNoCommunication`** with a null value
|
||||
— deliberately not `BadTypeMismatch`, which would tell the operator the Agent sent text of the wrong
|
||||
type and point them at the tag's authored data type; the Agent sent no value at all. Only the literal
|
||||
`UNAVAILABLE` sentinel is the "no value" statement on a string item. Before #824 every empty value
|
||||
was `BadNoCommunication`. A `DATA_SET`/`TABLE`-shaped observation is unaffected (its entry-less
|
||||
form is still Good `{}`; non-`<Entry>` children are still `BadNotSupported`).
|
||||
|
||||
**Integer-valued decimals are served on integral tags (#804).** Agents format integer-valued items as
|
||||
`42.000000`; an `Int*`/`UInt*` tag accepts any decimal whose value *is* an integer in range (`42.000000` → `42`,
|
||||
`1.5e3` → `1500`) losslessly. A real fraction stays `BadOutOfRange` — deliberately stricter than the Java
|
||||
@@ -296,7 +308,7 @@ limitations 4; a probe that does declare one yields a fixed `ArrayDim`).
|
||||
Parsing: the observation text is split on any whitespace, each token parsed
|
||||
invariant-culture. The result is **all-or-nothing** — one non-numeric /
|
||||
non-finite token is `BadTypeMismatch`, a literal `UNAVAILABLE` token or an
|
||||
empty body is `BadNoCommunication`, and a count that disagrees with the
|
||||
empty body is `BadNoCommunication` (an array is non-string, so #824's Good `""` never applies), and a count that disagrees with the
|
||||
observation's own `sampleCount` attribute (or a fixed `ArrayDim`) is
|
||||
`BadDecodingError`. A partial vector is never served as Good. Only a
|
||||
**Float64** array tag is served; any other declared element type stays
|
||||
@@ -677,7 +689,7 @@ deliberately different, kept.
|
||||
| Type inference SAMPLE→Double, numeric EVENT→Int64, else String (`MTConnectTypes`) | covered, **.NET exceeds** (TIME_SERIES → `double[]`, separator-insensitive types) | `MTConnectDataTypeInference` · `MTConnectDataTypeInferenceTests` |
|
||||
| UNAVAILABLE → `Bad_NoCommunication`, null value, source timestamp kept (`MTConnectValues`) | covered | `MTConnectObservationIndex.BuildSnapshot` · `Unavailable_observation_maps_to_BadNoCommunication_with_null_value`, `Unavailable_observation_keeps_the_agent_source_timestamp` |
|
||||
| UNAVAILABLE matched case-insensitively | divergence (kept) | ordinal match on the parser-normalised token, so a free-text EVENT reading "Unavailable" stays a value · `Unavailable_sentinel_is_matched_on_the_exact_literal` |
|
||||
| Blank text on a String node is a Good empty string | divergence (kept) | empty text is `BadNoCommunication` (an empty element is not an empty-string value) |
|
||||
| Blank text on a String node is a Good empty string | **covered** (was a divergence until #824, owner decision 2026-11-11) | `BuildSnapshot` serves Good `""` on a string-typed or unauthored item; a non-string item stays `BadNoCommunication` (Java has no typed node to refuse `""` on); `UNAVAILABLE` unchanged · `Empty_observation_value_on_a_string_item_is_a_Good_empty_string`, `A_parsed_empty_event_on_a_string_item_is_a_Good_empty_string`, controls `Empty_observation_value_on_a_non_string_item_stays_BadNoCommunication`, `Unavailable_on_a_string_item_is_still_BadNoCommunication` |
|
||||
| No fabricated numbers: narrow-integer range checks, float overflow ≠ Infinity, NaN/Infinity refused (`MTConnectValues.coerce`) | covered for integers; **gap → closed** for floats (`1e400`, `NaN`, `Infinity`, `1e39` on Float32 were served Good) | `Coerce`, `NonFiniteStatus` · `A_non_finite_float_is_never_served_as_good` |
|
||||
| Integer-valued decimal (`42.000000`) on an integral node | **gap → closed** (was `BadOutOfRange`) — lossless only; a real fraction is still refused, never truncated (Java truncates) | `TryIntegral` · `An_integer_valued_decimal_converts_losslessly`, control `A_fraction_or_an_out_of_range_decimal_stays_out_of_range` |
|
||||
| Source timestamps preserved; a non-Zulu offset normalised | covered | `ReadTimestampUtc` · `A_timestamp_with_a_non_zulu_offset_is_normalized_to_the_same_utc_instant` |
|
||||
@@ -932,7 +944,7 @@ for anything beyond values-and-conditions.
|
||||
- **Unit tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Tests/`
|
||||
(canned-XML fixtures, no network) — 714/714 at #804 (2026-11-10). `MTConnectJavaParityTests` holds the #804 gap tests and the ported Java regression cases (see [Java reference parity](#java-reference-parity-804)). Covers
|
||||
discovery tree shape, `MTConnectDataTypeInference`, observation indexing,
|
||||
`UNAVAILABLE` → `BadNoCommunication`, CONDITION state-word mapping,
|
||||
`UNAVAILABLE` → `BadNoCommunication` (an empty string-typed value is Good `""`, #824), CONDITION state-word mapping,
|
||||
multipart chunk framing, and ring-buffer re-baseline paging (both the
|
||||
sequence-gap and the `OUT_OF_RANGE` legs). The write path adds
|
||||
`MTConnectAgentClientWriteTests` (`9f17d7ef` — verb, URL, agent-error-in-200
|
||||
|
||||
Reference in New Issue
Block a user