demo mode: backfill phase mix + knn metric (no clobber on real data)

Two targeted fixes for the demo-toggle path; intentionally narrow so
we don't override widgets that already work in both modes (KNN
scatter, DB explorer).

Phase-mix bar
- Tracks `hasRealMix` and only injects a synthetic fallback on
  demo_start if no real snapshot/phase_mix event has been seen.
  If real data later arrives, applyMix overwrites the synthetic
  value automatically.
- Synthetic numbers mirror a real production run (500/78705
  episodes, ~4.5 hours of weighted seconds) so the bar reads
  correctly during a deck-only demo.

KNN model_metric
- Periodic demoTick tweaks now include `knn` alongside rnn/gru/lstm/
  bert. Initial demo_start already populated all five bars; the
  periodic tweak just keeps the knn bar from sitting frozen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Max Gorog 2026-05-08 16:27:25 -05:00
parent 4b2863ea99
commit af1f7fb56d
2 changed files with 35 additions and 3 deletions

View file

@ -238,8 +238,8 @@
}
// Occasionally tweak a model metric so the bars aren't static.
if (Math.random() < 0.05) {
const m = ['rnn', 'gru', 'lstm', 'bert'][Math.floor(Math.random() * 4)];
const base = { rnn: 0.872, gru: 0.911, lstm: 0.928, bert: 0.954 }[m];
const m = ['knn', 'rnn', 'gru', 'lstm', 'bert'][Math.floor(Math.random() * 5)];
const base = { knn: 0.736, rnn: 0.872, gru: 0.911, lstm: 0.928, bert: 0.954 }[m];
dispatch({ type: 'model_metric', model: m, accuracy: base + (Math.random() - 0.5) * 0.012 });
}
}
@ -1345,8 +1345,15 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
function fmtInt(n) { return (typeof n === 'number') ? n.toLocaleString() : '—'; }
// Tracks whether real phase_mix data has arrived. demo_start uses
// this to decide whether to inject a synthetic fallback — if real
// data is already present (from snapshot or the phase_mix feeder),
// the demo toggle does NOT clobber it.
let hasRealMix = false;
function applyMix(mix) {
if (!mix) return;
hasRealMix = true;
const w = mix.weighted_seconds || {};
const c = mix.counts || {};
// Prefer time-weighted proportions; fall back to label counts.
@ -1378,6 +1385,31 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
on('snapshot', m => { if (m.phase_mix) applyMix(m.phase_mix); });
on('phase_mix', applyMix);
on('demo_start', () => {
// Synthetic fallback: only fires if no real phase_mix has
// arrived yet (e.g. dashboard offline, or feeder hasn't done its
// first compute). The numbers below mirror a real production run
// so the bar reads correctly during a deck-only demo. If real
// data later arrives via snapshot or phase_mix event, applyMix
// overwrites this on the spot.
if (hasRealMix) return;
applyMix({
weighted_seconds: {
clean: 2659.1, armed: 725.7, infecting: 1607.4,
infected_running: 8308.3, dormant: 3059.3,
},
counts: {
clean: 451, armed: 198, infecting: 379,
infected_running: 614, dormant: 223,
},
sampled_episodes: 500,
population_episodes: 78705,
total_labels: 1865,
});
// applyMix flipped hasRealMix to true — flip back so a future
// real arrival is still recognised as the first real one.
hasRealMix = false;
});
on('demo_stop', () => {
// Demo toggle off doesn't wipe the dataset mix — the dataset is
// ground truth, the demo only fakes per-event widgets.

View file

@ -1314,6 +1314,6 @@
</article>
</div>
<script src="/static/dashboard.js?v=7c6859eb"></script>
<script src="/static/dashboard.js?v=883a2c24"></script>
</body>
</html>