Skip to content

Preventive maintenance compliance

Are we doing the preventive work we planned, when we planned it?

Current value (maintenance case data): 86.6% - ! Watch. Open the case.

Field Value
Definition Preventive work orders completed no later than due date + tolerance, divided by preventive work orders due in the period.
Formula count(preventive AND completed_at <= due_date + tolerance_days) / count(preventive due)
Unit %
Granularity Preventive work order; reported monthly by asset and crew.
Direction Higher is better ↑
Target and thresholds ≥ 90.0% (amber ≥ 80.0%)
Parameters tolerance_days = 3
Owner Maintenance Planner
Source Work-order generator (synthetic)
Input table work_orders, columns wo_type, due_date, completed_at
Recommended chart bar - Per-asset comparison sorted ascending; the worst assets are the action list.
Related KPIs Planned maintenance share, Mean time between failures (MTBF)

How to read it

A leading indicator of reliability. Late or skipped PMs show up as failures weeks later, so a drop here predicts a drop in MTBF.

Common misreadings

  • Counting a PM done three weeks late as "done" hides the risk window; the tolerance makes lateness explicit.
  • Rescheduling the due date to the completion date makes compliance 100% by construction - audit date changes.

Worked example (from the test suite)

wo_type due_date completed_at
preventive 2024-03-01 2024-03-01
preventive 2024-03-10 2024-03-13
preventive 2024-03-20 2024-03-24
preventive 2024-03-25 –
corrective 2024-03-05 2024-03-30

Parameters: tolerance_days = 3

Expected result: 50.00%. Tolerance 3 days. PM1 on the day: on time. PM2 +3 days: on time (limit inclusive). PM3 +4 days: late. PM4 not done: late. Corrective ignored. 2 / 4 = 0.5.

This example is asserted in tests/test_formulas.py against the Python function and in tests/test_sql.py against the SQL below.

Implementations

Python function (the single implementation used by the site):

@kpi("pm_compliance")
def pm_compliance(df: pd.DataFrame, tolerance_days: float) -> float:
    pm = df[df["wo_type"].eq("preventive")]
    return safe_ratio(pm_on_time(pm, tolerance_days).sum(), len(pm))

Reference SQL (tested on SQLite):

SELECT CAST(SUM(CASE WHEN completed_at IS NOT NULL
                      AND julianday(completed_at) <= julianday(due_date) + 3 THEN 1 ELSE 0 END) AS REAL) / COUNT(*)
FROM work_orders WHERE wo_type = 'preventive'