Skip to content

feat(data): bring-your-own-data — shot-bundle import through a DataSource seam - #64

Merged
obvious-autobuild[bot] merged 8 commits into
mainfrom
feat/byo-shot-bundle
Oct 11, 2026
Merged

obvious-autobuild[bot] merged 8 commits into
mainfrom
feat/byo-shot-bundle

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Why

The spec's external research settled the multi-machine question: FAIR-MAST is the only substantially open tokamak archive, so "support another machine" cannot mean "connect to the cloud" — it has to mean a physicist with AUG/DIII-D/JET access brings their own data. Until now there was no door: every route assumed the MAST loader, the benchmark hard-coded mast.load_shot(30166), and a bundle without equilibrium broke /replay outright. This PR builds that door on the ShotBundle v1 contract DATA-1 landed.

What

  • DataSource seam (fusionlab/data/sources.py): a small protocol (load_shot / load_db / cached_shots / clean_db / import_bundle) with two implementations — fair-mast (delegating through the existing mast.py facade, preserving its monkeypatch surface) and user-file (imported bundles). Source selection: FUSIONLAB_DATA_SOURCE, with the archive as the zero-config default.
  • Validated user-file ingestor: upload (or point FUSIONLAB_USER_BUNDLES at) an npz bundle; it is validated in full against ShotBundle v1 before anything is written and published atomically (atomicio) — a malformed upload fails with the first bad field named (B_T: expected (21,) on the t_s base, got ()) and leaves no partial file. An optional machine.json beside the bundles configures the imported machine (attribution, Te ranges, USD prim root, campaign pattern, isotope, L/H cut) — defaults are deliberately modest and never borrow MAST's calibrations.
  • String shot ids end to end: archive numbers and imported ids (AUG-34578) ride the same routes — replay, ψ, field lines, USD export naming, CSV/JSON export, the catalog search, and the agent tools. The benchmark resolves its shot through the seam (bench_shot in the machine config, first cached bundle as fallback) instead of a hard-coded id; harness tasks run on uploaded bundles with no worker-registry changes.
  • Routes: POST /data/bundles/{id} (multipart import; a bare POST on a shot number self-imports it through the cache-then-S3 path) and DELETE /data/bundles/{id} (imported bundles only — the archive's cache belongs to the loader).
  • Degradation preserved: a trace-only bundle (no ψ/LCFS) replays with the equilibrium_absent hint; imported ids carry no archive useful/abort flags and degrade to flagless rather than inventing them.
  • UI: an import-bundle control and delete affordance in the replay toolbar; the picker refreshes from the source's own /shots list.
  • Docs: docs/DATA_CONTRACT.md — the contract for a second machine: units table (m, T, MA, 1e20 m⁻³, keV, MW, MJ), field shapes and optionality, machine.json reference, what works without equilibrium, and the house rule: quantities are compared with your data, never "validated" or "predictive".

Deliberately out of scope: IMAS-netCDF via omas (a follow-on ingestor — the seam is shaped for it), per-machine model retraining (foreign bundles flag out-of-distribution instead), machine parameters in routes/UI beyond the config.

How to Review

  • fusionlab/data/sources.py — the seam + ingestor + machine config (start here).
  • fusionlab/api_replay.py — the string-id rewrite and the two new routes; _shot() is the one resolver every surface shares.
  • tests/test_sources.py — the DATA-2 acceptance criteria as tests; tests/test_api.py / test_agent_tools.py show the pin reframes (integer-id → string-id; _catalog() → source-aware).
  • docs/DATA_CONTRACT.md — the machine-owner-facing contract.

Test Evidence

Bundle import control: replay picker before and after importing a synthetic non-MAST bundle (AUG-34578, foreign geometry + time base) — the imported shot renders beside the archive shots with out-of-distribution shading
Bundle import control: replay picker before and after importing a synthetic non-MAST bundle (AUG-34578, foreign geometry + time base) — the imported shot renders beside the archive shots with out-of-distribution shading — before
Bundle import control: replay picker before and after importing a synthetic non-MAST bundle (AUG-34578, foreign geometry + time base) — the imported shot renders beside the archive shots with out-of-distribution shading — after

  • Full suite green on this head: 467 passed, 1 skipped; ruff + mypy clean; pre-push hook re-ran the suite on every push.
  • End-to-end on the served app: a synthetic non-MAST bundle (string id AUG-34578, foreign geometry R=1.65 m a=0.5 m κ=1.7, its own time base) imports, appears in /shots beside archive numbers, replays with source-aware attribution, exports to CSV with provenance columns, and co-groups with shot 30166 in a compare export; the benchmark ran on the uploaded bundle with no worker changes; malformed upload → HTTP 400 naming B_T; trace-only bundle replays with the hint; deletes clean up. Before/after screenshots attached as QA evidence.
  • Out-of-distribution honesty visible in the UI: the imported shot's energy plot shades the learned-H98 band as "outside the training range" — the twin says where the MAST-fitted correction does not apply.

Human author: Tapan Ganatma Nakkina ([email protected])

Closes #34

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

ObviousApp and others added 8 commits October 11, 2026 20:11
…ingestor

One named door for every surface: a DataSource protocol (name, MachineConfig,
load_shot/load_db/cached_shots), FairMastSource delegating to the mast.py
facade (the tests' monkeypatch surface, kept intact), and UserFileSource —
npz bundles validated against ShotBundle v1 on every read, identity stamped
from the file name, machine.json config overlay that fails loudly on unknown
fields, and a validate-then-publish import through fusionlab.atomicio so a
rejected upload never leaves a partial file. The derived catalog evaluates
each bundle at its peak-current slice with tau_E and beta_t_pct computed by
the documented formulas.

Part of DATA-2 (#34). FUSIONLAB_DATA_SOURCE selects the default source;
FUSIONLAB_USER_BUNDLES points at an imported-bundle directory.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
Human author: Tapan Ganatma Nakkina ([email protected])
Every route, cache and tool now addresses shots by string id — archive
numbers and imported bundle ids ride the same paths. Replay, psi,
field-lines, exports, USD naming and attribution resolve through the
source's machine config; /shots, /db and /db/search serve the configured
source's catalog beside imported bundles.

POST /data/bundles/{id} imports an npz bundle through the user-file
source (validated against ShotBundle v1, published atomically, optional
meta_json); a bare POST on a digit id imports that archive shot through
the cache-then-S3 path. DELETE removes imported bundles only. The
benchmark resolves its shot through the source seam (bench_shot config,
first cached bundle fallback) instead of a hard-coded archive id.

Agent tools take string ids and the action_proposed event now records
the canonical proposal args the confirm applies.

Closes #34 (with the ingestor and docs commits).

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
Shot ids stay strings across picker, deep links, search selection,
compare, retries and exports; the picker refreshes from the source's
own /shots list. An import control uploads an npz bundle (or
self-imports an uncached archive shot by number) and a delete control
removes imported bundles.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
docs/DATA_CONTRACT.md describes the ShotBundle v1 contract for a second
machine: the units table (m, T, MA, 1e20 m^-3, keV, MW, MJ), field
shapes and optionality, machine.json, the two import paths, what works
without equilibrium, and the no-validation-claims rule (compared with,
never "validated" or "predictive"). Repo map gains the data-module row;
the env docs cover the data-source selection vars.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
…id surface

New tests: source selection and facade delegation, valid/invalid bundle
ids, validated atomic imports (no partial file on rejection), machine
config, env-selected directories, derived catalogs, multipart upload
through replay/compare/CSV export, deletion, trace-only bundles without
equilibrium, malformed and truncated archives, numeric-upload-ID
rejection, imported-catalog search, and the benchmark endpoint on an
uploaded bundle. Existing pins that froze the pre-DATA-2 surface (int
ids, no machine kwarg, raw model args in proposal events) are reframed
to the string-id one; the invariants under test are unchanged.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
The rebase onto the moved main left semantic joins the textual merge
missed: imported catalogs now carry the derived mode column (FEAT-4's
lh_mode over this machine's isotope and enhancement cut, never MAST's
calibration), _archive_flags takes the source and degrades imported
ids to flagless (they carry no archive bookkeeping), and the HARN-4
tests that reached into the pre-DATA-2 internals (no-arg _catalog,
dict-returning _shot, integer tool-data ids) are reframed to the
source-aware string-id surface.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
The load_shot/limit_distances error hints listed the archive cache's
ids while the replay route now serves the configured source's shots —
archive plus imported bundles, as string ids. Pin the tool's data to
the source's own list so the hint matches what the route can load.

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
@obvious-autobuild
obvious-autobuild Bot marked this pull request as ready for review October 11, 2026 20:32
@obvious-autobuild
obvious-autobuild Bot merged commit c370499 into main Oct 11, 2026
3 checks passed
@obvious-autobuild
obvious-autobuild Bot deleted the feat/byo-shot-bundle branch October 11, 2026 20:37
obvious-autobuild Bot added a commit that referenced this pull request Oct 12, 2026
Task-mode IA: Replay / Compare / Explore workspaces behind one hash grammar; shared shot-load state machine; anchored what-if as a Replay drawer; command palette; catalog absorbed into Compare with MJ units and catalog naming; a11y tokens; integrated main (#61 #62 #64) + post-audit fixes (CSV-export error surfacing 02c16c3, mobile compare-bar wrap 73b4048). V1-V10 sweep PASS at 73b4048; V8 capability parity PASS (72 controls, art_A29dHIEm).

Co-authored-by: Tapan Ganatma Nakkina <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DATA-2: Bring-your-own-data: user shot-bundle import through a DataSource seam

2 participants