Environments and data
Tables, columns, objects, results, naming schemes.
Dalea has two ways to store information: documents (free-form, rich-text, collaborative) and environments (structured, schema-validated, queryable). Most labs use both.
This page explains the structured side: environments and the primitives that live in them.
Why structure matters
A spreadsheet is structured. An ELN page is unstructured. The first lets you ask "what was the average plasma concentration at 4 h across all 3 mg/kg dose groups?". The second forces you to read.
In Dalea you can have both: write up the experiment in a document, and have the underlying data be queryable in real time because it lives in an environment.
The five primitives
- Environment
- A schema container. Holds tables, columns, and naming schemes. Reusable across studies.
- Table
- An entity table (samples, animals, reagents), a result table (measurements over time), or an inventory table (physical stock you draw down).
- Column
- A field on a table. Has one of ten types (text, number, integer, boolean, date, datetime, enum, reference, file, json) and validation rules.
- Object
- A row in an entity table. One sample, one animal, one reagent. Gets a unique ID and a human-readable display ID like SMP-024.
- Result
- A measurement row in a result table. Always belongs to a result batch (one recording). Its columns are typed as dimensions, measurements or context.
The lifecycle
Designing a schema in Dalea always follows the same six steps. Watch them auto-cycle or click any to inspect:
Schema design first (left three boxes), then data entry (right three). Most labs only revisit columns and naming schemes a few times before stabilising.
Entity, result and inventory tables
This is the only schema decision that occasionally trips people up.
- Entity tables describe things: animals, plasma samples, plates. One row = one thing. They're optimised for lookup and reference.
- Result tables describe measurements: concentrations, viability values, expression levels. They're optimised for analytical queries (group, aggregate, filter on dimensions).
- Inventory tables describe physical stock: the eleven vials of a compound actually in the freezer. One row = one vial, with a quantity that gets drawn down and a location that changes.
Entity versus inventory is the pairing worth getting right. Test Articles holds the catalog record for compound DLA-7 once; Sample Stock holds each vial of it, linked back to that record. See designing item types.
Every column on a result table carries one of three roles:
- Dimension: the axes you'll query and group by (animal, timepoint, treatment).
- Measurement: the values you actually recorded (concentration in µg/mL, body weight in g, ct_value); the unit is set on the column, not written into its name.
- Context: informational metadata carried alongside the row (notes, operator, instrument) that is neither a grouping axis nor a value you aggregate.
The dimension/measurement split is what lets you ask "mean concentration at 4 h, grouped by dose group" in one click.
Naming schemes
Most labs hate manually typing IDs. A table's display pattern generates them for
you. A pattern is literal text plus {token} placeholders, and there are only
four kinds of token:
{###}or{0001}: a zero-padded counter. The token's length is the width, soANM-{###}gives ANM-001, ANM-002, … and{0001}gives 0001, 0002, …{A}or{AA}: an alphabetic counter, A, B, … Z, AA.{uuid}or{uuid:8}: random hex, optionally truncated, as inSMP-{uuid:8}.{column_name}: the value of a column on the same row, matched by the column's name or display name.{sex}{000}gives F001, F002 …
Anything else inside braces is treated as a column reference, so an unrecognised token silently renders as nothing. In particular there is no date or year token: if you want a year in the ID, put it in a real column and reference that column.
The counter follows the table's scope. A global table keeps one counter; a project- or entry-scoped table gets a separate counter per project or entry. Patterns run as soon as you create an object, so you almost never assign a display ID by hand.