Locations & Operations

Location types, LocationKinds, occupancy, and the operations that change where things are and what state they are in. See the Locations and Movement & Occupancy manual chapters.

CHESSCore.location_kinds — Constant
const location_kinds::Dict{Symbol,LocationKind}

Registry of every LocationKind defined with @location_kind, keyed by name. Kinds registered in any module are also added here, so this one table covers the whole session. Look up a single kind with @loc_str (loc"WP96") rather than indexing this directly.

source
CHESSCore.LocationKind — Type
struct LocationKind

LocationKind is a named, interned, immutable value describing a kind of location or labware (e.g. :Room, :Incubator, :WP96, :AltemisBox, :Well200). It replaces minting a new concrete Julia type per kind — every Location/Labware/Well instance carries a reference to a shared LocationKind value rather than duplicating its descriptive data.

Fields:

  • name: the kind's unique identifier.
  • categories: flat, ordered tags used for occupancy-rule resolution (no chaining/hierarchy).
  • shape: grid shape (rows, cols) if this kind is Labware-like; nothing otherwise.
  • socket: the LocationKind name filling each grid slot, if shape is set.
  • capacity: well volume capacity if this kind is Well-like; nothing otherwise.
  • vendor/catalog: descriptive product data, if applicable.
  • default_parent_cost/default_child_cost: fallback occupancy costs used when no specific or category-based occupancy_cost rule applies.
  • actuatable_attributes: names of AttributeKinds this kind is associated with as an instrument – descriptive only, not enforced; empty for non-instrument kinds.
  • performable_operations: mutating operation functions (e.g. move_into!, transfer!, set_attribute!) this kind's instrument can perform on other locations; empty for non-instrument kinds.
  • readable_types: names of ReadKinds this kind's instrument can produce; empty for non-instrument kinds.
  • is_instrument: whether this kind is capability-bearing (see is_capable) – independent of its structural concretetype, which is decided purely by capacity/shape. A kind can be both capability-bearing and grid-shaped (e.g. a plate reader with a physical deck), in which case it concretizes as Labware with capability data layered on top.

See also: concretetype, @location_kind, @loc_str

source
CHESSCore.concretetype — Method
concretetype(k::LocationKind)

Return the concrete Julia type (Well, Labware, or GenericLocation) that a LocationKind produces: Well if capacity is set, Labware if shape is set, GenericLocation otherwise. Purely structural – whether k is capability-bearing (is_instrument/is_capable) is an orthogonal fact, checkable on any concrete Location, not a 4th type.

source
CHESSCore.is_capable — Method
is_capable(k::LocationKind)
is_capable(x::Location)

Return whether k/x is capability-bearing – i.e. can perform operations on other locations (see performable_operations) and/or produce reads (see readable_types), as opposed to a plain location whose state only ever changes via direct calls, never its own action. Independent of concretetype: a capability-bearing kind can be grid-shaped (Labware) or not (GenericLocation) alike.

source
CHESSCore.@loc_str — Macro
@loc_str(kind)

String macro to recall a LocationKind registered with @location_kind by name, mirroring @spc_str/@org_str (and sharing their lookup machinery).

loc"WP96" is a pure lookup — it never allocates a new physical instance or touches the database. Actual instantiation stays an explicit call, e.g. generate_location(loc"WP96", "Plate 1").

Example:

julia> loc"WP96"
LocationKind(WP96)
source
CHESSCore.@location_kind — Macro
@location_kind labsymb categories shape socket capacity vendor catalog [default_parent_cost] [default_child_cost]

Define a new LocationKind and register it under labsymb, both as a const binding (so it can be used directly, e.g. WP96) and in the location_kinds registry (so it can be looked up by name, e.g. for database reconstruction).

Example:

julia> @location_kind WP96 [:Plate] (8,12) :Well200 nothing "Thermo" "123456"
WP96
source
CHESSCore.GenericLocation — Type
mutable struct GenericLocation <: Location

The concrete Location subtype for locations with mutable, heterogeneous membership over time (rooms, incubators, drawers, ...) — as opposed to Labware (fixed grid) or Well (terminal leaf). Every distinct kind (:Room, :Incubator, ...) is a LocationKind value carried in kind, not a distinct Julia type.

source
CHESSCore.Location — Type
abstract type Location end

Locations represent the physical objects that make up the lab. Location objects use the AbstractTrees.jl interface to construct relational hierarchies.

The concrete subtypes are GenericLocation (mutable membership over time), Labware (fixed, permanent grid membership), and Well (terminal leaf holding a Stock). Every distinct kind of location/labware (:Room, :Incubator, :WP96, ...) is data (a LocationKind), not a distinct Julia type — see @location_kind.

source
Base.getindex — Method
getindex(loc::Location,name::String)

Look up the unique direct child of loc named name. Throws ChildNotFoundError if no child matches, or AmbiguousChildNameError if more than one does — name is not guaranteed unique (see children_named for the non-throwing, plural alternative). There is deliberately no integer getindex for a generic Location: unlike Labware's fixed grid, children is a mutable Vector with no stable positional identity.

source
Base.parent — Method
parent(x::Location)

The location directly containing x, or nothing if x is at the root of its tree.

source
CHESSCore.actuatable_attributes — Method
actuatable_attributes(x::Location)

Return the Set{Symbol} of AttributeKind names x is associated with — descriptive data carried on x's LocationKind, not enforced anywhere (reserved for a possible future planning/scheduling feature). Empty for a non-capable location.

source
CHESSCore.ancestors — Method
ancestors(x::Location;rev=false)

return the parent location chain of location x in the order of most to least proximal.

Use the keyword arg rev=true to reverse the order from least proximal to most proximal

source
CHESSCore.children_named — Method
children_named(loc::Location,name::String)

Return every direct child of loc whose name matches, as a Vector (possibly empty). name(x) is explicitly documented as non-unique, so unlike Base.getindex(::Location,::String) this never errors — use it when duplicate names are expected and you want to handle them yourself.

source
CHESSCore.get_all_within — Method
get_all_within(loc::Location,typ::Type{<:Location})

Find and return all locations of type typ within loc or loc's children

source
CHESSCore.is_committed — Method
is_committed(x::Location)

Return true if x has a location_id — it has been committed to a database and is tracked there. Most Locations never need to be: this is the exceptional case, not the default one.

source
CHESSCore.is_locked — Method
is_locked(x::Location)

Access the state of the is_locked property of a location. Locked locations cannot be moved from their current parent, but children of locked locations can be moved.

See also: unlock!,lock!,toggle_lock!.

source
CHESSCore.location_id — Method
location_id(x::Location)

Access the location_id property of a location. nothing by default – every Location is a normal, complete in-memory object with or without one; a location_id only appears once x has been committed to a database (see is_committed).

source
CHESSCore.lock! — Method
lock!(x::Location;instrument::Union{Location,Nothing}=nothing)

Change the state of the is_locked property of a location to true.

See also: is_locked. To preview this without mutating x, see build_location (or CHESSDatabase's reconstruct_location).

source
CHESSCore.name — Method
name(x::Location)

Access the name property of a location. The name of a location does not need to be unique and can be used for display purposes.

source
CHESSCore.performable_operations — Method
performable_operations(x::Location)

Return the Set{Function} of operation functions (e.g. move_into!, transfer!, set_attribute!) that x can perform on other locations — data carried on x's LocationKind. Empty for a non-capable location.

source
CHESSCore.reads — Method
reads(x::Location, kind::ReadKind)
reads(x::Location, name::Symbol)

Return x's reads of a single ReadKind (by the kind itself, or by its name), sorted by read_time (reads with no recorded time sort first) – directly usable as a time series, regardless of the underlying collection's insertion order.

source
CHESSCore.reads — Method
reads(x::Location)

Access the full reads collection of a location – every Read ever recorded for it (see record_read!), in whatever order they were recorded. Unlike attributes, this is never overwritten – many independent reads of the same ReadKind can coexist.

source
CHESSCore.toggle_lock! — Method
toggle_lock!(x::Location;instrument::Union{Location,Nothing}=nothing)

Flip the state of the is_locked property of a location.

See also: is_locked. To preview this without mutating x, see build_location (or CHESSDatabase's reconstruct_location).

source
CHESSCore.unlock! — Method
unlock!(x::Location;instrument::Union{Location,Nothing}=nothing)

Change the state of the is_locked property of a location to false.

See also: is_locked. To preview this without mutating x, see build_location (or CHESSDatabase's reconstruct_location).

source
CHESSCore.occupancy — Method
occupancy(x::Location)

return the fractional occupancy of location x.

Occupancies can range from 0 (empty) to 1 (fully occupied).

The occupancy is calculated by summing the occupancy_cost of each child in the location.

source
CHESSCore.occupancy_cost — Method
occupancy_cost(parent::Location,child::Location)

Returns a Rational representing the fractional occupancy of child in parent.

Resolution precedence: an exact (parent kind, child kind) rule (see set_occupancy_cost!) wins outright; otherwise, a single matching category-based rule applies; if more than one category-based rule matches with no exact rule to disambiguate, throws AmbiguousOccupancyRuleError; otherwise falls back to max(parent_cost(parent),child_cost(child)).

source
CHESSCore.set_occupancy_cost! — Method
set_occupancy_cost!(parent_kind::Symbol,child_kind::Symbol,cost::Rational)

Register an occupancy-cost rule for a (parent_kind,child_kind) pair (or category pair — any of either kind's LocationKind categories). An exact kind-to-kind rule always takes precedence over a category-based one; if more than one category-based rule matches with no exact rule to break the tie, occupancy_cost throws AmbiguousOccupancyRuleError.

source
CHESSCore.Labware — Type
mutable struct Labware <: Location

The concrete Location subtype for a manufactured physical item with a fixed, homogeneous grid of slots (kind.shape), populated once at creation by build_location and never restructured afterward — add_to!/remove! on a Labware throw FixedMembershipError. Every distinct model (:WP96, :AltemisBox, ...) is a LocationKind value carried in kind, not a distinct Julia type.

source
CHESSCore.occupancy — Method
occupancy(x::Labware)

Always 1//1 – a Labware's children (kind.shape wells) are populated exactly once at construction and can never change (add_to!/remove! throw FixedMembershipError), so there is no "how full" question to answer the way there is for GenericLocation/Instrument. Mirrors occupancy(::Well) (Well.jl), which is fully-occupied for the same reason (fixed contents, no partial-membership state).

source
CHESSCore.Well — Type
mutable struct Well <: Location

Wells are special Location subtypes that contain a single Stock object.

A well's LocationKind (kind) determines its volume capacity in kind.capacity. kind is a required, explicit constructor argument (not derived from the parent), so a well's capacity is known immediately at construction, whether or not it has a parent yet.

Wells are only allowed to be located in Labware objects and cannot be moved from the labware — they are physically fused to it.

source
Base.empty! — Method
empty!(x::Well)

Remove the stock contained in a well by setting it to Empty()

See also: Empty. To preview this without mutating x, see build_location (or CHESSDatabase's reconstruct_location).

source
CHESSCore.deposit! — Function
deposit!(recipient::Well, stock::Stock, cost::Real=0)

Add stock into recipient's current Stock, guarded by wellcapacity (throws WellCapacityError if the combined stock would exceed it). cost is a plain tracked number (e.g. a reagent cost) added to the well's running cost, apportioned proportionally whenever withdraw! pulls material back out. Defaults to 0 for deposits that don't track a cost.

See also: withdraw!, transfer!.

source
CHESSCore.withdraw! — Method
withdraw!(donor::Well, quant::Union{Unitful.Volume,Unitful.Mass}) -> (Stock, Real)

Remove quant of donor's stock and return it along with its share of the well's cost.

The withdrawn Stock is a proportional slice of the well's contents: withdrawing a quarter of the total quantity takes a quarter of every reagent and organism. The well's cost is reduced by the same fraction, and that amount is returned as the second value. quant must have the same dimension (volume or mass) as the well's total quantity, otherwise an error is thrown.

See also: deposit!, transfer!.

source
CHESSCore._attributes_summary — Method
_attributes_summary(io::IO,x::Location)

Print x's environment (own attributes overriding inherited ones, via environment) as a table, with an Own/inherited column distinguishing locally-set attributes from inherited ones. Prints nothing if the environment is empty.

source
CHESSCore._capabilities_summary — Method
_capabilities_summary(io::IO,x::Location)

Print x's capability data (performable operations, actuatable attributes, readable types) if is_capable(x) – independent of x's concrete Location subtype. Prints nothing otherwise.

source
CHESSCore._children_summary — Method
_children_summary(io::IO,x::Location)

Print a summary (count, occupancy, breakdown by child kind) of x's children – never enumerates every child. Prints nothing if x has no children.

source
CHESSCore._present_children — Method
_present_children(x::Location)

Return x's children as a plain Vector{Location}, skipping any unassigned slots. GenericLocation stores children in a Vector (always fully assigned); Labware stores them in a Matrix that can contain unassigned slots before every socket is populated.

source
CHESSCore._reads_summary — Method
_reads_summary(io::IO,x::Location;nshow::Integer=3)

Print x's reads grouped by ReadKind, most recent nshow per kind (with a "+N more" tail note if truncated). Prints nothing if x has no reads.

source
CHESSCore._build_location — Method
_build_location(kind::LocationKind,name::String,next_id::Function,child_namer::Vararg{Function})

Recursively build a Location/Labware/Well object graph for kind, obtaining each node's location_id from next_id(name,kind). This is the shared, pure (no database calls) graph-construction logic behind both generate_location (real, DB-backed, in CHESSDatabase) and build_location (no location_id, in-memory only).

source
CHESSCore.build_location — Function
build_location(kind::LocationKind,name::String=string(UUIDs.uuid4()),child_namer::Vararg{Function}=plate_namer)

Build a kind Location entirely in memory — no database calls are made, and location_id is nothing (see is_committed). This is the default, ordinary way to get a Location: a plain in-memory Julia object, nothing exceptional about it. Useful for reasoning about hypothetical arrangements without minting real database rows, and it's the building block CHESSCore's own tests and fixtures use throughout. A location built this way cannot be passed to upload/cache (CHESSDatabase) until it's committed — see UncommittedLocationError. To commit a subtree to a database, see commit_location! (CHESSDatabase); to strip a committed location back down to an uncommitted one (e.g. to merge subtrees from separate databases), see release_location (CHESSDatabase).

Previewing hypothetical arrangements: since nothing here is ever committed unless you choose to, this is already the preview mechanism for anything that doesn't exist yet — build it, mutate it freely (move_into!, drain!, lock!, ...), inspect it, and simply never call upload/cache if you decide not to keep it. There's no "restore" step, because nothing was ever committed. For previewing a change to an already-committed location instead, see reconstruct_location (CHESSDatabase).

See also: generate_location, reconstruct_location, commit_location!, release_location (all CHESSDatabase).

source
CHESSCore.plate_namer — Method
plate_namer(row,col)

Return the microplate standard name for a row and col coordinate

Ex. platenamer(1,1) = "A1" , platenamer(8,12) = "H12"

source
CHESSCore.plot_well_heatmap! — Function
plot_well_heatmap!(plt, l::Labware; kwargs...)

Overlay a heatmap of each well's stock quantity (in µL; Empty wells render as zero) onto plt.

Extension point: the actual implementation lives in CHESSCoreLabwarePlottingExt, loaded automatically once both LabwarePlotting and Plots are also using'd. CHESSCore itself has no hard Plots dependency.

source
CHESSCore._check_capability — Method
_check_capability(instrument::Union{Location,Nothing}, fun::Function)

Shared gate for the four operations whose CHESS Database tables carry an InstrumentID (move_into!/transfer!/set_attribute!/record_read!): if instrument is given, it must have fun in its performable_operations, or this throws ArgumentError. A no-op if instrument is nothing (the default everywhere it's used) – this never mutates instrument and never changes the mutation logic of the operation itself, it only validates.

Checks only the coarse "can this instrument perform this operation at all" axis – actuatable_attributes/readable_types (the finer, per-argument axes) are deliberately not consulted here; they remain descriptive LocationKind data for a possible future planning/scheduling feature.

source
CHESSCore.add_to! — Method
add_to!(parent::Labware,child::Location)
add_to!(parent::Well,child::Location)

Labware slots are fixed at creation (populated by build_location) and Wells never have children — both throw FixedMembershipError rather than allowing move_into! to mutate their (structurally fixed) membership.

source
CHESSCore.can_move_into — Method
can_move_into(parent::Location,child::Location)

returns true if child can be moved into parent, throw an error if not

there are four errors that prevent movement

  1. LockedLocationError: child is locked in its current parent.
  2. AlreadyLocatedInError: child is already located in parent
  3. OccupancyError: parent has occupancy constraints that prevent it from containing child. This could happen if parent is full or if child is never meant to be able to fit inside parent.
  4. FixedMembershipError: child is a Well – wells are physically fused to their labware and can never be independently relocated, regardless of any occupancy rule (see the can_move_into(::Location,child::Well) method below).

See also: move_into!

source
CHESSCore.can_move_into — Method
can_move_into(::Location,child::Well)

A Well is physically fused to its Labware and can never be independently relocated – this always throws FixedMembershipError, regardless of any set_occupancy_cost! rule that might otherwise apply to the (parent kind, child kind) pair. Dispatches ahead of the generic can_move_into(::Location,::Location) for any Well child.

source
CHESSCore.move_into! — Function
move_into!(parent::Location,child::Location,lock::Bool=false;instrument=nothing)

Move child into parent, if allowed.

if lock=true, then also lock the child after the movement. See _check_capability for instrument.

source
CHESSCore.remove! — Method
remove!(parent::Labware,child::Location)
remove!(parent::Well,child::Location)

Mirrors add_to!'s Labware/Well overloads: slots are fixed at creation, so removing a child is just as structurally invalid as adding one – both throw FixedMembershipError.

source
CHESSCore.transfer! — Function
transfer!(donor::Well,recipient::Well,quantity::Union{Unitful.Volume,Unitful.Mass},configuration::String="";instrument=nothing)

Remove quantity from donor and move it to recipient

transfer! mutates the donor and recipient in place. To preview this without mutating either well, see build_location (or CHESSDatabase's reconstruct_location). See _check_capability for instrument.

source
CHESSCore.set_attribute! — Method
set_attribute!(x::Location,attribute::Attribute; instrument=nothing)

Set the value for a location's environmental attributes to attribute.

We use this method to ensure a proper pairing between the attribute type and the attribute in the dict. To preview this without mutating x, see build_location (or CHESSDatabase's reconstruct_location). See _check_capability for instrument.

source
CHESSCore.observe! — Method
observe!(well::Well, component::StockComponent, quant; instrument=nothing)
observe!(loc::Location, attribute::Attribute; instrument=nothing)
observe!(child::Location, parent::Union{Location,Nothing}; instrument=nothing)
observe!(loc::Location, facet::Symbol, value; instrument=nothing)

Declare one fact about loc as ground truth, whatever history predicted. Unlike the other operations, an observation makes no claim about how the state came about. It only states what the state is.

  • Component: component is present in well at exactly quant, and every other component is unchanged. observe!(well, rgt"glucose", 2u"g"). A zero quant asserts absence. Cost is unchanged.
  • Attribute: loc's own attribute is attribute, as with set_attribute!. A missing value means loc has no local value and defers to its parent.
  • Parent: child is located in parent (or nowhere, for nothing). This moves child like move_into! but ignores child's lock. Occupancy rules still apply.
  • Scalar facets: facet is one of :cost (a Well's cost), :locked or :active.

See _check_capability for instrument.

source
CHESSCore.set_component — Method
set_component(st::Stock, component::StockComponent, quant)

Return a copy of st in which component is present at exactly quant, leaving every other component unchanged. A zero quant removes the component. quant must have the dimension the component is tracked in: mass for a Solid, volume for a Liquid, and Biomass for an Organism.

See also: observe!

source
Base.:+ — Method
+(c1::Stock,c2::Stock)

Overload the additon operator to mix c1 and c2.

source
Base.:- — Method
-(c1::Stock,c2::Stock)

Overload the subtraction operator to remove c2 from c1.

If the result contains a chemical or organism with a negative quantity, a MixingError will be thrown saying which one is causing the problem – this includes organism biomass, which (unlike the old presence-only model) can now be fully removed, e.g. c - c == Empty() once biomass cancels exactly.

source