Skip to content

Validation and readiness

Two independent questions, deliberately kept apart. See Validation tiers and Analysis readiness.

Reports and findings

ValidationReport dataclass

ValidationReport(issues: tuple[ValidationIssue, ...] = ())

The result of validation: an ordered, deterministic list of findings.

is_valid property

is_valid: bool

True when there are no ERROR-severity issues.

This is a statement about structural validity only. It is never a claim that the data are scientifically valid or that any analysis is appropriate.

counts

counts() -> tuple[int, int, int]

(errors, warnings, infos).

Source code in src/openauc/models/validation.py
def counts(self) -> tuple[int, int, int]:
    """``(errors, warnings, infos)``."""
    return (len(self.errors), len(self.warnings), len(self.infos))

by_code

by_code(code: str) -> tuple[ValidationIssue, ...]

Every finding carrying code, in report order.

Source code in src/openauc/models/validation.py
def by_code(self, code: str) -> tuple[ValidationIssue, ...]:
    """Every finding carrying ``code``, in report order."""
    return tuple(i for i in self.issues if i.code == code)

codes

codes() -> tuple[str, ...]

Every finding's code, in report order (duplicates retained).

Source code in src/openauc/models/validation.py
def codes(self) -> tuple[str, ...]:
    """Every finding's code, in report order (duplicates retained)."""
    return tuple(i.code for i in self.issues)

for_tiers

for_tiers(
    *tiers: ValidationTier,
    severities: tuple[ValidationSeverity, ...]
    | None = None,
) -> ValidationReport

A report narrowed to findings pertaining to tiers.

Parameters:

Name Type Description Default
tiers ValidationTier

Keep a finding when any of its tiers is named here.

()
severities tuple[ValidationSeverity, ...] | None

When given, additionally keep only these severities.

None
Source code in src/openauc/models/validation.py
def for_tiers(
    self,
    *tiers: ValidationTier,
    severities: tuple[ValidationSeverity, ...] | None = None,
) -> ValidationReport:
    """A report narrowed to findings pertaining to ``tiers``.

    Args:
        tiers: Keep a finding when any of its ``tiers`` is named here.
        severities: When given, additionally keep only these severities.
    """
    selected = tuple(
        issue
        for issue in self.issues
        if any(tier in tiers for tier in issue.tiers)
        and (severities is None or issue.severity in severities)
    )
    return ValidationReport(issues=selected)

blocking_for

blocking_for(
    tier: ValidationTier,
) -> tuple[ValidationIssue, ...]

Every finding that prevents tier.

Source code in src/openauc/models/validation.py
def blocking_for(self, tier: ValidationTier) -> tuple[ValidationIssue, ...]:
    """Every finding that prevents ``tier``."""
    return tuple(i for i in self.issues if i.blocks_tier(tier))

raise_if_invalid

raise_if_invalid() -> None

Raise :class:StructuralValidationError if any errors are present.

Source code in src/openauc/models/validation.py
def raise_if_invalid(self) -> None:
    """Raise :class:`StructuralValidationError` if any errors are present."""
    if not self.is_valid:
        raise StructuralValidationError(str(self))

to_dict

to_dict() -> dict[str, Any]

Serialise the report to plain JSON-friendly Python types.

Source code in src/openauc/models/validation.py
def to_dict(self) -> dict[str, Any]:
    """Serialise the report to plain JSON-friendly Python types."""
    errors, warnings, infos = self.counts()
    return {
        "is_valid": self.is_valid,
        "counts": {"error": errors, "warning": warnings, "info": infos},
        "issues": [issue.to_dict() for issue in self.issues],
    }

ValidationIssue dataclass

ValidationIssue(
    code: str,
    message: str,
    severity: ValidationSeverity = ValidationSeverity.ERROR,
    location: str | None = None,
    tiers: tuple[ValidationTier, ...] = (
        ValidationTier.STRUCTURAL,
    ),
    blocks: tuple[ValidationTier, ...] = (),
    observed: str | None = None,
    expected: str | None = None,
    remediation: str | None = None,
    component: str | None = None,
    scan_ids: tuple[str, ...] = (),
)

A single validation finding.

Attributes:

Name Type Description
code str

Stable identifier for the check that produced the finding.

message str

Human-readable statement of what was found.

severity ValidationSeverity

See :class:~openauc.models.enums.ValidationSeverity.

location str | None

The single affected scan, sample or component, when exactly one is affected.

tiers tuple[ValidationTier, ...]

The tier(s) the finding speaks to. Never empty.

blocks tuple[ValidationTier, ...]

The tier(s) the finding prevents. A finding may pertain to a tier without blocking it.

observed str | None

What was actually found, rendered as text.

expected str | None

The condition that would have satisfied the check.

remediation str | None

A concrete suggestion for resolving the finding.

component str | None

The model field or component the finding concerns.

scan_ids tuple[str, ...]

Every affected scan identifier, sorted. One finding aggregates a condition across many scans rather than emitting one finding per scan.

tier property

tier: ValidationTier

The primary (first-named) tier this finding speaks to.

blocks_structural_validity property

blocks_structural_validity: bool

True when this finding prevents structural validity.

Equivalent to carrying ERROR severity: by the severity policy only errors may block the archival or structural tiers.

blocks_tier

blocks_tier(tier: ValidationTier) -> bool

True when this finding prevents tier.

Source code in src/openauc/models/validation.py
def blocks_tier(self, tier: ValidationTier) -> bool:
    """True when this finding prevents ``tier``."""
    return tier in self.blocks

describe

describe() -> str

A multi-line rendering including observed/expected/remediation.

Source code in src/openauc/models/validation.py
def describe(self) -> str:
    """A multi-line rendering including observed/expected/remediation."""
    lines = [str(self)]
    details = (
        ("tiers", ", ".join(t.value for t in self.tiers)),
        ("blocks", ", ".join(t.value for t in self.blocks) or "nothing"),
        ("component", self.component),
        ("observed", self.observed),
        ("expected", self.expected),
        ("remediation", self.remediation),
        ("scans", ", ".join(self.scan_ids) if self.scan_ids else None),
    )
    lines.extend(f"    {label}: {value}" for label, value in details if value)
    return "\n".join(lines)

to_dict

to_dict() -> dict[str, Any]

Serialise the finding to plain JSON-friendly Python types.

Source code in src/openauc/models/validation.py
def to_dict(self) -> dict[str, Any]:
    """Serialise the finding to plain JSON-friendly Python types."""
    return {
        "code": self.code,
        "message": self.message,
        "severity": self.severity.value,
        "location": self.location,
        "tiers": [t.value for t in self.tiers],
        "blocks": [t.value for t in self.blocks],
        "observed": self.observed,
        "expected": self.expected,
        "remediation": self.remediation,
        "component": self.component,
        "scan_ids": list(self.scan_ids),
    }

Entry points

validate_experiment

validate_experiment(
    experiment: AUCExperiment,
) -> ValidationReport

Run every check across all four tiers and return the full report.

Findings are ordered by the fixed check registry; within a check, affected scans are sorted. Equivalent experiments therefore produce equivalent reports.

Source code in src/openauc/models/validation.py
def validate_experiment(experiment: AUCExperiment) -> ValidationReport:
    """Run every check across all four tiers and return the full report.

    Findings are ordered by the fixed check registry; within a check, affected
    scans are sorted. Equivalent experiments therefore produce equivalent
    reports.
    """
    from openauc.models.checks import CHECKS

    issues: list[ValidationIssue] = []
    for check in CHECKS:
        issues.extend(check(experiment))
    return ValidationReport(issues=tuple(issues))

validate_experiment_structure

validate_experiment_structure(
    experiment: AUCExperiment,
) -> ValidationReport

Validate archival and structural consistency only.

Returns the ARCHIVAL and STRUCTURAL findings of ERROR or WARNING severity. Readiness findings and purely informational findings are reported by :func:validate_experiment instead. This is a representational check; it makes no scientific judgement.

Source code in src/openauc/models/validation.py
def validate_experiment_structure(experiment: AUCExperiment) -> ValidationReport:
    """Validate archival and structural consistency only.

    Returns the ``ARCHIVAL`` and ``STRUCTURAL`` findings of ``ERROR`` or
    ``WARNING`` severity. Readiness findings and purely informational findings
    are reported by :func:`validate_experiment` instead. This is a
    representational check; it makes no scientific judgement.
    """
    return validate_experiment(experiment).for_tiers(
        *_STRUCTURAL_TIERS, severities=_STRUCTURAL_SEVERITIES
    )

Readiness

ReadinessAssessment dataclass

ReadinessAssessment(entries: tuple[AnalysisReadiness, ...])

Readiness for every reported workflow, plus the permanent non-assessment.

scientific_suitability property

scientific_suitability: AnalysisReadiness

Always :attr:ReadinessStatus.NOT_ASSESSED.

for_analysis

for_analysis(analysis: AnalysisKind) -> AnalysisReadiness

The entry for analysis.

Source code in src/openauc/models/readiness.py
def for_analysis(self, analysis: AnalysisKind) -> AnalysisReadiness:
    """The entry for ``analysis``."""
    for entry in self.entries:
        if entry.analysis is analysis:
            return entry
    raise KeyError(f"no readiness entry for {analysis.value!r}")

to_dict

to_dict() -> dict[str, Any]

Serialise to plain JSON-friendly Python types.

Source code in src/openauc/models/readiness.py
def to_dict(self) -> dict[str, Any]:
    """Serialise to plain JSON-friendly Python types."""
    return {"entries": [entry.to_dict() for entry in self.entries]}

AnalysisReadiness dataclass

AnalysisReadiness(
    analysis: AnalysisKind,
    status: ReadinessStatus,
    blocking_issues: tuple[ValidationIssue, ...] = (),
    advisory_issues: tuple[ValidationIssue, ...] = (),
    note: str | None = None,
)

Whether the metadata one workflow would need is present.

POTENTIALLY_READY reports metadata presence only. It is never a claim that the data are correct or that the analysis is appropriate.

to_dict

to_dict() -> dict[str, Any]

Serialise to plain JSON-friendly Python types.

Source code in src/openauc/models/readiness.py
def to_dict(self) -> dict[str, Any]:
    """Serialise to plain JSON-friendly Python types."""
    return {
        "analysis": self.analysis.value,
        "status": self.status.value,
        "note": self.note,
        "blocking_issues": [issue.to_dict() for issue in self.blocking_issues],
        "advisory_issues": [issue.to_dict() for issue in self.advisory_issues],
    }

assess_experiment_readiness

assess_experiment_readiness(
    experiment: AUCExperiment,
) -> ReadinessAssessment

Report metadata readiness per workflow. Makes no scientific judgement.

Source code in src/openauc/models/readiness.py
def assess_experiment_readiness(experiment: AUCExperiment) -> ReadinessAssessment:
    """Report metadata readiness per workflow. Makes no scientific judgement."""
    report = experiment.validate()
    experiment_type = experiment.metadata.experiment_type
    entries = tuple(
        _assess_one(analysis, experiment_type, report)
        for analysis in (
            AnalysisKind.SEDIMENTATION_VELOCITY,
            AnalysisKind.SEDIMENTATION_EQUILIBRIUM,
        )
    )
    scientific = AnalysisReadiness(
        analysis=AnalysisKind.SCIENTIFIC_SUITABILITY,
        status=ReadinessStatus.NOT_ASSESSED,
        note=SCIENTIFIC_SUITABILITY_NOTE,
    )
    return ReadinessAssessment(entries=(*entries, scientific))

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.

ValidationSeverity

Bases: StrEnum

Severity of a validation finding.

The severity policy is fixed and applied uniformly:

  • ERROR — may block ARCHIVAL or STRUCTURAL validity. Only errors affect :attr:~openauc.models.validation.ValidationReport.is_valid.
  • WARNING — never blocks archival or structural validity. It either blocks a readiness tier or flags a representational anomaly.
  • INFO — descriptive only; blocks nothing.

Readiness findings never use ERROR.

ValidationTier

Bases: StrEnum

The question a validation finding speaks to.

Tiers are answered independently; a finding may pertain to more than one. Scientific suitability is deliberately not a tier — see :class:ReadinessStatus and :class:AnalysisKind.

ReadinessStatus

Bases: StrEnum

Outcome of an analysis-readiness assessment.

POTENTIALLY_READY reports that the metadata a future workflow needs is present. It is never a statement that the data are correct, of good quality, or scientifically suitable — that is never assessed.

AnalysisKind

Bases: StrEnum

A workflow whose metadata prerequisites can be reported on.

SCIENTIFIC_SUITABILITY is included so that "not assessed" is a machine-readable, always-present part of every assessment rather than a prose disclaimer. Its status is permanently :attr:ReadinessStatus.NOT_ASSESSED.