Reads & Instrument Measurements
Registering what an instrument can measure
A ReadKind is either quantitative, with units, or qualitative, with string values that can be limited to a fixed set. @read registers one, as @attribute and @location_kind do for their kinds:
julia> @read Conductivity u"mS/cm"
ReadKind(Conductivity)
julia> Conductivity(1.2u"mS/cm")
1.2 mS cm⁻¹CHESS already includes the read kinds that its supported instruments produce, such as Absorbance. @read_str recalls a registered kind by name:
julia> read"Absorbance"(0.9u"OD")
0.9 ODRecording a read
record_read!(loc, read; instrument=nothing) appends a Read to a location's collection of reads. The example records reads on well A1 of a new plate:
julia> using Dates
julia> a1 = build_location(loc"WP96", "Plate 1")["A1"];
julia> record_read!(a1, read"Absorbance"(0.47u"OD", DateTime(2026,1,1,9,30)))
julia> record_read!(a1, read"Absorbance"(0.42u"OD", DateTime(2026,1,1,9,0)))
julia> reads(a1, read"Absorbance")
2-element Vector{Read}:
0.42 OD
0.47 ODA location holds one Attribute per kind, which a new value overwrites. Reads are never overwritten, so many reads of the same kind coexist. reads(x) returns them in the order they were recorded. Passing a kind or its name, as in reads(x, kind_or_name), returns only that kind, sorted by read_time, which gives a time series whatever the recording order.
Qualitative reads
A qualitative kind is either free text or limited to a fixed set of values:
julia> @read Observation
ReadKind(Observation)
julia> @read ColorimetricResult nothing Set(["Positive","Negative"])
ReadKind(ColorimetricResult)
julia> Observation("looked a bit cloudy")
looked a bit cloudy
julia> ColorimetricResult("Positive")
Positive
julia> ColorimetricResult("Maybe")
ERROR: ArgumentError: "Maybe" is not an allowed value for ColorimetricResult; allowed: Set(["Negative", "Positive"])is_quantitative and is_qualitative tell the two kinds apart, and read_unit returns the canonical unit of a quantitative read:
julia> is_quantitative(Conductivity), is_qualitative(Conductivity)
(true, false)
julia> is_quantitative(ColorimetricResult), is_qualitative(ColorimetricResult)
(false, true)
julia> read_unit(Conductivity(1.2u"mS/cm"))
mS cm⁻¹Unknown and missing
As with an Attribute (see Environmental Attributes & Inheritance), missing means that no reading was attempted or the location was out of scope. Unknown means that a reading was attempted but came back indeterminate, for example from a sensor fault or a saturation error:
julia> read"Absorbance"(Unknown)
Unknown
julia> read"Absorbance"(missing)
missingInstrument capability gating
CHESS has no separate instrument type. An instrument is a GenericLocation or Labware, whichever fits its physical shape, whose LocationKind is flagged is_instrument. The kind carries the capability data for the model: performable_operations, actuatable_attributes, and readable_types.
The optional instrument keyword of record_read! checks capability. With no instrument, nothing is checked. With an instrument, its performable_operations must include record_read!, or the call throws ArgumentError. CHESS's Epoch2 plate reader lists record_read!, and its Autoclave does not:
julia> reader = build_location(loc"Epoch2", "Reader 1");
julia> autoclave = build_location(loc"Autoclave", "Autoclave 1");
julia> record_read!(a1, read"Absorbance"(0.5u"OD"); instrument=reader)
julia> record_read!(a1, read"Absorbance"(0.5u"OD"); instrument=autoclave)
ERROR: ArgumentError: Autoclave 1 cannot perform record_read!is_capable reports whether a location, or a location kind, is capability-bearing. It does not depend on the type of the location. The plate reader is capable and a plate is not:
julia> is_capable(reader), is_capable(a1)
(true, false)The check asks only whether the instrument may perform record_read!. The readable_types field is descriptive and is not enforced, so a plate reader can record any registered ReadKind, not only the ones in its own readable_types.