Methodology

About YieldGuard

The engineering story behind the predictions — data, features, training, deployment.

What we built

YieldGuard is an end-to-end predictive maintenance system that ingests sensor streams from industrial machines (PLCs, SCADA, IoT edge devices), engineers 256+ time-series features, and predicts equipment failure 24 hours in advance with a calibrated probability score.

The system runs entirely in the browser. The LightGBM model is exported to a compact JSON format and scored via a TypeScript tree-walker — no API round-trips, no cold starts, no data leaving the user's device.

Training data

Training data was generated by a physics-informed synthetic generator (`src/yieldguard/data/synthesizer.py`). 50 machines × 70 days × 10-minute intervals = 504,000 rows across 6 sensor channels.

Each machine follows a physical signal model with per-channel degradation physics — exponential amplitude ramps for vibration/acoustic, temperature rise via bearing friction, RPM drift. Failure events are randomized (onset timing, magnitude, duration) to avoid a trivially separable dataset.

Signal model:
  X(t) = μ_machine + seasonal(t) + degradation(t, t_fail) + noise(t)

Degradation onset:   ~48 hours before failure (randomized ±24h)
Magnitude factors:   vib ×2.5, temp ×1.8, aco ×2.2 (at peak)
Hard negatives:      5–8 recoverable excursions per machine
Label noise:         5% random flip on boundary samples
Positive class:      ~9.3% of total rows

Feature engineering

FeatureEngineer(TransformerMixin) computes 256+ features per row. All temporal operations are performed inside groupby('machine_id') — never on a flat concatenated DataFrame — to prevent cross-machine data leakage.

The engineer is joblib-serialized alongside the model at training time and loaded together at inference. This guarantees the exact same feature computation between training and serving.

Rolling stats × 4 windows [6, 12, 36, 144] samples
  → mean, std, range, skew, kurt             (30 per channel = 180)

EWMA × 2 spans [12, 72] + deviation          (4 per channel  = 24)

Lag + diff + pct_change × 4 lags             (12 per channel = 72)
  pct_change clipped to [-10, 10] (avoids ±∞ at near-zero)

Rate of change (raw + smoothed)              (2 per channel  = 12)

FFT: energy, dominant_hz, spectral_entropy   (3 per channel  = 18)

Cross-channel:
  vibration × temperature coupling
  current draw relative to RPM (power proxy)
  pressure / temperature ratio               (4 features)
                                         ───────────────
                                         Total: 310 → curated to 256

Training methodology

Cross-validation uses TimeSeriesExpandingCV with a 24-hour gap between train and validation folds. The gap prevents the model from seeing the run-up to a failure event in both sets simultaneously.

Class imbalance (~9% positive) is handled via scale_pos_weight ≈ 9.8 — no oversampling or SMOTE. The primary metric is PR-AUC (average precision), which is more informative than ROC-AUC for imbalanced data.

Optuna TPE Bayesian HPO runs 50 trials per model, maximizing PR-AUC across folds. The final model is refit on all training data using the median best_iteration across folds (rather than allowing early stopping on a combined dataset, which would cause overfitting).

Probabilities are isotonic calibrated on a held-out fold — the model's raw sigmoid output is a ranking score, not a probability; calibration makes the output honest.

Strategy:    TimeSeriesExpandingCV — 5 folds, 24h gap
Objective:   PR-AUC (primary), ROC-AUC (secondary)
Imbalance:   scale_pos_weight = n_neg / n_pos ≈ 9.8
HPO:         Optuna TPE, 50 trials, pruning enabled
Refit:       n_estimators = median(best_iteration_ across folds)
Calibration: IsotonicRegression on held-out fold

Model performance

Evaluated on a time-series holdout (last 20% of the data, never seen during training or HPO). All metrics shown at the tuned operating threshold.
LightGBM
0.8561
PR-AUC
0.9753
ROC-AUC
0.614
Threshold
270
Trees
XGBoost
0.8567
PR-AUC
0.9746
ROC-AUC
0.612
Threshold
647
Trees
These metrics reflect the synthetic validation set. Real-world performance on your specific machine class will vary — we recommend fine-tuning on historical failure data for production use.

In-browser inference

The trained LightGBM model is exported via booster_.dump_model() to a compact JSON structure and served as a static file. A TypeScript tree-walker (web/lib/engine/model.ts) scores the ensemble by recursively walking split nodes, summing leaf values, applying sigmoid, then isotonic calibration.

The feature pipeline (web/lib/engine/features.ts) is a port of the Python FeatureEngineer — rolling statistics, EWMA, lag/diff/pct (clipped), ROC, and a direct DFT (FFT without scipy) for spectral features.

Export:    lgb_model.booster_.dump_model() → model.json (~800 KB)
           isotonic calibration params → calibration_x/y arrays
           feature names + healthy baseline stats → feature_spec.json
           5 demo machine series → demo_scenarios.json

TS scorer: O(n × depth) per tree — ~1000 trees × depth 6
           Total inference: <50ms on modern hardware
           No WASM, no server, no API — pure TypeScript

Tech stack

ML / Python
XGBoostLightGBMscikit-learnOptunaSHAPNumPyPandasSciPy
Serving
FastAPIPydantic v2joblibuvicornDockerRender
Frontend
Next.js 15TypeScriptTailwind CSSframer-motionRechartslucide-react
In-browser ML
LightGBM JSON exportTypeScript tree scorerDirect DFT (FFT)Isotonic calibration

Architecture

Browser (Next.js on Vercel)
  Demo page ──┐
  Dashboard ──┼──► web/lib/engine/
              │     features.ts  — 256+ feature computation
              │     model.ts     — LightGBM tree scorer + calibration
              │     explain.ts   — risk driver ranking
              │     predict.ts   — orchestration
              │       ▲ reads static JSON artifacts (no API)
              │
              └ web/lib/engine/
                  model.json          — LightGBM dump_model() export
                  feature_spec.json   — feature metadata + baselines
                  demo_scenarios.json — 5 sample machine histories

Python (offline training — source of truth)
  synthesizer.py    — physics-informed synthetic data
  preprocessor.py   — cleaning, imputation, stuck sensor detection
  engineer.py       — FeatureEngineer(TransformerMixin) → 256 features
  trainer.py        — CV + Optuna HPO + calibration
  export_model.py   — writes engine JSON artifacts

FastAPI (Render — optional, not on critical path)
  POST /predict → PerMachineBuffer(288) → features → model → response
  POST /explain → SHAP TreeExplainer (on demand)
  GET  /drift   → PSI + KS drift report