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 failure
SYNTHETIC DEMO

What the study actually observes

time →
  1. Machine AFailure observed
  2. Machine BStill running at last follow-up
  3. Machine CFailure observed
  4. Machine DStudy ended first

observed failure right-censored: survived at least this long

  1. 01 / COUNTWho is still at risk?duration ≥ event time

    A censored machine contributes until its last observed hour.

  2. 02 / ESTIMATESurvival over timeS(t) = ∏ (1 − dᵢ / nᵢ)

    Kaplan–Meier steps down only when failures are observed.

  3. 03 / MODELKeep censoring in the fitfailure: f(t) · censored: S(t)

    The Weibull likelihood uses an observed failure or a survival bound.

  4. 04 / EXPLORECompare renewal scenariosP(T ≤ a + 168 | T > a)

    Conditional cohort risk feeds a cost simulation with stated assumptions.

Conceptual map of the reviewed implementation. Timelines are illustrative; the repository uses synthetic observations and costs, so this graphic is not an operational result.

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.

← All notes