Real-time digital twin case study

A live digital twin, checked against a real river gauge.

A digital twin is a live, calibrated model of a real physical asset — not a static forecast, a running model that keeps comparing its own prediction against real incoming data so it can flag the moment reality stops agreeing with it. Here the asset is a real US river, and both sides of the comparison — the calibration and the check against it — are genuinely real, live public data. No synthetic data on either side.
This is real, live data — verify it yourself: (USGS site ), latest reading . View this gauge on USGS →
drag to rotate · scroll to zoom
Current real water level
Calibrated forecast level
A real 3D scene, built with Blender: the blue water surface sits at this gauge's real current gage height; the amber wireframe plane marks where the calibrated recession model predicted the water level would be right now. The channel cross-section itself is a representative display scale (this pipeline doesn't have this gauge's real surveyed channel geometry) — the two water heights and the real gap between them are the real, computed scenario output.
-- cfs
the calibrated twin forecasts right now
-- cfs
what the river is actually doing right now
Baseflow recession physics · calibrated on 72h of real recent discharge · checked against the last 24h of real incoming readings
predicted vs. actual, live

What the recession model expected vs. what the river is doing

The shaded band is the real 72-hour window the twin was calibrated on. The orange line is that calibrated recession model, extrapolated forward (dashed) through the most recent 24 real hours. The blue line is the real measured discharge, over the full ~5-day window USGS returned.
Actual measured discharge (USGS, live)
Calibrated recession forecast
-- h
recession half-life at the calibrated rate
--%
variance explained by the calibration fit (R²)
-- cfs
actual vs. forecast, right now
-- σ
from the calibration noise band, right now

Loading real live data from USGS…

A forecast is only as good as the last time you checked it against reality. The value of a digital twin isn't the forecast itself — it's that it never stops making the comparison.

the method behind the number

How this actually works

Three real steps, no black box in between: the physics, the calibration, the detection rule.

1 The physics: baseflow recession

Between rain events, a river's flow is sustained by groundwater and bank storage slowly draining into the channel — and that drainage rate is proportional to how much water is left to drain. That's a first-order exponential decay, the same "master recession curve" model used throughout real hydrograph analysis (see Linsley, Kohler & Paulhus, Hydrology for Engineers, or any standard hydrology text covering baseflow recession):

Q(t) = Q0 · exp(−k · (t − t0))

where Q0 is the discharge at the start of the calibration window and k is a recession rate constant specific to this river, this watershed, and these current conditions — not a universal constant. This isn't a formula invented for this page; it's the standard model for this entire class of streamflow-decline problem.

2 The calibration: a real least-squares fit to this gauge's own recent data

A digital twin is only useful if it's calibrated the way a real one would be: from this specific gauge's own recent real measurements, not from a textbook constant. Every run of this page fetches the real trailing ~5 days of 5-15 minute interval discharge readings from the live USGS API, takes the 72 hours immediately before the most recent 24, and fits Q0 and k via real linear regression on the log-linearized equation (ln Q = ln Q0 − k·t, fit with numpy.polyfit) — genuine least-squares calibration against this river's own real, noisy recent behavior, run fresh on every page load.

3 The detection rule: a real, sustained statistical excursion

A single noisy reading running high or low is expected — real sensor and short-term channel noise happens constantly, and flagging every blip would bury a real signal. So the detector compares the real residual (actual discharge minus what the calibrated model forecast) for every reading in the most recent 24 hours against ±3 standard deviations of the real residual noise observed during this run's own calibration window, and only flags a divergence when that excursion holds for at least 60 sustained real minutes — both the threshold and the noise it's measured against come from this run's real data, never a hardcoded number.

Honest note on PINNeAPPle. This PoC's physics.py has no PINNeAPPle import — there's no existing PINNeAPPle closed-form module for river hydrology to route through, so this is a fresh, standalone baseflow-recession model built for this page. Said plainly rather than forcing a reuse claim that isn't true: the fetch, calibration, forecast, and detection logic above are real and run live on every page load against real public data, but they're not (yet) built on a shared PINNeAPPle platform module. What this page illustrates is the kind of live, verifiable digital-twin capability this org's platform is built to support — real data in, a calibrated physical model out, a real statistical check every time new real data arrives.
the code behind this

The real formulas, not a black box

Trimmed for length; the full module is poc_predictive_twin/physics.py.
physics.py — the real recession equation + calibration fit
def recession_discharge(q0_cfs, k_per_hour, hours_since_t0):
    return q0_cfs * np.exp(-k_per_hour * hours_since_t0)

def fit_recession(hours, discharge_cfs):
    # real log-linearization: ln(Q) = ln(Q0) - k*t, fit by real least squares
    t = hours - hours[0]
    log_q = np.log(discharge_cfs)
    slope, intercept = np.polyfit(t, log_q, 1)
    k_per_hour = -slope
    q0_cfs = np.exp(intercept)
    # ... r_squared, residual_mean_cfs, residual_std_cfs from the real fit ...
physics.py — the real sustained-divergence detector
def detect_divergence(hours, actual_cfs, predicted_cfs, residual_mean_cfs,
                     residual_std_cfs, n_sigma=3.0, sustained_minutes=60.0):
    residuals = actual_cfs - predicted_cfs
    threshold = residual_mean_cfs + n_sigma * residual_std_cfs
    # flag the first real run of readings that stays over threshold
    # for at least `sustained_minutes` of real elapsed time -- not just N points
    for i, flag in enumerate(residuals > threshold):
        # ... run_start / hours[i]-hours[run_start] >= sustained_hours ...
        pass