/* ── Dataset + Train/Push + Model Registry panels ──
Ported from the Hover design project, extended per the operator's scope-growth ask (hov-zaml.7): a full
model registry (every trained version kept, push ANY of them, not just the newest) with per-model notes. */
const { useState } = React;
// ─────────────────────────────────────────────────────────────
// ZONE 4 — DATASET
// ─────────────────────────────────────────────────────────────
function ClassBalanceBar({ stats }) {
const { counts, total, minorityFrac } = stats;
const warn = total > 0 && stats.classes >= 2 && minorityFrac < 0.30;
// the smallest class that actually has samples -> the one to nudge more of
const present = SARG.classes.filter(c => (counts[c.label] || 0) > 0);
const smallest = present.reduce((a, c) => ((counts[c.label] || 0) < (counts[a.label] || 0) ? c : a),
present[0] || SARG.classes[0]);
return (
Hold the drifter inside a mat and tap IN-MAT, in clear water and tap OPEN WATER, and lift it into the air for OUT OF WATER. Each label appends a span here.
) : (
<>
LabelEndedDurSamplesAvg
{[...spans].reverse().map(s => (
))}
↻Train uses all {stats.spans} spans. It retrains from scratch on everything above — not just the last one you labeled.
>
)}
);
}
// ─────────────────────────────────────────────────────────────
// ZONE 5 — TRAIN + PUSH (the model registry itself is its own zone -- see registry.jsx)
// ─────────────────────────────────────────────────────────────
function ResultPanel({ result, liveVersion, onPush }) {
const good = result.tone === 'good';
const accent = good ? 'var(--sage)' : 'var(--amber)';
const bg = good ? 'rgba(122,166,146,0.08)' : 'rgba(179,135,61,0.09)';
const border = good ? 'rgba(122,166,146,0.3)' : 'rgba(179,135,61,0.35)';
const st = result.dataset;
const alreadyPushed = liveVersion > 0 && result.version === liveVersion;
return (
{alreadyPushed ? (
Pushed to board as model v{liveVersion}
) : (
<>
{good
? 'The board keeps running its current model until you push.'
: 'You can still push, but this model is flagged — watch the live verdict closely.'}
>
)}
{alreadyPushed && (
// Post-push loop-closer nudge (design carry-over, prompt-11): val accuracy is noisy on small field
// datasets -- the live board verdict, watched by hand, is the real ground truth.
↻Now move the drifter in and out of the mat, and watch the Board verdict up top — that live test is the real ground truth.
)}
);
}
function TrainPushZone({ stats, result, training, liveVersion, onTrain, onPush }) {
const canTrain = stats.classes >= 2 && stats.total > 0; // at least 2 of the (up to 3) classes present
const presentNames = SARG.classes.filter(c => (stats.counts[c.label] || 0) > 0).map(c => c.name);
return (
Train & push
{canTrain ? (
<>Will train on {stats.spans} spans · {SARG.classes.filter(c => (stats.counts[c.label] || 0) > 0).map((c, i) => (
{i > 0 && ' / '}{stats.counts[c.label]} {c.short}
))}>
) : stats.total === 0 ? (
Label some readings first — nothing to train on yet.
) : (
Need at least 2 classes. You only have {presentNames.join(', ')} — add another (open water, in-mat, or out of water).
)}