Local Contribution Bundle Export — v0
Status: implemented (service + CLI; bundle file output only) See: Bundle v0 format, Local bundle export v0 spec, Local-offline implementation plan
What this milestone does
Local export v0 turns selected local database rows into validated
ContributionBundleV0
JSON files.
This is a service + CLI milestone. The service
(backend/app/services/contribution_bundle_export.py)
owns the conversion and validation logic; the CLI wrapper
(backend/scripts/export_contribution_bundle.py)
handles argument parsing, database session setup, and writing JSON to
disk.
What this milestone does not do
- It does not import anything into a hosted instance.
- It does not implement hosted dry-run, hosted submit, or hosted import.
- It does not push local bundles to a remote.
- It does not add a public API route for export.
- It does not add any frontend UI.
- It does not synchronize raw database rows between instances.
- It does not package external artifacts (logs, geometries) into the bundle — only the manifest shape exists in v0.
These remain on the local-offline implementation plan as later milestones.
Supported export roots
--kind |
Selector flag | Source table |
|---|---|---|
thermo |
--thermo-id |
thermo |
kinetics |
--kinetics-id |
kinetics |
Both selectors are repeatable, so a single bundle can carry multiple
records of the same family. Mixed thermo + kinetics bundles are
explicitly rejected by ContributionBundleV0; export one of each
family separately.
Future bundle families (statmech, transport, network,
computed_reaction) are not yet supported.
Quick start
Run from
backend/(cd backendonce). The CLI lives atbackend/scripts/export_contribution_bundle.pyand is invoked by path below.
Thermo:
conda run -n tckdb_env python scripts/export_contribution_bundle.py \
--kind thermo \
--thermo-id 1 \
--output ./thermo-bundle.tckdb.json \
--title "Example thermo contribution" \
--summary "Selected thermo records from local TCKDB"
Kinetics:
conda run -n tckdb_env python scripts/export_contribution_bundle.py \
--kind kinetics \
--kinetics-id 10 \
--output ./kinetics-bundle.tckdb.json \
--title "Example kinetics contribution" \
--summary "Selected kinetics records from local TCKDB"
The CLI uses the same DB_* environment variables as the rest of the
project (see backend/app/api/config.py).
CLI options
Required
| Flag | Meaning |
|---|---|
--kind {thermo,kinetics} |
Bundle family. Must match the selector flag in use. |
--thermo-id INT (repeatable) |
Local thermo.id to export. Required for --kind thermo. |
--kinetics-id INT (repeatable) |
Local kinetics.id to export. Required for --kind kinetics. |
--output PATH |
Where to write the bundle JSON file. |
--title STR |
Submission title written to submission.title. |
--summary STR |
Submission summary written to submission.summary. |
Source-instance metadata (optional)
| Flag | Default | Meaning |
|---|---|---|
--instance-name |
local-tckdb |
Free-text label for the source instance. |
--instance-kind |
local |
One of local or lab_server. hosted is intentionally not allowed in v0. |
Exporter metadata (optional)
| Flag | Default | Meaning |
|---|---|---|
--exporter-label |
current OS user | Local label, written to exporter.local_user_label. Not a hosted identity. |
--orcid |
— | ORCID iD if known. |
--affiliation |
— | Free-text affiliation. |
--email |
— | Free-text contact. |
--exporter-notes |
— | Free-text notes. |
Output behavior
| Flag | Default | Meaning |
|---|---|---|
--overwrite |
off | Replace --output if it already exists. Without this, the CLI exits with an error rather than clobbering an existing file. |
Dependency closure
Each export root pulls in the minimum supporting data needed to reconstruct an upload-equivalent payload:
Thermo (Thermo row)
- the
Thermorow's scalar fields,tmin_k/tmax_k, and note - attached
ThermoNASA(if any) andThermoPointrows - target
SpeciesEntryand its parentSpecies(rebuilt as the upload schema'sspecies_entryidentity payload) Literaturereference if attachedSoftwareReleaseandWorkflowToolReleaseif attached
Kinetics (Kinetics row)
- the
Kineticsrow's scalar fields,tmin_k/tmax_k, model kind, and note - canonical
ea_kj_molis round-tripped back to the upload schema's(reported_ea, reported_ea_units)pair usingkj_mol(the canonical storage unit), which is lossless ChemReactionreversibility and reaction-family metadata- ordered structured participants from
ReactionEntryStructureParticipant, rebuilt asspecies_entryidentity payloads Literaturereference if attachedSoftwareReleaseandWorkflowToolReleaseif attached
Closure failure
The export service raises ContributionBundleExportError (and the CLI
exits non-zero) when:
- the requested
thermo_id/kinetics_iddoes not exist - a thermo's species entry or species identity is missing
- a kinetics row's reaction entry, chem reaction, or structured participants are missing
- the assembled bundle fails
ContributionBundleV0validation for any reason (including nested upload-schema validation)
The bundle file is not written when any of these fail.
What is not reconstructed in v0
These are within the upload schemas but are skipped by v0 export to keep the surface honest:
- inline supporting
calculationsandsource_calculationson a thermo upload (would require LOT/software resolution and a full string-key reconstruction) applied_energy_correctionson a thermo uploadenergy_level_of_theorydriven SP source-calc auto-resolution on kinetics
These can be added incrementally once the hosted import path knows what to do with them.
Local refs
local_refs in v0 is annotation only. The service emits a small
set of human-readable, traceability-oriented refs covering the root
record and its immediate dependencies:
| Bundle root | Local-ref keys emitted |
|---|---|
| Thermo | thermo:t<id>, species_entry:se<id>, species:s<id> |
| Kinetics | kinetics:k<id>, reaction:r<id>, species_entry:se<id>, species:s<id> (one per distinct participant) |
Labels are deliberately prefixed (t1, se42, s5) so they pass the
ContributionBundleV0 validator's "no purely numeric label" rule, which
exists to discourage shipping raw DB primary keys as portable identity.
The local DB ids are still embedded for debug/traceability only —
the bundle does not claim them as hosted identities, and the hosted
importer (when it lands) is responsible for resolving real hosted
identities via scientific content, not via these labels.
The v0 schema does not enforce coverage between local_refs and
records, so the set above is intentionally minimal.
Output format
- UTF-8 JSON
- 2-space indentation, key order matches schema field order
created_atis set at export time in UTCmanifest.sha256 == nullandmanifest.files == [](no artifacts in v0)- the assembled bundle is validated against
ContributionBundleV0before the file is written, so a written file is always a valid v0 bundle
Validating an exported bundle
The bundle is validated automatically before being written. To re-validate an existing file with the same schema (e.g. as part of an external CI step):
import json
from pathlib import Path
from app.schemas.workflows.contribution_bundle import ContributionBundleV0
raw = json.loads(Path("./thermo-bundle.tckdb.json").read_text(encoding="utf-8"))
ContributionBundleV0.model_validate(raw)
This re-runs every nested upload validator
(ThermoUploadRequest, KineticsUploadRequest) plus the bundle-level
family/local-ref/manifest rules.
Testing
Service + CLI tests live in
backend/tests/services/test_contribution_bundle_export.py
and cover thermo export, kinetics export, missing-root failures,
incomplete-dependency failures, CLI smoke output, and a credential-leak
guard.
Run them from backend/:
conda run -n tckdb_env pytest tests/services/test_contribution_bundle_export.py -v
Hosted import is not implemented yet
These bundles are not sent anywhere. There is no hosted dry-run, no hosted submit, no push-to-hosted UX, and no import endpoint. Those arrive in later milestones on the local-offline implementation plan.