When an observation window ends while a machine is still running, its lifetime is incomplete—but it is not missing. I know the machine survived at least until its last follow-up. Reliability Lab is my portfolio prototype for keeping that fact in the analysis instead of quietly turning it into a failure or dropping it from the cohort.
The repository is explicit about its scope: its fleet, sensor readings and costs are synthetic. It is an educational decision-support prototype, not an operational safety system. I focused this write-up on the implementation choices that can be inspected in code, and on what those choices do not establish.
RELIABILITY LAB / OBSERVATION MODEL
A follow-up ending is not a failureWhat the study actually observes
time →- Machine AFailure observed
- Machine BStill running at last follow-up
- Machine CFailure observed
- Machine DStudy ended first
observed failure right-censored: survived at least this long
- 01 / COUNTWho is still at risk?
duration ≥ event timeA censored machine contributes until its last observed hour.
- 02 / ESTIMATESurvival over time
S(t) = ∏ (1 − dᵢ / nᵢ)Kaplan–Meier steps down only when failures are observed.
- 03 / MODELKeep censoring in the fit
failure: f(t) · censored: S(t)The Weibull likelihood uses an observed failure or a survival bound.
- 04 / EXPLORECompare renewal scenarios
P(T ≤ a + 168 | T > a)Conditional cohort risk feeds a cost simulation with stated assumptions.
Record the end of observation, not an invented lifetime
Each fleet row contains a duration and an event_observed flag. If failure occurs during follow-up, the duration is the observed lifetime and the flag is true. If the study ends first, the duration is the last observed operating time and the flag is false. That second row is right-censored: its unknown failure time is greater than the recorded duration.
The seeded demo generator creates latent Weibull lifetimes and independent administrative follow-up cutoffs, then records the earlier of the two. The analysis receives the recorded duration and event flag; it does not need the generator’s hidden lifetime field. A test removes that optional field before loading a generated cohort, so the input contract can be exercised without leaking synthetic ground truth into the estimator.
This matters because “no failure observed yet” is not the same statement as “this machine will never fail.” Treating every censored unit as a permanent non-failure would understate risk. Treating the follow-up end as if it were a failure would overstate it. The event flag keeps those meanings separate.
Let the risk set shrink at the right time
The Kaplan–Meier estimator updates survival at each observed failure time. At time t, the risk set contains machines whose recorded duration is at least t. If d failures occur among n machines still under observation, survival is multiplied by 1 − d/n.
A censored machine remains in the risk set through its last observed time; after that, the analysis has no basis for saying what happened to it. It contributes exposure without being counted as a failure. The implementation also reports Greenwood-based pointwise intervals, which make uncertainty visible as the cohort thins. The estimate is still only as credible as its observation and censoring assumptions.
The Weibull fit uses the same distinction in a parametric form. An observed failure contributes the event-time density. A censored record contributes the probability of surviving beyond its follow-up time. The code searches a bounded range of shape values and computes the corresponding scale for each candidate. If every record is censored, it rejects the fit rather than returning a deceptively precise answer.
Turn a population curve into a scenario
For a Weibull cohort, the conditional failure probability over a future window is:
P(T ≤ age + horizon | T > age) = 1 − S(age + horizon) / S(age)
The dashboard uses a 168-hour window and evaluates example equipment ages against the fitted cohort distribution. This is a population estimate. The current path does not condition that probability on a particular machine’s temperature, vibration or load, even though the synthetic rows include those fields.
The maintenance simulator then compares run-to-failure, fixed service intervals and risk-trigger intervals. Each simulated replacement renews the lifetime; machines are independent; service and failure costs are fixed. Repeated runs summarize simulated cost, downtime and interventions. Those assumptions make the code easy to explore, but they do not make its lowest-cost scenario a recommended maintenance schedule. The cost inputs are invented for the demo, and the simulation does not model crew availability, spare-part constraints or parameter uncertainty.
Test the mechanics against known synthetic truth
One deterministic test draws 6,000 Weibull lifetimes with a known shape of 2.1 and scale of 1,200 hours, applies independent censoring cutoffs between 400 and 2,500 hours, and fits the censored sample. It checks that the recovered shape is within 0.13 and the scale within 65 hours of the generator values. Other tests cover tied event/censor times, Kaplan–Meier steps, conditional-risk identities and reproducibility of a seeded policy simulation.
The recorded GitHub Actions run passed the test suite on Python 3.10, 3.11 and 3.12 for the reviewed source revision. That supports the implementation’s behavior on its tests; it does not validate a maintenance decision against real equipment.
What I would require before using a real fleet
The synthetic generator uses independent administrative censoring. In a plant, equipment may disappear from follow-up for reasons related to condition, replacement, site policy or sensor failure. That can violate the simple assumption behind these estimates. A real analysis would need a documented cohort, a defensible censoring process, data-quality checks, uncertainty analysis and review by people responsible for the equipment.
I would also compare the small reference implementation with a mature survival-analysis library, inspect calibration and residual behavior on held-out operational data, and test how decisions change when failure, service and downtime costs move. If sensor readings are meant to change a machine-specific risk estimate, they need a validated covariate model; recording them in a CSV is not enough.
The useful engineering boundary is clear: a censored observation is evidence about survival up to a time, not a label for the future. Reliability Lab makes that distinction executable and exposes a path from the estimate to a scenario. Its synthetic evidence is useful for checking the mechanics, while any operational conclusion still needs real data and a validated model.
For a related look at forecasting from incomplete event histories, read how FlowTwin keeps future events out of a process-time prediction or how Aurora isolates invalid telemetry before replay.
The public Reliability Lab repository contains the survival estimators, censored Weibull fit, maintenance simulation and deterministic tests.