Scoring methodology

This is the full explanation of how a single shift becomes a Fatigue Index, a Relative Risk, and a colour band. It cites the files that own each calculation so a safety reviewer can trace any number on the grid back to source.

Engine version
2026.04.2
Default profile
general@1.0
Engine tests
57

1. The inputs we accept

A score is a pure function of a ShiftInput. The public type lives at src/lib/fri-engine/types.ts. Nothing else influences the output. No network call, no LLM, no randomness.

InputType pathPurpose
start / endShiftInput.start, ShiftInput.endISO timestamps for the shift. Duration is end minus start.
break_minutesShiftInput.break_minutesIn-shift rest, subtracted from duration to produce effective hours. Below the profile break threshold on a shift of qualifying length raises a warning.
job_typeShiftInput.job_typeSector role (driver, clinical, manual, etc.). Drives the job component and inherits defaults from the profile.
prior_shiftsShiftInput.prior_shiftsPast shifts for this worker, used to compute rest before start and cumulative workload across a rolling window.
travel_minutes_beforeShiftInput.travel_minutes_beforeCommute or on-duty travel required before the shift starts, in minutes (0–240). This is deducted from the available rest window before the sleep opportunity estimate and before the daily-rest warning threshold is applied. Based on the door-to-door rest principle documented in HSE RR446 commentary and Dawson & McCulloch (2005).

2. The three components

The Fatigue Index is a weighted sum of three sub-scores, each on a 0 to 100 scale. Each component lives in its own file so it can be reviewed and unit tested in isolation.

Cumulative

weight 0.4

Captures workload over a rolling window of prior shifts: total hours worked, consecutive days, and how much rest the worker actually got before the current start. Heavier when a pattern is both long and dense.

src/lib/fri-engine/cumulative.ts

Job

weight 0.35

Captures task load: job_type and the sector profile scale a baseline that represents how demanding the role is. A 12h clinical night weighs heavier than a 12h back-office day.

src/lib/fri-engine/job.ts

Timing

weight 0.25

Captures circadian timing: night work, early starts, rotating direction, and shift length. Returns both a number and a tag (the inferred shift tag on the meta block).

src/lib/fri-engine/timing.ts

FI = round( 0.4 x C + 0.35 x J + 0.25 x T ), clamp 0 to 100

3. Relative Risk

Once the Fatigue Index is known, we map it to a Relative Risk (RI) value. RI is anchored so an FI of 30 returns approximately 1.0, the reference. An FI of 55 returns roughly 1.55, and an FI of 80 returns roughly 2.0. The slope is tuned against the HSE FRI reference set and clamped to a sensible range.

RI = clamp( 1.0 + (FI minus 30) / 50, 0.6, 2.6 ), rounded to 2 decimals

RI is useful because it compares schedules directly (a pattern at RI 1.6 carries more risk than a pattern at RI 1.1) without implying a per-incident probability that we have not earned.

4. Bands on the grid

The grid collapses the FI into three bands so a rota manager can scan a week at a glance. Cutoffs live in src/lib/team/grid.ts as bandFor() and move together with the engine weights when we recalibrate.

  • Green FI 0 to 21

    Consistent with a reference pattern. No scheduled intervention needed.

  • Amber FI 22 to 39

    Elevated. Worth a review: rotation length, consecutive nights, or insufficient rest are the usual drivers.

  • Red FI 40 and up

    High risk. The pattern should change: split the shift, shorten the block, or add rest before the next start.

5. Warnings, in plain English

Alongside the numeric score, the engine emits plain-English warnings. The logic is in buildWarnings() in src/lib/fri-engine/score.ts. Each warning names the compliance profile that triggered it. They cover:

  • Shift duration over the profile maximum (12 hours for both built-in profiles).
  • In-shift break below the profile minimum: 20 minutes for shifts over 6 hours (Standard WTR) or 30 minutes for shifts over 8 hours (Network Rail NR 003).
  • Daily rest before the shift below the profile minimum: 11 hours (Standard WTR) or 12 hours (Network Rail NR 003). When travel_minutes_before is set, the effective rest window (rest minus travel) is checked, not the raw gap.
  • Consecutive night shifts exceeding the profile cap. Network Rail NR 003 caps at 4 consecutive nights; Standard WTR has no cap.
  • Post-night-run rest below the profile threshold. Network Rail NR 003 requires 48 hours of rest after a run of nights ends; Standard WTR uses the standard daily-rest minimum.
  • Fatigue Index crossing the amber or red band thresholds.

None of these warnings are triggered by the LLM. They are produced by the same pure function that produces the number.

6. Compliance profiles

A compliance profile is a named set of warning thresholds. Selecting a profile does not change the FI or RI calculation; it only affects which warnings fire and at what levels. The profile is stored per workspace in orgs.compliance_profile_id and can be changed in Settings. Three profiles are built in:

ProfileMin restBreakNight run capPost-nights rest
standard_wtr11 h20 min > 6 h shiftnone11 h
network_rail_00312 h30 min > 8 h shift4 nights48 h
hgv_wtd11 h30 min > 6 h shiftnone11 h

The hgv_wtd profile adds three additional week-level checks on top of the per-shift warnings above: a 60-hour single-week hard cap, a 90-hour fortnightly cap (14-day rolling window), and a 17-week rolling average check that flags any worker whose average weekly hours exceed 48h over the reference period. These checks are computed in the compliance digest layer, not in the per-shift FRI engine, so they appear as WTD breaches on the Compliance Dashboard rather than as shift warnings.

Sources: UK Working Time Regulations 1998 Regulations 10, 11, and 12 for the Standard WTR profile; Network Rail Fatigue Management Standard NR/L2/OHS/003 clauses 3.2 to 3.5 for the Network Rail NR 003 profile; EU Road Transport Working Time Directive 2002/15/EC as retained in UK law (Road Transport (Working Time) Regulations 2005, Regulations 4, 5, and 7) for the HGV Road Transport WTD profile.

7. Reference material

The engine is built against published UK and international fatigue guidance. The primary references are:

  • HSE RR446: The development of a fatigue / risk index for shiftworkers. Worked examples from this report are preserved as regression tests in src/lib/fri-engine/__tests__/rr446-examples.test.ts.
  • HSE FRI toolkit: the Fatigue and Risk Index tool and supporting guidance documents.
  • UK Working Time Regulations 1998: statutory 20 minute break and 11 hour daily rest reference periods used in the warnings block.
  • Network Rail NR/L2/OHS/003 Fatigue Management Standard: clauses 3.2 to 3.5 set the minimum rest, break, consecutive-nights, and post-night-run thresholds used in the Network Rail NR 003 compliance profile.
  • Dawson D. and McCulloch K. (2005): “Managing fatigue: It’s about sleep”. Occupational and Environmental Medicine 62(4) 231–233. The paper establishes commute time as a direct reduction of the sleep opportunity available between shifts, which is the basis for deducting travel_minutes_before from the effective rest window rather than treating it as a neutral activity.
  • EU Road Transport Working Time Directive 2002/15/EC (retained in UK law as the Road Transport (Working Time) Regulations 2005): Regulation 4 (weekly working time 48h average over 17-week reference), Regulation 5 (60h single-week hard cap and 90h fortnightly limit), and Regulation 7 (breaks) form the basis of the HGV Road Transport WTD compliance profile.
  • Sector specific guidance: RSSB rail fatigue guidance and CAA CAP 1871 for aviation, used to inform sector coefficient profiles.

8. Versioning and reproducibility

Every stored score carries the fri_version and profile_version that produced it. This means:

  • Any number on the grid can be replayed against the same engine version months later.
  • Any change to coefficients, weights, or thresholds bumps FRI_VERSION (currently 2026.04.2).
  • Profiles version independently so a sector tuning does not pretend to be a wholesale engine rewrite.

9. Known limitations

  • Rotapulse scores the schedule, not the person. Individual sleep quality, caffeine, medical conditions, and sleep disorders are not captured. travel_minutes_before captures the commute dimension but not sleep quality once home.
  • Sector coefficient profiles currently cover generic, driver, and clinical roles. Aviation CAP 1871 and road transport Regulation 561/2006 variants are on the roadmap.
  • Relative Risk is a calibrated scalar against a reference pattern, not a causal probability of an incident. Use it to compare schedules, not to predict individual outcomes.
  • Compliance profile warnings are threshold checks only. A shift that passes all threshold checks can still carry elevated FI if the pattern is cumulatively demanding.
  • LLM narrative text, when enabled, explains a score rather than producing one. The engine re-validates every suggestion before it is shown.
Questions or a specific RR446 worked example you want us to regression test? Email hello@rotapulse.co.uk.