---
title: "Foundation Models for Predictive Maintenance"
description: "See where Chronos-2, TimeGPT, MOIRAI-2, and TimesFM 2.5 fit in a TimescaleDB architecture for predictive maintenance, with a worked EV battery example."
section: "Postgres for IoT"
---

> **TimescaleDB is now Tiger Data.**

**Summary:** "Predictive maintenance" is two different problems, with two different answers. **Failure-event prediction**, which classifies whether an asset will fail from labeled failure history, stays with traditional ML (gradient boosting, survival models) by default. **Degradation-trajectory forecasting**, which predicts where a sensor signal is heading, is where zero-shot foundation models like Chronos-2 are a front-runner rather than a fallback: fed the right covariates, they beat tuned, production-grade gradient-boosting pipelines on public benchmarks, including the benchmark of the pipeline's own maintainers. What decides accuracy is less which model you pick than two levers, covariate access and quantile calibration. In a Postgres-native architecture, TimescaleDB stores and prepares the sensor data (hypertables, continuous aggregates) and serves the model's output. The forecasting itself runs as an external inference call, self-hosted or via the Nixtla-hosted TimeGPT API, on any Postgres deployment, whether self-hosted or Tiger Cloud.

## Predictive maintenance is two problems, not one

Most predictive-maintenance discussions blur two tasks that have different data requirements and, as of 2026, different answers.

**Failure-event prediction** asks: *will this asset fail in the next N days?* It's classification or survival analysis, and it needs labeled failure events, meaning records of actual failures tied to the sensor history that preceded them. Zero-shot foundation models don't do this task: they forecast numeric series, not failure labels. If you have a well-understood asset type and a meaningful set of labeled failures, a purpose-built gradient-boosting or survival model is still the right default, and nothing below changes that.

**Degradation-trajectory forecasting** asks: *where is this signal heading?* For battery capacity, vibration amplitude, bearing temperature, or pressure drift, you forecast the trajectory and alert when it's projected to cross a threshold. This is ordinary time-series forecasting, the labels are the sensor series itself, and it's where the 2026 evidence has moved.

The rest of this piece is about the second problem: what the current evidence shows, which levers matter, and how the pattern runs on TimescaleDB.

## What the 2026 evidence actually shows

Until recently, the safe claim was "zero-shot models are viable where no labeled model exists, but a tuned model on your own data usually wins." For covariate-driven trajectory forecasting, that claim no longer holds up against the benchmarks:

- **OpenSTEF's official benchmark**, LF Energy's production short-term energy forecasting pipeline operated by Dutch grid operators, publishes [<u>results</u>](https://openstef.github.io/openstef/user_guide/guides/benchmark_results.html) on 55 real grid targets: zero-shot Chronos-2 posts the lowest probabilistic error (rCRPS 0.044 vs. 0.065 for their trained XGBoost preset, ~32% better). In the maintainers' own words: "Chronos-2, a zero-shot foundation model that is not trained on this data, posts the lowest aggregate rCRPS and rMAE on this benchmark." That's not a vendor benchmark. It's a production pipeline's own team measuring their trained models losing to a zero-shot call. (We [independently replicated](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md) the wind-park portion of this benchmark end-to-end and measured the same ranking and a similar margin.)
- **The FETS benchmark** ([<u>arXiv:2604.22328</u>](https://arxiv.org/abs/2604.22328)) tested 54 energy datasets with an XGBoost baseline tuned harder than most production systems ever are (SHAP-based feature selection, 250 Optuna trials per quantile), and covariate-fed Chronos-2 still beat it by ~23% overall, and by 32-34% on wind and solar. "The baseline just needed tuning" is directly refuted here: it was tuned, exhaustively, and lost.
- **fev-bench** ([<u>arXiv:2509.26468</u>](https://arxiv.org/abs/2509.26468); 100 held-out tasks with confidence intervals) shows the leading zero-shot models roughly 30% ahead of tuned CatBoost and statistical ensembles on probabilistic loss.

The same comparison, seen in the data rather than a metric table: four representative days of day-ahead wind forecasts from our replication, plotting zero-shot Chronos-2 and the trained XGBoost preset against what actually happened. Both models track the ramps; the differences that decide the scores are visible at the edges. The zero-shot model follows the peak harder, and its uncertainty band (the shaded region) widens and narrows with conditions instead of staying fixed. The window wasn't cherry-picked: it's selected programmatically because its error ratio matches the [full 306-day benchmark](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md).

Just as important is where tuned models still win. The boundaries are real and specific:

- **Intermittent retail demand**: on M5-style data, zero-shot Chronos loses to the M5-winning LightGBM lineage by a wide margin ([<u>arXiv:2507.22053</u>](https://arxiv.org/abs/2507.22053)). Hierarchical, promotion-driven, sparse series are still gradient-boosting territory.
- **Market-noise signals**: tuned XGBoost beat Chronos-2 on Belgian grid system imbalance ([<u>ML6/Elia</u>](https://www.ml6.eu/en/blog/chronos-2-meets-the-grid-forecasting-system-imbalance-with-a-time-series-foundation-model)); statistical decomposition remains unbeaten on day-ahead electricity prices ([<u>arXiv:2506.08113</u>](https://arxiv.org/abs/2506.08113)).
- **Univariate evaluations**: most published "baselines beat foundation models" results evaluated the models without covariates, which, as the next section shows, is the whole ballgame for physical systems.

The pattern behind both lists: foundation models win where the signal is driven by measurable external physics and the model can see those measurements. They lose where the signal is sparse, label-structured, or driven by market noise. Most degradation-trajectory forecasting, the physical world running down under measurable stress, sits squarely in the first category.

## The two levers that matter more than model choice

### Lever one: covariates

A sensor trajectory is rarely self-explanatory. Wind power is a function of wind speed; battery degradation is a function of temperature and cycling stress. The forecasting literature has known this for a decade. In the [GEFCom2014 competition](https://robjhyndman.com/papers/gefcom2014.pdf), winning wind-power entries were all pipelines mapping weather forecasts to power, with model class secondary. The foundation-model results now show the same thing sharply:

- In our own energy benchmark, the *same* TimesFM 2.5 checkpoint [scored 41% better](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md) with 11 weather covariates than univariate (and TimesFM's covariate mechanism is only a linear side-channel, so the transformer never even attends to the covariates).
- UniWind ([<u>arXiv:2607.01670</u>](https://arxiv.org/html/2607.01670v1)) found history-only zero-shot foundation models **more than 2× worse** than weather-fed models on wind power.
- The Chronos-2 report ([<u>arXiv:2510.15821</u>](https://arxiv.org/abs/2510.15821)) attributes its largest gains to in-context covariate conditioning. That capability, more than scale, is what separates it from the previous model generation.

The practical rule is simple: never call a foundation model univariate on a physical system if you have the driving measurements. The most common implementation mistake here is feeding the model only the target series while temperature, load, and duty-cycle data sit in the same table.

One more finding worth flagging: "zero-shot" is not one performance tier. On identical inputs in our benchmark, Chronos-2 and the small MOIRAI-2 checkpoint [differed by 2×](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md) in probabilistic error. Pick your model from a current leaderboard for your data regime; don't assume the four models below are interchangeable.

### Lever two: quantile calibration

Trajectory forecasts feed alerts, and alerts live or die on the *uncertainty band*, not the point forecast. A band that's too narrow fires confident false alarms and misses real risks; this is a documented failure mode of gradient-boosted quantile regression in operations. NREL's production solar forecasting system was measured at 48-71% empirical coverage where 90% was claimed ([<u>arXiv:2510.15780</u>](https://arxiv.org/html/2510.15780)).

Two consequences:

- **Foundation models' probabilistic heads are a large part of why they win.** Their quantiles are trained on massive corpora and arrive well-calibrated; per-quantile GBM setups are brittle. In benchmark after benchmark, the zero-shot models' edge is bigger on probabilistic metrics than point metrics.
- **If you stay classical, calibrate.** Split-conformal quantile calibration (CQR) is a cheap post-processing step that fixes the band without touching the median: a Belgian offshore-wind study measured a 12% CRPS improvement ([<u>arXiv:2602.13010</u>](https://arxiv.org/html/2602.13010)), and our own replication on grid data [measured 11.8%](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md), closing about half the gap to Chronos-2 for a few lines of post-processing. The one requirement: fit the calibrator on *held-out* predictions, never in-sample (a boosted model's in-sample residuals are optimistically small, which neuters the correction).

## The four models: what they are and how they differ

| **Model** | **Vendor** | **Architecture** | **Covariate mechanism** | **Open weights?** | **Best-fit integration pattern** |
| --- | --- | --- | --- | --- | --- |
| **Chronos-2** | Amazon Science | Encoder-only, T5-inspired, 120M parameters | Native in-context conditioning on past *and* known-future covariates; the strongest covariate story of the four | Yes, Apache 2.0 (`amazon/chronos-2`) | Self-hosted inference (GPU-fast, CPU-viable); AWS/AutoGluon deployment path |
| **TimeGPT** | [<u>Nixtla</u>](https://www.nixtla.io/) | Transformer encoder-decoder, self-attention | Exogenous variables via API; multivariate and agentic features rolling out in TimeGPT-2.1 | No, API-only, Nixtla-hosted | Hosted API call; your windowed data leaves your infrastructure |
| **MOIRAI-2** | Salesforce AI Research | Decoder-only transformer, multi-token prediction | "Any-variate" attention; covariates are first-class, but sequence length (and inference cost) scales with covariate count | Yes (`Salesforce/moirai-2.0-R-small, uni2ts`) | Self-hosted inference |
| **TimesFM 2.5** | Google Research | Decoder-only transformer, 16K-step context, 200M parameters (down from 500M in TimesFM 1.0) | Linear "XReg" side-channel: an in-context regression on covariates, with the (univariate) transformer forecasting residuals | Yes (`google-research/timesfm`) | Self-hosted; GCP-proprietary path via [<u>AlloyDB</u>](https://docs.cloud.google.com/alloydb/docs/ai/perform-time-series-forecasting) `ai.forecast()` |

Two notes on this table. First, leaderboard positions churn fast. Chronos-2 led GIFT-Eval and fev-bench at its October 2025 release, and newer entrants have already moved the top of the board, so treat any ranking, including the results cited above, as a snapshot. Second, the covariate-mechanism column matters more than the parameter counts that usually headline these comparisons: it predicts both accuracy on covariate-driven tasks and inference cost (in our benchmark, MOIRAI-2's cost per forecast [grew ~6-7×](https://github.com/timescale/forecast-store/blob/main/docs/benchmark_log.md) going from 3 covariates to 11, for no accuracy gain, because its small checkpoint had already saturated).

TimeGPT remains the practical outlier: API-only means a data-egress decision the other three don't require.

## Architecture pattern: where TimescaleDB fits

Foundation models run as external inference, the same way ARIMA and Prophet do in Tiger Data's [<u>forecasting guide</u>](https://www.tigerdata.com/learn/time-series-forecasting). They never run inside the database. TimescaleDB's job is storage and serving: staging the input window (target *and covariates*) and storing and serving the forecasts that come back.

The pattern breaks into four stages:

1. **Feature prep and retention.** A [<u>continuous aggregate</u>](https://www.tigerdata.com/docs/learn/continuous-aggregates) rolls raw, high-frequency sensor readings into the windowed summaries the model consumes, while a [<u>retention policy</u>](https://www.tigerdata.com/docs/reference/timescaledb/data-retention) manages how much raw versus aggregated history stays queryable. Critically, the aggregate should carry the covariate columns, not just the target.
2. **External inference call.** The windowed target *plus covariates* is shaped into the model's input format and passed to the model: a self-hosted Chronos-2, MOIRAI-2, or TimesFM 2.5 endpoint, or a TimeGPT API call.
3. **Write-back, with the full band.** The model's quantile forecasts, not just a point estimate, are written into a TimescaleDB table that answers three questions per row: what moment the forecast is *about*, when it was *made*, and when the database *wrote* it. Append each forecast run rather than overwriting: every run becomes a retrievable **vintage**, and keeping every vintage alongside the actuals that later arrive is what turns accuracy tracking, model comparison, and calibration checks into plain SQL instead of a separate ML-ops project.
4. **Alerting on quantiles.** A downstream check compares the *band* against the threshold: the pessimistic quantile crossing a limit is an early warning with a confidence attached, which is strictly more useful than a point forecast crossing it.

For the schema, compression, and retention detail behind step one, see the [<u>predictive maintenance database architecture guide</u>](https://www.tigerdata.com/learn/predictive-maintenance-database); for step four's alerting mechanics, the [<u>time-series anomaly detection guide</u>](https://www.tigerdata.com/learn/time-series-anomaly-detection-methods-sql-real-time-implementation).

### Where teams start from

Most readers land here from one of three starting points. If you're already running SQL or Python forecasting on TimescaleDB from the [<u>forecasting guide</u>](https://www.tigerdata.com/learn/time-series-forecasting), this pattern is an inference step added on top of a setup you already have. If your sensor data currently sits in a historian, CSV exports, or another time-series store, it needs to land in TimescaleDB first before this pattern applies; the [<u>IIoT database requirements guide</u>](https://www.tigerdata.com/learn/iiot-database-requirements) covers what to look for in that migration, including write-throughput and partitioning needs for a large, heterogeneous fleet. And if you're starting a greenfield sensor-data project, you can design the hypertable and continuous aggregate layer with this pattern in mind from day one.

## Worked example: forecasting EV battery degradation

Here is the pattern applied to a concrete scenario: a fleet of EV batteries with voltage, current, temperature, and cycle-count telemetry, where the goal is forecasting capacity early enough to schedule maintenance before failure.

This is an illustrative walkthrough using representative, TimescaleDB-shaped battery telemetry, not a specific customer's production data. Treat the specific values as representative; the runnable version lives in the accompanying code notebook (link to be added on publish).

### Hypertable schema

`CREATE TABLE battery_telemetry (
  time        TIMESTAMPTZ NOT NULL,
  pack_id     TEXT NOT NULL,
  cell_id     TEXT NOT NULL,
  voltage     DOUBLE PRECISION,
  current     DOUBLE PRECISION,
  temperature DOUBLE PRECISION,
  cycle_count INTEGER
);

SELECT create_hypertable(
  'battery_telemetry',
  'time',
  chunk_time_interval => INTERVAL '1 day'
);`

### Continuous aggregate for the feature window

An hourly rollup is the right grain here: the dynamics that stress a battery (charge cycles, thermal excursions, load spikes) live inside the day, and hourly buckets give the model a rich context (a 60-day window is ~1,440 points) instead of a thin daily one. The rollup carries the target signal *and* the covariates the model will condition on, which is the covariate lever from earlier, applied:

`CREATE MATERIALIZED VIEW battery_hourly_health
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', time) AS hour,
  pack_id,
  avg(voltage) AS avg_voltage,
  avg(temperature) AS avg_temperature,
  max(cycle_count) AS max_cycle_count,
  max(voltage) - min(voltage) AS voltage_range
FROM battery_telemetry
GROUP BY hour, pack_id;`

A [<u>retention policy</u>](https://www.tigerdata.com/docs/reference/timescaledb/data-retention) on the raw hypertable keeps the recent high-frequency window; `battery_hourly_health` retains the long rollup the model reads. One grain doesn't fit every failure mode, and that's fine: the pattern applies per resolution. This hourly series catches operational degradation over days, while slow capacity fade (a months-scale signal) gets the same treatment on a weekly rollup of the same telemetry.

### Inference call, with covariates

The hourly series for a pack is pulled from the continuous aggregate and passed to Chronos-2 as a target plus covariates. Thermal history and cycling stress are the physics driving degradation; leaving them out is leaving accuracy on the table:

`from chronos import BaseChronosPipeline

pipeline = BaseChronosPipeline.from_pretrained(
    "amazon/chronos-2",
    device_map="cuda",
)

# One task: target series plus the covariates that drive it,
# pulled from battery_hourly_health for one pack.
# (See the Chronos-2 model card for the covariate input format.)
task = {
    "target": pack_history["avg_voltage"].to_numpy(),
    "past_covariates": {
        "avg_temperature": pack_history["avg_temperature"].to_numpy(),
        "max_cycle_count": pack_history["max_cycle_count"].to_numpy(),
    },
}

quantiles, mean = pipeline.predict_quantiles(
    [task],
    prediction_length=168,         # hours: a 7-day dispatch horizon
    quantile_levels=[0.1, 0.5, 0.9],
)`

If planned duty cycles are known ahead of time (scheduled routes, charging plans), they belong in the future-covariates slot, since known-future inputs are where Chronos-2's conditioning helps most.

### Write-back: three clocks and the band, appended per run

A forecast row needs three timestamps, because three different questions have three different answers: what hour is the forecast *about*, when was it *made*, and when did the database *write* it?

`CREATE TABLE battery_forecasts (
  pack_id      TEXT NOT NULL,
  target_hour  TIMESTAMPTZ NOT NULL, -- the hour the forecast is about
  available_at TIMESTAMPTZ NOT NULL,  -- when the forecast was made (its knowledge time)
  recorded_at  TIMESTAMPTZ NOT NULL DEFAULT now(),  -- when the database wrote it
  voltage_q10  DOUBLE PRECISION,      -- pessimistic
  voltage_q50  DOUBLE PRECISION,      -- median
  voltage_q90  DOUBLE PRECISION,      -- optimistic
  model_name   TEXT NOT NULL,
  PRIMARY KEY (pack_id, model_name, target_hour, available_at)
);

INSERT INTO battery_forecasts
  (pack_id, target_hour, available_at,
   voltage_q10, voltage_q50, voltage_q90, model_name)
VALUES
  ('pack-4471', '2026-09-08 14:00+00', now(),
   351.2, 361.8, 370.4, 'chronos-2');
-- recorded_at is never written by the client: the database stamps it.`

Two deliberate choices here. The table is append-only: a new forecast run inserts rows under a new `available_at` (a new vintage) and never updates old ones, so every forecast the system ever made stays queryable next to the telemetry that later arrived. And `available_at` is distinct from `recorded_at`: they coincide in live operation, but the moment you backfill history or replay a backtest, "when was this knowable" and "when did my database learn it" diverge, and keeping both is what lets the write-back table answer *did we alert late because the model was wrong, or because the data arrived late?* That habit pays for itself the first time someone asks "how accurate were last quarter's forecasts?" The answer is a join, per model, per lead time, in SQL.

### Alert check: the forecast in force, on the pessimistic quantile

The alert should run against the latest vintage per pack and hour as of now, which is one `DISTINCT ON` read with no correlated subqueries:

`WITH current_forecast AS (
  SELECT DISTINCT ON (pack_id, target_hour)
         pack_id, target_hour, voltage_q10, voltage_q50
  FROM battery_forecasts
  WHERE target_hour BETWEEN now()          -- the hours being alerted on...
                    AND now() + INTERVAL '7 days'
    AND available_at <= now()              -- ...and the knowledge cutoff
  ORDER BY pack_id, target_hour, available_at DESC
)
-- Early warning: the pessimistic (q10) trajectory breaches the pack's
-- operating floor within the 7-day horizon — enough lead time to pull
-- the pack into the next maintenance window.
SELECT pack_id, target_hour, voltage_q10, voltage_q50
FROM current_forecast
WHERE voltage_q10 < 350;`

Alerting on `q10` rather than the median means the alert carries its own confidence statement: "even in the ~90th-percentile-bad scenario, this pack breaches the floor." Tuning the alert becomes choosing a quantile, which is a far better knob than a fudge factor on a point forecast. It's exactly why the calibration lever matters: a miscalibrated q10 makes this query fire wrong.

The `available_at <= now()` cutoff looks redundant today and becomes the whole point later: replace every `now()` with a past timestamp and the identical query replays exactly what the system believed at that moment, which turns "would this alert policy have caught pack X days earlier?" from a data-archaeology project into the same query with a different parameter.

Heterogeneous EV fleets (mixed chemistries, pack ages, and usage patterns) are the *strongest* fit for zero-shot models, since nobody builds a labeled degradation model per chemistry-vintage combination. But don't assume "classical wins" is the safe default for a homogeneous, data-rich fleet either: benchmark both.

On the database side, this workload holds up at scale in production: Octave runs [<u>high-compression, high-query-volume battery analytics</u>](https://www.tigerdata.com/blog/high-compression-ratio-and-speedy-queries-on-historical-data-while-revolutionizing-the-battery-market) on TimescaleDB for the second-life battery market, and Kempower runs [<u>sub-second queries and 93% compression across 20,000+ EV charging points</u>](https://www.tigerdata.com/case-studies/kempower) on Tiger Data. Neither is a claim that either company runs foundation-model forecasting today; they're evidence the storage and query layer this pattern depends on is production-proven at battery and EV scale.

## Decision framework: which approach fits your problem

### Choose classical SQL/statistical forecasting if:

- You need a simple trend or seasonal forecast and don't need to model complex dynamics. Moving averages, linear regression, and exponential smoothing are all native in Postgres. See the [<u>forecasting guide</u>](https://www.tigerdata.com/learn/time-series-forecasting) for the decision framework between these approaches, or [<u>what time-series forecasting is</u>](https://www.tigerdata.com/blog/what-is-time-series-forecasting) for the underlying definitions.

### Choose traditional ML (gradient boosting, survival models) if:

- Your task is **failure-event prediction**: classifying failures from labeled failure history. Foundation models don't do this task.
- Your signal looks like the regimes where tuned models still win: sparse or intermittent series, hierarchical demand, market-noise-driven targets.
- You need interpretability, meaning the ability to point to which input (temperature, load, cycle count) drove a specific alert, which a hand-built, feature-based model gives you more directly than a foundation model's forecast.
- Either way: **calibrate the quantile band** (split-conformal, fitted on held-out predictions). It's a cheap, large improvement to any classical probabilistic pipeline, and your alerts run on the band.

### Choose a zero-shot foundation model if:

- Your task is **degradation-trajectory forecasting** and you have the driving covariates (temperature, load, weather, duty cycle) to feed it, which is a front-runner configuration even against tuned models with ample history.
- You're managing a **heterogeneous fleet** without per-asset-type labels, the classic fit, since building a separate labeled model per asset type doesn't scale.
- Within the choice: self-hosting points to Chronos-2 (strongest covariate conditioning), MOIRAI-2, or TimesFM 2.5; comfort with a hosted API points to TimeGPT. Check a current leaderboard for your data regime: the models are not one tier, and the board moves quarterly.

## Getting started

The pattern is four stages: a continuous aggregate prepares the feature window (target **and covariates**) with a retention policy managing history; an external inference call runs the forecast; the quantile band writes back into TimescaleDB with its knowledge time, appended per run as a vintage; and an alert fires on the pessimistic quantile of the forecast in force.

You'll work with three TimescaleDB capabilities directly: [<u>continuous aggregates</u>](https://www.tigerdata.com/docs/learn/continuous-aggregates) for feature prep, [<u>retention policies</u>](https://www.tigerdata.com/docs/reference/timescaledb/data-retention) for raw-versus-aggregate history, and [<u>hyperfunctions</u>](https://www.tigerdata.com/docs/reference/timescaledb/hyperfunctions) for time-series feature computation. If you're evaluating whether to run this kind of sensor workload on Postgres, you can try the pattern on Tiger Cloud; for the complete runnable EV battery example including the calibration and baseline-comparison code, see the accompanying code notebook (link to be added on publish).