Skip to content

Experiments and metadata

The canonical in-memory model. See Canonical data model for the design.

AUCExperiment

AUCExperiment dataclass

AUCExperiment(
    metadata: ExperimentMetadata,
    scans: tuple[ScanMetadata, ...],
    observations: Observations,
    samples: tuple[SampleMetadata, ...] = (),
    instrument: InstrumentMetadata | None = None,
    provenance: ImportProvenance | None = None,
)

A complete canonical AUC experiment.

Parameters:

Name Type Description Default
metadata ExperimentMetadata

Experiment identity (area A).

required
scans tuple[ScanMetadata, ...]

Per-scan metadata, one record per scan.

required
observations Observations

The radial signal data (shared or per-scan axes).

required
samples tuple[SampleMetadata, ...]

Optional sample/buffer metadata.

()
instrument InstrumentMetadata | None

Optional instrument and run metadata.

None
provenance ImportProvenance | None

Optional import-provenance record.

None

Construction does not enforce cross-object consistency (e.g. that scan identifiers match the observations); use :meth:validate_structure to check that and obtain a report. Field-level invariants are enforced by the component models at their own construction.

validate_structure

validate_structure() -> ValidationReport

Run archival and structural validation (does not raise).

Returns the ARCHIVAL and STRUCTURAL findings of ERROR or WARNING severity. Use :meth:validate for the full report across all four tiers, including informational findings.

Source code in src/openauc/models/experiment.py
def validate_structure(self) -> ValidationReport:
    """Run archival and structural validation (does not raise).

    Returns the ``ARCHIVAL`` and ``STRUCTURAL`` findings of ``ERROR`` or
    ``WARNING`` severity. Use :meth:`validate` for the full report across
    all four tiers, including informational findings.
    """
    return validate_experiment_structure(self)

validate

validate() -> ValidationReport

Run every check across all four tiers (does not raise).

The report covers archival, structural and both readiness tiers. It makes no claim about scientific validity: see :meth:assess_readiness.

Source code in src/openauc/models/experiment.py
def validate(self) -> ValidationReport:
    """Run every check across all four tiers (does not raise).

    The report covers archival, structural and both readiness tiers. It
    makes no claim about scientific validity: see :meth:`assess_readiness`.
    """
    return validate_experiment(self)

assess_readiness

assess_readiness() -> ReadinessAssessment

Report whether the metadata a future workflow needs is present.

This assesses metadata presence only. Scientific suitability is always reported as NOT_ASSESSED.

Source code in src/openauc/models/experiment.py
def assess_readiness(self) -> ReadinessAssessment:
    """Report whether the metadata a future workflow needs is present.

    This assesses metadata presence only. Scientific suitability is always
    reported as ``NOT_ASSESSED``.
    """
    return assess_experiment_readiness(self)

optical_systems

optical_systems() -> tuple[OpticalSystem, ...]

Distinct optical systems named across scans (and the instrument).

Source code in src/openauc/models/experiment.py
def optical_systems(self) -> tuple[OpticalSystem, ...]:
    """Distinct optical systems named across scans (and the instrument)."""
    systems = {scan.optical_system for scan in self.scans}
    if self.instrument is not None:
        systems.add(self.instrument.optical_system)
    return tuple(sorted(systems, key=lambda s: s.value))

summary_data

summary_data() -> ExperimentSummary

A structured, factual summary of the experiment's structure.

Holds counts, ranges and metadata-presence facts only — no scientific calculation, quality score or inferred value.

Source code in src/openauc/models/experiment.py
def summary_data(self) -> ExperimentSummary:
    """A structured, factual summary of the experiment's structure.

    Holds counts, ranges and metadata-presence facts only — no scientific
    calculation, quality score or inferred value.
    """
    return summarise_experiment(self)

summary

summary() -> str

A factual, human-readable summary of the experiment's structure.

The summary describes structure and metadata only. It makes no claim about scientific validity or suitability for sedimentation analysis. Equivalent to self.summary_data().to_text().

Source code in src/openauc/models/experiment.py
def summary(self) -> str:
    """A factual, human-readable summary of the experiment's structure.

    The summary describes structure and metadata only. It makes no claim
    about scientific validity or suitability for sedimentation analysis.
    Equivalent to ``self.summary_data().to_text()``.
    """
    return self.summary_data().to_text()

export

export(
    path: str | Path,
    *,
    overwrite: bool = False,
    exported_at: datetime | None = None,
) -> Path

Write this experiment to an AUCX archive and return the path.

The archive is written atomically and verified before it replaces anything. See :func:openauc.export_aucx.

Source code in src/openauc/models/experiment.py
def export(
    self,
    path: str | Path,
    *,
    overwrite: bool = False,
    exported_at: datetime | None = None,
) -> Path:
    """Write this experiment to an AUCX archive and return the path.

    The archive is written atomically and verified before it replaces
    anything. See :func:`openauc.export_aucx`.
    """
    # Imported here so the model layer does not depend on the format layer
    # at import time.
    from openauc.formats.aucx import export_aucx

    return export_aucx(self, path, overwrite=overwrite, exported_at=exported_at)

to_dict

to_dict() -> dict[str, Any]

Serialise the experiment to plain JSON-friendly Python types.

Source code in src/openauc/models/experiment.py
def to_dict(self) -> dict[str, Any]:
    """Serialise the experiment to plain JSON-friendly Python types."""
    return {
        "metadata": self.metadata.model_dump(mode="json"),
        "instrument": (
            self.instrument.model_dump(mode="json")
            if self.instrument is not None
            else None
        ),
        "samples": [s.model_dump(mode="json") for s in self.samples],
        "scans": [s.model_dump(mode="json") for s in self.scans],
        "observations": self.observations.to_dict(),
        "provenance": (
            self.provenance.model_dump(mode="json")
            if self.provenance is not None
            else None
        ),
    }

from_dict classmethod

from_dict(data: dict[str, Any]) -> AUCExperiment

Reconstruct an experiment from :meth:to_dict output.

Source code in src/openauc/models/experiment.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> AUCExperiment:
    """Reconstruct an experiment from :meth:`to_dict` output."""
    instrument = data.get("instrument")
    provenance = data.get("provenance")
    return cls(
        metadata=ExperimentMetadata.model_validate(data["metadata"]),
        scans=tuple(ScanMetadata.model_validate(item) for item in data["scans"]),
        observations=Observations.from_dict(data["observations"]),
        samples=tuple(
            SampleMetadata.model_validate(item) for item in data.get("samples", [])
        ),
        instrument=(
            InstrumentMetadata.model_validate(instrument)
            if instrument is not None
            else None
        ),
        provenance=(
            ImportProvenance.model_validate(provenance)
            if provenance is not None
            else None
        ),
    )

Experiment identity

ExperimentMetadata

Bases: BaseModel

Experiment identity (area A).

Only experiment_id is required. Every other field is optional and, when absent, is left as None (structurally absent) rather than given a default value. experiment_type defaults to UNKNOWN — an explicit "not stated", never an inferred type.

Scans

ScanMetadata

Bases: BaseModel

Metadata for a single radial scan.

Samples

SampleMetadata

Bases: BaseModel

Sample and buffer metadata.

Instrument

InstrumentMetadata

Bases: BaseModel

Instrument, rotor and run-level acquisition metadata.

Quantities

The primitive for a scientific scalar: a value, a declared unit, an explicit presence status and a provenance tag. See Missing, unknown and not-applicable.

Quantity

Bases: BaseModel

A scientific scalar with a retained unit, status and provenance.

Attributes:

Name Type Description
value float | None

The numeric value, or None when the status is not PRESENT. A present value must be finite (never NaN/inf).

unit Unit

The declared unit. Use Unit.OTHER with unit_label for open-ended units (e.g. concentration) and Unit.UNKNOWN when the unit is not known. The model never infers this.

unit_label str | None

Verbatim source unit text, retained when unit is OTHER or UNKNOWN.

status ValueStatus

Explicit presence semantics (present/missing/unknown/ not-applicable).

provenance ValueProvenance

Where the value came from (supplied/converted/inferred/ user-confirmed/unknown).

is_present property

is_present: bool

True only when a real numeric value is carried.

of classmethod

of(
    value: float,
    unit: Unit,
    *,
    unit_label: str | None = None,
    provenance: ValueProvenance = ValueProvenance.SUPPLIED,
) -> Quantity

Build a PRESENT quantity carrying value in unit.

Source code in src/openauc/models/metadata.py
@classmethod
def of(
    cls,
    value: float,
    unit: Unit,
    *,
    unit_label: str | None = None,
    provenance: ValueProvenance = ValueProvenance.SUPPLIED,
) -> Quantity:
    """Build a PRESENT quantity carrying ``value`` in ``unit``."""
    return cls(
        value=value,
        unit=unit,
        unit_label=unit_label,
        status=ValueStatus.PRESENT,
        provenance=provenance,
    )

missing classmethod

missing(
    provenance: ValueProvenance = ValueProvenance.SUPPLIED,
) -> Quantity

A value the source did not provide.

Source code in src/openauc/models/metadata.py
@classmethod
def missing(
    cls, provenance: ValueProvenance = ValueProvenance.SUPPLIED
) -> Quantity:
    """A value the source did not provide."""
    return cls(value=None, status=ValueStatus.MISSING, provenance=provenance)

unknown classmethod

unknown(
    provenance: ValueProvenance = ValueProvenance.UNKNOWN,
) -> Quantity

A value the source explicitly marks as unknown.

Source code in src/openauc/models/metadata.py
@classmethod
def unknown(cls, provenance: ValueProvenance = ValueProvenance.UNKNOWN) -> Quantity:
    """A value the source explicitly marks as unknown."""
    return cls(value=None, status=ValueStatus.UNKNOWN, provenance=provenance)

not_applicable classmethod

not_applicable(
    provenance: ValueProvenance = ValueProvenance.SUPPLIED,
) -> Quantity

A value that does not apply to this experiment.

Source code in src/openauc/models/metadata.py
@classmethod
def not_applicable(
    cls, provenance: ValueProvenance = ValueProvenance.SUPPLIED
) -> Quantity:
    """A value that does not apply to this experiment."""
    return cls(value=None, status=ValueStatus.NOT_APPLICABLE, provenance=provenance)

Provenance

ImportProvenance

Bases: BaseModel

Record of an experiment's origin and per-category value provenance.

The value-category tuples (supplied_values etc.) hold references — for example dotted field names — to the values in each provenance category. They complement the per-value :class:~openauc.models.metadata.Quantity provenance tag, giving an experiment-level audit list.

SourceChecksum

Bases: BaseModel

A checksum of one source file an experiment was read from.

An import can draw on more than one file — a manifest and a data file, for instance — and each is recorded separately rather than collapsed into a single field. role names what the file was to the import (manifest, data_file), not what it contains.

A checksum establishes integrity — that the bytes are unchanged since they were read. It says nothing about authenticity or origin.

Enumerations

enums

Enumerations for the canonical AUC data model.

All categorical vocabularies used by the model live here so they have a single definition and stable string values (the enums are StrEnum, so their members serialise to their declared string). Representing an optical system or unit here does not imply that importing or scientifically interpreting it is implemented — representation and support are deliberately separate.

ExperimentType

Bases: StrEnum

The kind of AUC experiment. UNKNOWN is explicit, never inferred.

OpticalSystem

Bases: StrEnum

Optical detection systems the model can represent.

Representation support is not a claim that file import or scientific interpretation is implemented or validated for every system.

Unit

Bases: StrEnum

Declared units retained by the model.

The canonical unit for each physical quantity is listed below. The model retains the declared unit and never infers or silently converts. Units that are open-ended (e.g. concentration) or absent are represented by OTHER (carry the verbatim text in Quantity.unit_label) or UNKNOWN.

RadiusAxisMode

Bases: StrEnum

Whether scans share one radius axis or each carry their own.

ValueStatus

Bases: StrEnum

Explicit presence semantics for a scientific value.

MISSING, UNKNOWN and NOT_APPLICABLE are conceptually different and must not be collapsed into a single sentinel or a default value.

ValueProvenance

Bases: StrEnum

Where a value came from, retained per value.