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:

🧪
Environment
In-vivo PK
⊞
Tables
Animals, Doses, Samples, PK Results
|
Columns
Sex (enum), Weight (number, g)…
#
Naming schemes
Animal: ANM-{###}
â—‰
Objects
ANM-001 … ANM-024
📈
Results
Concentration vs time

Schema design first (left three boxes), then data entry (right three). Most labs only revisit columns and naming schemes a few times before stabilising.

The environment lifecycle: schema design then data entry. The PK study you'll build in the tutorial follows exactly this sequence.

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, so ANM-{###} 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 in SMP-{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.

What's next