A ground-up design for BEACON's first product-grade interface — how it stores data, how it computes, and how it lets two very different audiences decide which forecast model is best for them.
BEACON compares AI weather models against conventional numerical weather prediction using standardized METplus verification, entirely in the browser, with no server. Every interface BEACON has had so far — including the current one — was a prototype; this document designs the first product-grade BEACON GUI. The governing design brief asks for something harder than a dashboard refresh: one product that serves a verification scientist and a program manager without betraying either, and that lets a user redefine what “best” means for their mission — honestly.
This proposal rests on three decisions:
Everything else — the tiered store contract, the question-shaped navigation, the honesty gates in the profile roll-up — follows from these three.
Three bodies of prior work were surveyed for this design: the existing BEACON visualization and its prototypes, the MET-AL lab (thirteen browser experiments in modern MET verification), and the metplus-data-store project (a full re-encoding of a real 62 GB verification archive into browser-streamable stores, with parity tests). The new design inherits deliberately — each asset below was adopted, adapted, or declined on its merits.
| Verdict | Asset | Why |
|---|---|---|
| Adopt | Single-file stat Parquet contract | The full 1.53M-row METplus archive fits in one 8.8 MB sorted Parquet, already proven under DuckDB-WASM over HTTP range reads. This is the spine (§3). |
| Adopt | MET-parity math library | A dependency-free ES-module library (categorical, continuous, vector, ensemble, bootstrap CIs) validated bit-for-bit against 20k+ real MET records, with ratio-of-sums aggregation built in. Becomes the client-side stat engine (§4). |
| Adopt | Oracle parity mechanism | Ship MET's own derived rows next to the ingredients; recompute in the browser; display the match. Turns correctness into a visible trust feature (§4). |
| Adopt | Manifest-driven capability flags | The existing app's manifest pattern, extended: what data exists drives what the UI offers, including honest “not available in this dataset” states (§3, §5). |
| Adopt | De-identification registry + leak check | Raw archive identifiers never reach the UI; a display-name registry with an automated leak check sanitizes site-identifying strings at build time (§3.4). |
| Adopt | URL-as-state engineering | Full view state — scope, view, profile, lens — serializes to the link. Proven in the current app; extended to carry custom profiles (§6.5). |
| Adapt | Plain↔technical label dictionary | The current app's two-way translation table grows into a three-vocabulary registry: plain, technical, impact (§5.3). |
| Adapt | Paired-bootstrap significance grid | A MET-AL prototype renders head-to-head significance from paired resampling — the statistically correct alternative to eyeballing CI overlap (§4.3, §5). |
| Adapt | Metric / dimension / filter / facet model | A clean interaction vocabulary for cross-filtered exploration; informs the scope bar (§5.2). |
| Decline | Executive / Advanced mode toggle | Named prototype residue by the brief. Replaced by the vocabulary lens (§5.3). |
| Decline | Per-metric weight sliders (0–2) | The Arena prototype's take on weighting. Rebuilt as structured Decision Profiles with honesty gates (§6). |
| Decline | Offline single-file builds | The MET-AL lab itself pivoted to streaming-first. the BEACON GUI is a hosted product, not an email attachment. |
| Decline | Virtual-reference / Icechunk storage | Rejected in the data-store project's own adversarial review: the browser reader can't consume it. Plain Zarr v3 + flat Parquet won. |
The brief fixes the platform: browser-only, no backend, potentially slow local queries handled gracefully. The design answers with a tiered static-store contract — every tier is a plain file layout on static hosting, readable via HTTP range requests, and every tier is optional except the first two. The manifest tells the app what it has; the app never guesses.
A small JSON document, fetched first, that declares per campaign: the model registry (§3.4), observation sources, variables and levels with their coverage, the valid-time span (the brief's date-filter bound), statistic families present, byte sizes and URLs of every store, and explicit capability flags — for example ensemble: absent in the current archive, which has no probabilistic line types. Capability flags are what let the UI distinguish “this dataset can't answer that yet” from “no data in this slice” (§5.4).
One sorted Parquet file per campaign holding the long/tidy METplus stat table: full MET header columns plus line_type, stat_name, stat_value, and confidence bounds where MET emitted them. The surveyed archive proves the scale argument: 1.53 million rows — four models, 24 cycles, twelve variables, six line types — compresses to 8.8 MB, and DuckDB-WASM answers grouped queries against it over HTTP range reads in an existing working demo.
The load-bearing property: the spine keeps the aggregable line types (CTC counts, SL1L2/VL1L2 partial sums) next to the derived ones (CNT, CTS, VCNT). Derived statistics answer “what did MET conclude for this exact slice?”; aggregables let the client honestly recompute statistics for any slice — across cycles, lead windows, or whatever a Decision Profile weights — without committing the mean-of-ratios error (§4.2).
The brief's drill-down job says every summary is a path into the evidence behind it. Post-processed statistics dead-end at the aggregate; Tier 2 extends the path. It holds, per campaign: the point-observation table (the surveyed archive's is 11.7 M rows in a 44 MB Parquet — station, position, variable, level, value, QC flag) and matched-pair output — MET's MPR line type, now decided-enabled in the pipeline (§12), landing as its own Parquet table rather than in the spine to protect the spine's size. Matched pairs are what make station-level drill-down and conditional verification (§8) possible. Paired forecast/observed grids also exist in the archive but need a small cataloging pass before browser use. Tier 2 is manifest-gated: when a campaign lacks it, drill-down bottoms out at the source stat rows, honestly labeled.
The raw archive contains identifiers that must not reach a public UI — model names and verification masks that encode the pilot site. Sanitization is therefore not an editing pass but a build stage: a display-name registry maps every raw identifier to a public name (e.g., a site-specific baseline renders as “Regional WRF”), and the build fails if any unmapped or forbidden string survives into a published store or bundle. The MET-AL lab already runs exactly this leak check for its public data bundle; the BEACON GUI promotes it to a contract requirement. The UI renders only registry names; provenance panels (§4.4) show registry names too, with raw identifiers available only in non-public builds.
The app is host-agnostic by construction — any static host with HTTP range support satisfies the contract — so demo and production can differ in origin and freshness without differing in code. Two shapes are designed:
data_mode: "snapshot" with an as-of date, and the UI badges every surface with it, so a demo verdict is never mistaken for a live one.data_mode: "live" and the manifest regenerated as campaigns are processed. Nothing in the app binds to either origin.The engine runs single-threaded by default, which avoids cross-origin isolation headers entirely; threading is an optional optimization where headers can be set. A failed engine initialization is the one prominent error state (§5.4); a failed individual query degrades to a quiet empty state.
DuckDB-WASM (in a Web Worker) does what SQL is good at: slicing the spine by scope, grouping into series, feeding the cascade filters. The stat engine — the adopted MET-parity math library — does what SQL must not be trusted to do: derive verification statistics from aggregables, aggregate across rows correctly, resample for uncertainty, and normalize for profile roll-ups. Queries are keyed by scope and cancellable; a superseded query's results are discarded before render, which is the mechanism behind the brief's no-stale-flicker rule.
Verification statistics do not average. The RMSE of two days is not the mean of two RMSEs; a hit rate over a month is not the mean of daily hit rates. The only honest aggregation sums the ingredients — contingency counts, partial sums — and derives the statistic once, at the end (ratio of sums, never mean of ratios). Because the spine carries those ingredients, every number in the BEACON GUI that spans more than one MET output row is derived this way, by a library whose aggregation primitives were validated against MET itself.
Where MET emitted confidence bounds, the UI shows them. Where a user asks “is this difference real?” the design does not let CI overlap masquerade as the answer — overlapping ranges do not demonstrate equivalence, and the brief flags this as a correctness requirement. Instead, head-to-head significance is computed the way the verification community actually does it: paired resampling on the difference series (both models scored on the same cases, resampled together), rendered as a significance mark with its confidence level. A surveyed MET-AL prototype already implements this grid client-side; the microcopy rule is fixed: “ranges that overlap do not mean the models are equivalent.”
A referee's authority rests on its arithmetic being checkable. Because the spine carries both ingredients and MET's own conclusions, the client can prove its math on the user's machine, continuously.
Concretely, every displayed statistic offers a provenance panel: which stat rows contributed, the derivation (“RMSE from summed squared errors over N pairs”), the aggregation span, and a parity check against MET's derived rows where one exists. This generalizes the “oracle parity” mechanism already prototyped in the surveyed stores — and it is the feature that lets an expert trust a verdict they didn't compute themselves.
The brief lists eight jobs-to-be-done and forbids inheriting the old screen structure. The BEACON GUI organizes around the questions users arrive with, in three surfaces plus one overlay:
Ensemble calibration (job 6) is designed as a Compare capability that the manifest currently reports absent — it renders as an honest “not available in this dataset” explainer until probabilistic line types arrive (§5.4, §11).
The data's selection dimensions cascade — models constrain observation sources, which constrain variables and levels, and so on — and many combinations simply have no data. The design treats the current selection as a first-class object: a persistent scope bar summarizing model set, observation source, variable, level, statistic, and valid-time range.
Every quantity in the product has three registered renderings, from one translation registry:
| Lens | Same quantity, rendered | Primary audience |
|---|---|---|
| Plain | “Typical 2-meter temperature error, in degrees” | Decision-makers, newcomers |
| Technical | TMP Z2 · RMSE (K) — exact METplus notation, never paraphrased | Verification scientists, model developers |
| Impact | “≈ $12k–$40k per event at stake” — estimate, assumptions attached (§7) | Program managers, planners |
The lens is a single global control. Plain is the default for a first visit; the chosen lens rides in the URL, so a shared link reproduces not just the data view but the register it was read in. Crucially the lens is per-rendering, not per-mode: hovering (or focusing) any labeled quantity reveals its other two renderings, so an expert reading Plain-language output can always see the exact notation underneath, and a newcomer in Technical view is one gesture from an explanation. Nobody is trapped in a mode that wasn't built for them — this replaces the executive/advanced split.
Every data region distinguishes, visibly and textually:
One error is deliberately loud: the data engine failing to initialize, which blocks everything and says so plainly. Any single failed query degrades to the quiet empty state.
The brief's central truth: “best” is not absolute. A Decision Profile captures a use case's priorities and rolls verification evidence into a standing under those priorities — transparently, recomputably, and labeled as the value judgment it is.
A small, serializable object — not a UI arrangement:
Preset candidates below come from research on thirty weather-dependent professional cohorts; the catalogue and every weight in it are content to be set with domain experts. Decision (§12): presets ship draft-labeled — marked draft in-product until expert review, rather than waiting on it. General (balanced) is the clearly-labeled default, active from first paint so a visitor gets a coherent answer with zero configuration. One preset is not a draft: MET's own reference skill-score index (§8), whose weights are fixed by the verification community and double as a calibration target for the roll-up engine.
| Preset | Leans toward | Example cohorts served |
|---|---|---|
| General (balanced) — default | Even facet weights; every family judged on its own terms | First visit, program overview |
| Severe weather & emergency mgmt. | Event detection, precipitation extremes, day-one leads | Emergency managers, response planners |
| Wind energy | Near-surface wind speed/direction, short leads, random error | Farm operators, grid load forecasters |
| Aviation operations | Wind aloft, visibility/ceiling family, nowcast-to-day-one | Dispatch, flight planning |
| Agriculture & water | Temperature extremes, precipitation totals, medium-range | Growers, reservoir operations |
| Reference skill index (MET) | MET's published skill-score-index weighting, unmodified — a community-defined composite, not a BEACON judgment | Verification scientists; doubles as the roll-up engine's parity target (§8) |
Normalization (stage 3) makes non-comparable measures blendable, so it must be inspectable, not a black box. The proposed default: within each facet, each model's aggregated statistic maps to a relative standing on 0–1 against the model field in the current scope, after applying the metric's direction (a data property — reducing a lower-is-better error always reads as improvement). The method is named in the UI, its intermediate values are visible in the “how this was computed” panel, and the architecture treats it as a pluggable strategy so domain experts can evaluate alternatives (e.g., skill-score-referenced scaling) without a redesign.
Sparse data meets weighted priorities constantly, and the brief demands the collision be visible. Three mechanisms:
One capability, two editors over the same object — neither audience gets a dumbed-down or gated-off version:
The active profile — preset or custom — serializes compactly into the URL. A colleague opening a shared link sees the same verdict under the same priorities, with the same amber context. Named presets travel by id; custom profiles travel by value; server-side persistence stays out of scope per the brief.
Impact Translation re-expresses skill differences in consequences — above all money — so a decision-maker can grasp what a difference in accuracy is worth. It is engineered as the third vocabulary (§5.3): an optional lens over the same quantities, never a replacement for them.
The cost model is a visible object. Each translation rests on a named cost model attached to the profile: an exposure unit (“per event”, “per operating day”), parameters (“imbalance penalty $/MWh”, “cost of an unnecessary protective action”), a mapping from error statistics to expected cost, and a source note for every parameter. Choosing the Impact lens opens with the assumptions, not just the number.
Defaults are illustrations, and say so. No supplier of validated cost parameters exists yet (decision, §12), so v1 ships illustrative defaults: order-of-magnitude parameter sets per preset, sourced from public literature where possible, every one user-adjustable in place. The framing is fixed accordingly — figures are labeled Illustrative estimate — theoretical, based on adjustable assumptions, the assumptions drawer opens on first use of the lens, and the disclaimer travels with shared links. The lens's honest job at this stage is to teach how skill converts to consequence and let users bring their own numbers — not to assert what anything costs.
Ranges, labels, and challengeability. Figures render as ranges (parameter bounds propagated through the mapping). Parameter edits recompute live and travel with the shared profile — turning a suspicious number into a sensitivity analysis. The technical rendering of the underlying statistic stays one gesture away, so an expert can always interrogate the conversion. A confidently wrong dollar figure is the failure mode this section exists to prevent; when a profile has no cost model, the Impact lens says exactly that instead of improvising one.
The brief describes the product; MET describes what verification can honestly say. Several MET/METplus capabilities the brief never mentions bear directly on its central goal — fair, use-case-specific, region-specific comparison — and most cost little to adopt because they arrive as new rows in the existing store contract, not new architecture. Two deserve the headline:
Fairness across model resolutions is a MET capability, not a UI nicety. Point-to-point scoring double-penalizes a high-resolution model — a storm predicted correctly but displaced one grid cell scores worse than no storm at all — which systematically flatters coarse global models in exactly the comparisons BEACON exists to referee. MET's neighborhood methods (fractions skill score) and station-neighborhood analogue (HiRA) are the community's answer, and a referee comparing 2.5 km regional baselines against 0.25° global AI models should adopt them.
MET already ships a weighted composite — use it as the profiles' anchor. MET's skill-score index (the GO-Index family) is a community-defined weighted blend of skill scores across variables, levels, and leads. It is precedent that weighted composites are legitimate verification practice, a shipped preset whose weights BEACON did not choose, and — because MET can compute it offline — an end-to-end parity target for the entire profile roll-up engine, extending the trust loop (§4.4) from individual statistics to the composite itself.
| MET capability | Why it serves honest comparison | How it lands | When |
|---|---|---|---|
| Verification regions (masks) | “Best” differs by region as much as by use case; region-conditioned verdicts answer real questions | Region becomes a scope dimension and a profile facet axis (§6.1); spine rows already carry the mask column | Now — data present |
| Skill-score index (GO-Index family) | Community-sanctioned weighted composite; calibrates the roll-up engine | “Reference skill index” preset (§6.2) + composite-level parity check | Now — offline job feeds reference values |
| Neighborhood skill (FSS) | Scale-aware scoring; removes the double penalty that flatters coarse models | New line types in the Tier 1 spine; Compare gains a spatial-scale control where present | Pipeline config |
| HiRA (station neighborhoods) | The point-verification analogue of FSS; fair to high-res models at stations | New line types in the spine | Pipeline config |
| Percentile thresholds | Defines “events” against each model's own climatology, removing systematic-bias advantage in event detection | Pipeline config; threshold provenance in the manifest | Pipeline config |
| Climatology-anchored scores (anomaly correlation) | The headline metric of the AI-weather literature; enables like-for-like comparison with published results | Anomaly statistics in the spine once climatology fields join the pipeline | Pipeline config |
| Ramp-event verification | Scores catching rapid changes — wind ramps, temperature swings — precisely what several profiles value most | Ramp contingency rows feed the event-detection facet | Pipeline config |
| Conditional verification on matched pairs | “How does it do when it actually rains” — stratify by observed conditions, station, or time of day | Tier 2 MPR Parquet (§3.3) + client-side stat jobs | Next campaign — MPR enabled (§12) |
| Object-based verification (MODE/MTD) | Decomposes error into displacement, size, intensity, timing — a plain-language error vocabulary (“right storm, wrong place”) | Tier 4 — object & event tables: Parquet of object attributes and matched pairs (Fig. 1); a browser MODE engine already exists in the surveyed lab | Next cycle |
Only the last row adds a tier; everything else is new rows in existing stores plus facet vocabulary. None of it adds screens — Compare and Evidence absorb these as dimensions and drill-downs, and the scope bar's capability-awareness (§5.4) keeps absent ones honest. This is deliberate: adopting more of MET must deepen the referee, not sprawl the product.
The brief's appendix defines the review test: every “must honor” item satisfied, by mechanisms this document can point to.
| Must honor (brief) | Mechanism here |
|---|---|
| Two audiences, both first-class | Vocabulary lens + per-element register reveal (§5.3); no mode split |
| Eight jobs achievable | Standing / Compare / Evidence surfaces + Profile overlay (§5.1); ensemble job manifest-gated (§5.4) |
| Non-expert first encounter, no terminology | Standing: plain verdict sentence, default profile, zero setup (§5.5) |
| Cascade dependencies feel coherent | Scope bar; unavailable options annotated, not hidden (§5.2) |
| Empty results read as normal | Designed no-data state naming the narrowing choice (§5.4) |
| Analysis-fixed dimensions self-explain | “Set by the analysis” scope segments with inline why (§5.2) |
| Metric direction is a data property | Direction gate in normalization; no user toggle anywhere (§6.3) |
| CI overlap must not imply equivalence | Paired-difference significance; fixed microcopy (§4.3) |
| Valid-time date filtering, bounded | Manifest spans drive date controls (§3.1, §5.2) |
| Cross-metric summary judges each measure on its own terms | Per-facet normalization; matrix cells never share a scale (§5.1, §6.3) |
| Presets + labeled default + custom profiles | §6.2, §6.5; weights set with domain experts, draft-labeled until then |
| Weighted ranking reads as value judgment | Amber convention, always-visible profile chip, “how computed” panel (§5.5, §6.3) |
| Roll-up preserves direction; normalization inspectable | Honesty gates 3–4; pluggable, named normalization (§6.3) |
| Honest degradation under sparse data | Coverage meter, no silent renormalization, verdict floor (§6.4) |
| Profile in shareable state | Preset by id, custom by value, in the URL (§6.5) |
| Cost assumptions visible; estimates labeled | Cost model as visible object; ranges; in-place edit (§7) |
| Impact optional; native stats reachable | Lens, not mode; technical rendering one gesture away (§5.3, §7) |
| Browser-only; graceful loading; no stale flicker | Static tiers (§3); keyed, cancellable queries (§4.1); skeletons (§5.4) |
| Loading / no-data / unavailable distinguishable | Three designed states, manifest-driven third state (§5.4) |
| Engine failure loud; single query quiet | §3.5, §5.4 |
| Shareable URL for full view state | Scope + view + profile + lens serialization (§5.1, §6.5) |
| NSF NCAR brand; WCAG AA; responsive | Token layer; acceptance-gate accessibility; re-rank-don't-shrink (§5.6) |
| Out-of-scope items not designed around | Tiers 3–4 reserved as contract only; roadmap isolated (§11) |
Every predecessor was a prototype, so continuity with prior code carries no weight here — each layer is chosen on current merits among modern, open-source options. The load-bearing fact: the data layer (stores, worker, stat engine) is framework-agnostic by construction, so the UI-framework choice is deliberately the most reversible one on this list.
| Layer | Recommendation | Rationale / alternative considered |
|---|---|---|
| Language & build | TypeScript + Vite | Typed contracts end-to-end (manifest, profile object, query results); Vite is the current open-source standard for SPA tooling. |
| UI framework | Svelte 5 | Compiled, fine-grained reactivity drives the bespoke SVG encodings (verdict bars, significance grid, coverage meter) without virtual-DOM overhead; small bundles suit a browser-only data app; accessible headless primitives via Bits UI. React 19 + React Aria considered: the deepest accessibility-component ecosystem — the right call if complex-widget a11y is weighted above leanness. Either satisfies the design; the architecture doesn't care. |
| Query engine | DuckDB-WASM (Web Worker, single-thread default) | Proven twice against this exact Parquet contract; single-thread avoids isolation-header constraints (§3.5). |
| Verification math | Vendor the MET-parity library as a typed package | Dependency-free ES modules with self-tests; framework-agnostic; parity pedigree is the point (§4). Wrap, don't rewrite. |
| Async/state | URL as source of truth; TanStack Query (framework adapters for both candidates) over the worker | Keyed caching + cancellation gives the no-stale-flicker guarantee near-free (§4.1). |
| Charts | Observable Plot + bespoke SVG components | Plot for statistical series (already proven on this data); bespoke SVG where honesty encodings are the design (verdict bars, significance grid, coverage meter). ECharts considered: capable but heavier, and canvas internals resist the fine-grained a11y and encoding control the signature views need. |
| Styling | Design-token CSS layer (custom properties), NSF NCAR brand tokens | Tokens are the brand + amber-convention enforcement point (§5.6). |
| Testing | Vitest + Playwright + stat-parity suite in CI | The parity suite (client math vs. MET derived rows, on real sanitized fixtures) is the referee's regression test. |
| Fields (Tier 3, reserved) | zarrita.js when the cycle opens | Proven against these stores; nothing to build now (§11). |
Excluded from this cycle by the brief, but the architecture leaves each a clean socket rather than a rework:
ECNT, RHIST, PCT) appear in a campaign. No redesign, new data.MPR emission is enabled (§12), and the Evidence surface receives it as soon as the next campaign is processed (§3.3, §8).Resolved with the product owner, 2026-07-09:
| Decision | Resolution |
|---|---|
| Product & repo name | BEACON GUI (repo: beacon-gui). Every predecessor — including the current visualization — was a prototype; this is BEACON's first product-grade interface, and the name says so rather than implying an evolution of something never released. |
| Preset shipping | Ship draft-labeled (§6.2). The expert weighting session still happens; it just doesn't block launch. |
| Cost models | Illustrative, user-adjustable defaults with prominent theoretical-estimate disclaimers (§7). No parameter supplier exists yet; the lens teaches the conversion and invites the user's own numbers. |
| Matched-pair emission | Enabled. MPR output lands as Parquet in Tier 2 (§3.3) — same engine, same contract as the rest of the stores. |
| Demo deployment | Cloudflare static-snapshot demo (§3.5): spine + manifest bundled with the deploy, larger tiers from the existing R2 bucket, data_mode: "snapshot" badged in the UI. |
| Brand application review | Confirmed: token layer reviewed against the NSF NCAR brand toolkit before visual build-out (§5.6). |
Still open:
| Decision | Notes |
|---|---|
| Production deployment shape | Data and processing live on an on-premise server today (Caddy served the prototype). The store contract runs unchanged there or on object storage; decision deferred until production planning (§3.5). |
| Normalization default | Relative standing (proposed) vs. skill-score-referenced scaling (§6.3). The pluggable design defers this safely — and the MET reference index (§8) now provides a community-defined anchor to evaluate candidates against. |
| Preset weights session | Scheduling the domain-expert review that graduates presets out of draft (§6.2). |