Better Predictive Maintenance with Nori
Learn what predictive maintenance is and how to use Nori to predict whether equipment will fail within an upcoming maintenance window.
The task: predict failure within a maintenance window
Predictive maintenance uses sensor readings, operating conditions, inspection records, and other historical signals to identify failure risk before an outage occurs. Teams can use that warning to inspect equipment, schedule service, or reserve capacity, whether the system is a production machine, vehicle, battery, or server fleet.
In this guide, we formulate predictive maintenance as a specific question: will this system fail within the next 30 operating cycles?
We demonstrate the approach on NASA's public FD001 turbofan simulation benchmark, which provides run-to-failure histories for 100 training engines and a separate 100-engine test fleet.
For each engine, we use Nori's continuous output as a failure score between 0 and 1. Higher scores indicate greater modeled risk of failure inside the window; the score is not necessarily a calibrated probability. A threshold selected on validation data converts it into an alert or no-alert decision.
If the decision instead needs an estimate of how much operating life remains, see the sibling blog post, Better Remaining Useful Life Prediction with Nori.
Why it has been hard
Predictive-maintenance data is difficult because normal operation is abundant while confirmed failures are rare. Machine histories have different lengths, nearby sensor readings are highly correlated, and a useful alert must arrive early enough to support action without overwhelming the team with false alarms. A conventional workflow adds task-specific model training, class weighting, hyperparameter tuning, calibration, and repeated retraining as the fleet changes.
How Nori changes it
Nori reads labeled machine snapshots—including the scarce failure examples—as context at inference time. It scores a new machine from the relationships among operating conditions, sensor trajectories, and past outcomes, without fitting a new task-specific model. On this benchmark, it makes better use of those examples than the tuned XGBoost baseline.
Teams still define the maintenance window, create time-aware features, and validate the alert threshold. Nori removes the separate model-training and hyperparameter-search stage.
Building the context
The context table should reflect what the maintenance team knows at scoring time. We build one row for each machine snapshot, with feature columns that describe the machine's current state and how its behavior has evolved. For failure-window prediction, the label records whether failure followed inside the chosen window.
| Machine | Operational context | Current state | Evolution through time | Nori output | ||||
|---|---|---|---|---|---|---|---|---|
| Age (cycles) | Sensor 2 current | Recent 5-cycle level (vs. history) | Recent 5-cycle shift (vs. prior 5) | vs. own history | Normalized slope | Out-of-range run | Failure score (continuous) | |
| Engine 58 | 176 | 643.01 | 1.46 | 0.42 | 1.49 | 0.012 | 1% | 0.510 |
| Engine 41 | 123 | 642.54 | 1.42 | 0.62 | 0.51 | 0.014 | 3% | 0.525 |
| Engine 61 | 159 | 643.29 | 1.44 | -0.20 | 2.31 | 0.014 | 3% | 0.666 |
| Engine 94 | 133 | 642.77 | 0.14 | -0.85 | 0.11 | 0.011 | 2% | 0.075 |
Figure 1 — Each row represents one machine snapshot. For FD001, three operating settings and the 14 sensor channels that change under its single operating condition feed three feature blocks: operational context describes where the machine is operating; current state captures its latest and recent behavior; and evolution through time summarizes longer-term level, direction, persistence, and volatility. Nori returns a continuous failure-window score, which an alert threshold—0.50 in this illustration—converts into an operational 0/1 decision.
The expandable snippet below shows one way to build a representative subset of the columns in Figure 1.
View the feature-building code snippet
1import numpy as np
2
3def operational_context(snapshot):
4 return {"age_cycles": snapshot["cycle"]}
5
6
7def current_state(sensor_2_history):
8 values = np.asarray(sensor_2_history, dtype=float)
9 center = values.mean()
10 scale = values.std() or 1.0
11 recent_5 = values[-5:]
12 previous_5 = values[-10:-5]
13 return {
14 "sensor_2_current": values[-1],
15 "sensor_2_recent_5_level": (recent_5.mean() - center) / scale,
16 "sensor_2_recent_5_shift": (recent_5.mean() - previous_5.mean()) / scale,
17 }
18
19
20def evolution_through_time(sensor_2_history):
21 values = np.asarray(sensor_2_history, dtype=float)
22 center = values.mean()
23 scale = values.std() or 1.0
24 standardized = (values - center) / scale
25 longest_run = run = 0
26 for outside_range in np.abs(standardized) > 2:
27 run = run + 1 if outside_range else 0
28 longest_run = max(longest_run, run)
29 return {
30 "sensor_2_vs_history": standardized[-1],
31 "sensor_2_normalized_slope": np.polyfit(np.arange(len(values)), values, 1)[0] / scale,
32 "sensor_2_out_of_range_run": longest_run / len(values),
33 }These columns are only an illustrative subset. The benchmark also measures the share of recent observations outside the machine's earlier normal range, along with longer-history direction, persistence, and volatility. Engineering knowledge, exploratory findings, known degradation or failure mechanisms, and research from the problem domain can suggest additional features that make degradation easier to recognize. Across the 14 changing sensors and operating context, the resulting table contains 172 numeric inputs.
The exact summaries will vary by system. Rotating equipment may use vibration bands, crest factor, or spectral kurtosis; batteries may use capacity fade, resistance growth, charge-rate exposure, and thermal excursions; server fleets may use saturation duration, load slope, error bursts, and restart frequency. The transferable idea is to turn a changing history into a fixed row that captures the patterns relevant to the system.
How to turn Nori's score into a maintenance alert
Nori is a pretrained tabular foundation model. Its fit() call stores labeled machines as in-context examples.
1from synthefy_nori import NoriRegressor
2
3model = NoriRegressor(
4 model="nori-6m",
5 # Optional for datasets with free-text alert or maintenance fields:
6 # text_columns=["recent_alerts", "technician_notes"],
7)
8model.fit(X_train, failure_labels.astype(float))
9raw_failure_score = model.predict(X_test, output_type="mean")
10failure_score = np.clip(raw_failure_score, 0.0, 1.0)
11
12# Convert the continuous score into an operational 0/1 prediction.
13alert_threshold = 0.5 # Replace with a threshold selected on validation data.
14failure_prediction = (failure_score >= alert_threshold).astype(int)FD001 is numeric-only, so the optional text setting stays disabled for this benchmark. On another dataset, repeated alarm codes can be categorical columns, while free-form alerts, log excerpts, or technician notes can be named with text_columns; Nori handles their text preprocessing internally.
Binary labels are supplied as zero and one, but Nori is a regressor and returns a continuous value that can land slightly outside that label range. We clip it to [0, 1] before presenting it as a failure score. Comparing the score with alert_threshold then produces the final class: a score at or above the threshold becomes 1, and a lower score becomes 0.
The 0.5 value makes the code concrete; a production threshold should be selected from out-of-fold predictions on the training fleet. For example, choose the threshold that keeps false alarms below an acceptable rate or produces no more inspections than the team can handle. The official test fleet should not be used to choose it.
XGBoost receives the same table and is tuned on the training fleet before evaluation.
Nori beats the XGBoost baseline
For the benchmark, failure inside the next 30 operating cycles is treated as the positive class. Twenty-five of the 100 official test engines fall inside that window.
Nori reaches 0.968 PR-AUC, compared with 0.945 for the tuned XGBoost, and leads ROC-AUC by 0.006. XGBoost has higher recall at the single 5% false-positive operating point, while precision at 50% recall is tied.
Figure 2 — Results on the official 100-engine test fleet. Nori leads the threshold-independent PR-AUC and ROC-AUC metrics; XGBoost leads recall at the fixed 5% false-positive point, and precision at 50% recall is tied.
Because Nori uses labeled machines as context, teams can update the examples or maintenance window without launching another hyperparameter search or producing a new task-specific model artifact.
The XGBoost baseline is not an untuned default. We searched 500 configurations with four-fold grouped cross-validation on the training engines, selected the best configuration there, and evaluated the official test fleet only after that choice was fixed.
Apply the same playbook beyond maintenance
This walkthrough uses engine sensor data to predict failure inside a future window, but the same playbook can apply across industries and problem types. Wherever a system produces a history of signals, teams can combine its current state, recent behavior, and longer-term evolution into one row, then use Nori to predict the outcome that drives the next decision.
Churn prediction is one example. A row for each account or user could summarize recent usage, longer-term engagement trends, support interactions, plan changes, clicks, and other relevant signals, then predict whether that customer will churn within a chosen window. Similar tables could support incident prediction, fraud detection, delivery-risk scoring, and many other operational decisions.
These are only a few possibilities. We're always interested in learning about novel ways teams are using Nori. Join us on Discord and tell us what you're building.
Run the full example
- Run this post in Google Colab: Open the failure-window notebook · View the source on GitHub
- Sibling post: Better Remaining Useful Life Prediction with Nori
- Dataset: NASA Prognostics Center of Excellence repository
- Nori quickstart: docs.synthefy.com/nori/quickstart
- GitHub: github.com/Synthefy/synthefy-nori
- Model weights: huggingface.co/Synthefy
Questions? contact@synthefy.com

