Preventive Maintenance
A Maintenance Schedule is a recurring plan that defines what maintenance to do, when to do it next, and whether the system should auto-generate a maintenance event when it comes due. A schedule fires repeatedly over the life of the asset; each firing creates a MaintenanceEvent row that captures the actual work.
This page covers PM scheduling — the planned, recurring side. For unplanned breakdowns and event logging, see Maintenance Events.
Where it lives
- Create / list: Maintenance Hub at
/equipment/maintenance→ PM Schedule tab → + New PM Plan. - Edit: click any schedule row.
- One-click complete: any schedule whose
next_due_atis within 7 days appears on the Equipment Hub's "Today's PM Schedule" widget with an inline Mark Done popover. - Permissions:
maintenance_readto view,maintenance_writeto create/edit/mark done.
The PM form
Basics
| Field | Notes |
|---|---|
| Name | Human-readable label ("Weekly Press Inspection", "200hr Filter Change") |
| Description | Free-text. Use it to summarize the task. The full task list lives below in Tasks. |
| Asset | The asset this schedule applies to. Required. |
| Assigned to | Operator / technician responsible. Optional. Drives the recipient of maintenance_due notifications. |
Trigger configuration
A Schedule Type segmented control picks one of three trigger modes. The fields below it change based on the choice.
CALENDAR
Recurs on a fixed time cadence.
| Field | Notes |
|---|---|
| Interval | Number (1, 2, 6, etc.) |
| Unit | Days / Weeks / Months |
| Next at | Date the first/next event fires. Auto-rolls forward by interval after each completion. |
Example: "Weekly Press Inspection" → Interval 1, Unit Weeks. After each completed event, next_due_at rolls forward 7 days.
RUNTIME_HOURS
Recurs based on the asset's total_operating_hours counter.
| Field | Notes |
|---|---|
| Interval | Hours between services (e.g. 500) |
| Trigger at | Absolute hour reading at which the next event fires (e.g. 1500 means fire when the asset reaches 1500 running hours). |
Example: "500hr Oil Change". If the asset is at 1023 hours and the interval is 500, set trigger_at_value = 1500. The cron checks daily; when asset.total_operating_hours >= 1500, it generates the event. On completion, trigger_at_value advances to 2000.
v1 limitation: total_operating_hours is manually updated. Until MFI/PLC integration ships, runtime-based PM is only as accurate as the meter readings the operators enter.
CYCLE_COUNT
Same pattern as RUNTIME_HOURS but counted in production cycles instead of hours. Use this when wear is more closely correlated with shots / sheets / strokes than wall-clock running time.
Tasks (checklist)
Optional list of discrete steps the technician should complete. Each row has:
seq— display orderlabel— what to do ("Inspect belt tension", "Replace filter A")done— checkbox the technician ticks during the eventnotes— free-text observations per step
The checklist is stored as JSON on the schedule and copied onto each generated MaintenanceEvent so the technician's checkboxes are scoped to that occurrence. Edits to the schedule's task list don't retroactively change in-progress events.
Use task lists for procedures that have multiple steps; skip them for "go inspect the thing" entries.
Notification
auto_generate_event
When true, the daily 08:00 cron auto-creates a SCHEDULED MaintenanceEvent for any schedule whose next_due_at falls within the next 7 days. When false, the schedule shows up in alerts but a human has to manually create the event from the schedule row.
Most teams want this on — it surfaces the work as a real task in the events tab rather than just an alert that gets dismissed.
One-click Mark Done
For routine PMs, the full event-logging modal is too much friction. The Equipment Hub's "Today's PM Schedule" widget gives each due schedule a Mark Done popover with just two fields:
- Optional notes (e.g. "Replaced filter; belt tension fine")
- Save
Clicking Save does all of this in one round-trip:
- Creates a
MaintenanceEvent(typePM, statusCOMPLETE, started_at = ended_at = now). - Posts an
AssetTransactionof typeMAINTENANCElinked to the event. - Updates the schedule's
last_done_atto now and advancesnext_due_atby the configured interval. - Fires a
maintenance_completedSSE event so the hub updates instantly.
For non-routine maintenance (root cause analysis, parts used, real downtime to record), use the full event modal — see Maintenance Events.
Cron behavior
| Schedule | Cron | What it does |
|---|---|---|
support-ms 08:00 daily | 0 8 * * * | Scans MaintenanceSchedule rows. Fires maintenance_due for any within the next 7 days. Fires maintenance_overdue for any past due. If auto_generate_event=true, creates a SCHEDULED MaintenanceEvent. |
support-ms 5-min | */5 * * * * | Recomputes asset and WC runtime_status rollup — affects what shows on the hub but not directly PM. |
maintenance_due / maintenance_overdue events appear in the Equipment Hub's Alerts Feed and (when configured) notify the assigned_workforce.
Setup recipe
A pragmatic starter set for a press line:
| Schedule | Asset | Type | Interval | Auto-generate |
|---|---|---|---|---|
| Daily startup checklist | Heidelberg Press 1 | CALENDAR | 1 Days | yes |
| Weekly press inspection | Heidelberg Press 1 | CALENDAR | 1 Weeks | yes |
| 500hr oil change | Heidelberg Press 1 | RUNTIME_HOURS | 500 hrs | yes |
| 2000hr full service | Heidelberg Press 1 | RUNTIME_HOURS | 2000 hrs | yes |
| Monthly belt inspection | Folder Unit 7 | CALENDAR | 1 Months | yes |
Add task checklists to the longer-running ones (full service); skip checklists for one-step PMs (daily startup).
Troubleshooting
| Symptom | Likely cause |
|---|---|
next_due_at doesn't update after Mark Done | Mutation hit the backend but failed silently. Open browser devtools → Network tab → check the POST /maintenance/schedule/:id/mark-done response. |
| Auto-generated event has no checklist | The schedule was created without one. Edit the schedule, add tasks, save — future events will have them. Past events keep their original (empty) checklist. |
maintenance_due alerts never fire | support-ms cron isn't running, OR the schedule's active flag is false, OR auto_generate_event is true and the event was already created earlier (the alert dedupes on schedule_id within the scan window). |
| RUNTIME_HOURS PM doesn't fire | total_operating_hours hasn't reached trigger_at_value. Update the asset's operating-hours field (operator entry or UpdateAssetRuntimeStatus RPC). |
| Mark Done doesn't show for an overdue PM | The schedule is active=false. |
Related
- Maintenance Events — what happens when a schedule fires (or unplanned work occurs)
- Asset Management — where
total_operating_hourslives - Equipment Hub — Today's PM Schedule widget
- Spare Parts — link parts the PM will consume