Most "ML in the browser" demos stop at classifying a cat photo. I wanted tobuild the whole product: a gas-well predictive maintenance console where thedataset generator, three neural models, live inference and the recommendationengine all run client-side. No server. No API keys. Open the page and the modelstrain in ~10 seconds on your GPU.
🔗 Live demo: https://alicelabs-llc.github.io/vigia-ml/💻 Code: https://github.com/alicelabs-llc/vigia-ml
The pipeline: 5 layers, each with a statistical twin
Layer Job ML model Statistical fallback
N1 Forecast 6/12/24 h projection of 6 sensor variables LSTM(28) + recursive rollout, calibrated 80% CI Damped Holt (α=0.32, β=0.11, φ=0.93)
N2 Anomaly Index 0–100 + per-variable contributions Dense autoencoder 180→72→20→72→180 Multivariate z-scores (120 min window)
N3 Diagnosis Fault hypothesis with physical evidence 5-class softmax over 20 physical features 23-rule physics engine v2.4
N4 Projection P(threshold crossing in 24 h) + ETA Monte Carlo over the forecast same over Holt
N5 Actions Prioritized recommendations fusion of N3 + rules same
The design rule: every ML layer degrades to its statistical twin. If WebGLfails, if the model is still training, if inference throws — the console isnever blank.
Making training non-blocking (the hard part)
model.fit() freezes the main thread. The engine instead:
trains batch by batch with trainOnBatch() and yields withtf.nextFrame();
runs the whole engine inside a module Web Worker — the UI stays at60 fps even on the CPU backend;
enforces a time budget per phase: a model that exceeds its quota stopsearly and ships with what it learned;
probes WebGL first: if the backend declares support but doesn't actuallycompute, it falls back to CPU with a reduced dataset.
One subtle bug: retraining aborted the old engine but never disposed itsworker — every retrain leaked one. Abort + dispose now.
The data problem: simulate physics, not noise
Synthetic telemetry from random noise teaches nothing. The simulator insteademits operational regimes: steady production, liquid loading (risinggradient → falling P-tubing and gas rate), line restriction, sensor failure(frozen signal — trivial to reconstruct, so the AE never learns to flag it; adedicated frozen-signal detector does), control problems.
52 training wells → ~680 forecast windows, ~820 normal windows, ~1.1k labeledwindows, split by well to avoid leakage, saturated regimes oversampled.Everything seeded: same browser, same training curves.
Calibrated uncertainty without a Bayesian model
The LSTM forecast grows an 80% CI from residual σ measured per (step, variable)on validation — and widens with √(rollout blocks) as the recursive rolloutaccumulates drift. Not Bayesian, honest, and cheap.
What it can't do (documented, not hidden)
Telemetry is synthetic; the ≥95% validation accuracy is a simulator artifact. Areal supervised "failure in X hours" model needs documented interventionhistory. Recursive rollouts drift at 24 h — the CI reflects it. All of this isin the README's Limitations section, because the fastest way to lose anindustrial audience is pretending otherwise.
Try the interesting part
Open the demo, wait for the LSTM · TF.JS chips (~10 s), pick a well, injectLiquid loading, and watch N2 → N3 → N5 react within a minute. Then trySensor failure and notice the frozen-signal detector beat the autoencoderto it.
Everything is source-available (AL-1.0): simulator, dataset generator, models,worker plumbing and 39 Vitest tests. Feedback welcome in GitHub Discussions —especially from production engineers: do the diagnoses match what you see inthe field?

Top comments (0)