Summary
For a numeric Quantity field with optional: true, omitting the field is accepted (HTTP 201) but sending it with null is rejected (HTTP 400 must be a number). Both forms are user intent for "no value here", and the asymmetry is undocumented and confusing.
Reproduction
Datastream with baro_altitude declared as Quantity with "optional": true.
T14: POST observations body={"result":{"geo_altitude":10500}}
→ HTTP 201 ✓ (omitted is OK)
T15: POST observations body={"result":{"baro_altitude":null,"geo_altitude":10500}}
→ HTTP 400 {"error":"... result.baro_altitude must be a number"}
Full transcript: docs/research/evidence/issue-004/nan-tests-head-2026-04-30.txt (cases T14, T15).
Source
In internal/api/observation_schema_validation.go, the datarecord branch only checks for field absence to short-circuit on optional: true. When the field is present with null, control reaches the quantity branch with value = nil, the float64 type assertion fails, and the error fires.
Proposed fix
In the datarecord (and vector / dataarray) branch, treat value == nil (a JSON null) as equivalent to absence when the field is optional: true:
fieldVal, present := record[field.Name]
if (!present || fieldVal == nil) {
if field.Optional != nil && *field.Optional { continue }
return fmt.Errorf("%s.%s is required by datastream schema", path, field.Name)
}
Priority
P3 — UX / consistency; not a security or data-loss bug, but it forces clients to special-case absent vs explicit-null in their result encoders. Same diagnostic pattern documented in #19 for phenomenonTime vs resultTime: two ways of expressing absence should converge.
Cross-refs
Summary
For a numeric Quantity field with
optional: true, omitting the field is accepted (HTTP 201) but sending it withnullis rejected (HTTP 400must be a number). Both forms are user intent for "no value here", and the asymmetry is undocumented and confusing.Reproduction
Datastream with
baro_altitudedeclared asQuantitywith"optional": true.Full transcript:
docs/research/evidence/issue-004/nan-tests-head-2026-04-30.txt(cases T14, T15).Source
In
internal/api/observation_schema_validation.go, thedatarecordbranch only checks for field absence to short-circuit onoptional: true. When the field is present withnull, control reaches thequantitybranch withvalue = nil, thefloat64type assertion fails, and the error fires.Proposed fix
In the
datarecord(andvector/dataarray) branch, treatvalue == nil(a JSONnull) as equivalent to absence when the field isoptional: true:Priority
P3 — UX / consistency; not a security or data-loss bug, but it forces clients to special-case absent vs explicit-null in their result encoders. Same diagnostic pattern documented in #19 for
phenomenonTimevsresultTime: two ways of expressing absence should converge.Cross-refs