Cumulative Duty Caps Compared
The exact question this guide answers is which cumulative cap catches a heavy roster first, and why a block that clears every FAA window can still breach an EASA one. The two regimes both limit accumulated duty, but they count over different intervals — the FAA over rolling hours (168 and 672), EASA over rolling days (7, 14, and 28) — and they meter different quantities, FDP hours under the FAA and duty hours under EASA. The result is a genuine gap: the EASA 14-day cap has no FAA analogue, so a fortnight of dense flying can be legal in the United States and illegal in Europe. This page runs one two-week block through both sets of windows, reports which binds, and shows the rolling-window code behind the FAA vs EASA scenario comparisons. The caps themselves come from the regulatory reference tables, and the rolling aggregation mirrors the duty time validation rule engines.
Prerequisites
- Python 3.11+ and, for the vectorised path,
polars. - A duty history with UTC report and release instants and per-duty FDP and duty minutes.
- Both cap sets seeded — FAA §117.23 (60 FDP h/168 h, 190 FDP h/672 h) and EASA ORO.FTL.210 (60 h/7 d, 110 h/14 d, 190 h/28 d) as parameterised windows.
- Duty vs FDP distinction — the FAA meters FDP hours; EASA meters duty hours, which include some ground duties FDP excludes.
- Regulatory versions pinned — the eCFR §117.23 amendment and the ORO.FTL.210 revision.
The scenario, stated precisely
A crew member flies a dense 14-day block that accumulates 112 duty hours and 104 FDP hours, spread so that no single rolling 168-hour window exceeds 58 FDP hours. The question is whether the block is legal, and under which regime.
Step 1 — Evaluate the FAA rolling-hour windows
The FAA caps FDP hours at 60 in any 168 consecutive hours and 190 in any 672 consecutive hours. With a peak of 58 FDP hours in the worst 168-hour window and 104 over the whole fortnight, the block clears both FAA windows — the 672-hour (28-day) cap of 190 is nowhere near binding at two weeks.
CAPS = {
"FAA": [
{"metric": "fdp", "window_hours": 168, "cap_minutes": 60 * 60},
{"metric": "fdp", "window_hours": 672, "cap_minutes": 190 * 60},
],
"EASA": [
{"metric": "duty", "window_hours": 7 * 24, "cap_minutes": 60 * 60},
{"metric": "duty", "window_hours": 14 * 24, "cap_minutes": 110 * 60},
{"metric": "duty", "window_hours": 28 * 24, "cap_minutes": 190 * 60},
],
}
def worst_window(duties, metric, window_hours):
"""Max sum of `metric` minutes over any trailing window of the given length."""
from datetime import timedelta
span = timedelta(hours=window_hours)
worst = 0
for i, anchor in enumerate(duties):
total = sum(
d[metric] for d in duties
if anchor["report"] - span < d["report"] <= anchor["report"]
)
worst = max(worst, total)
return worst
Verify: for this block, worst_window(duties, "fdp", 168) returns 3480 minutes (58 hours), below the 3600-minute (60-hour) FAA cap, and the 672-hour window is far below 190 hours. The FAA verdict is compliant.
Step 2 — Evaluate the EASA rolling-day windows
EASA caps duty hours at 60 in any 7 days, 110 in any 14 days, and 190 in any 28 days. The 14-day window is the one with no FAA counterpart, and the block’s 112 duty hours over the fortnight exceed the 110-hour cap by two hours. EASA fails the block.
def evaluate(duties, regime):
results = []
for cap in CAPS[regime]:
worst = worst_window(duties, cap["metric"], cap["window_hours"])
results.append({
"regime": regime,
"window_hours": cap["window_hours"],
"metric": cap["metric"],
"worst_minutes": worst,
"cap_minutes": cap["cap_minutes"],
"breached": worst > cap["cap_minutes"],
})
return results
easa = evaluate(duties, "EASA")
breached = [r for r in easa if r["breached"]]
# breached -> the 14-day duty window: 6720 worst vs 6600 cap
Verify: evaluate(duties, "EASA") flags the 336-hour (14-day) window as breached — 6720 worst-window minutes against the 6600-minute (110-hour) cap — while the 7-day and 28-day windows stay clear.
Step 3 — Reconcile and report the binding regime
The block is legal under the FAA and illegal under EASA, so for a dual-jurisdiction crew EASA binds and the roster must be rebuilt to shed at least two duty hours from the worst 14-day window.
def reconcile_caps(faa, easa):
faa_ok = not any(r["breached"] for r in faa)
easa_ok = not any(r["breached"] for r in easa)
binding = None if faa_ok and easa_ok else ("EASA" if not easa_ok else "FAA")
return {"faa_compliant": faa_ok, "easa_compliant": easa_ok, "binding_regime": binding}
verdict = reconcile_caps(evaluate(duties, "FAA"), easa)
# {'faa_compliant': True, 'easa_compliant': False, 'binding_regime': 'EASA'}
Verification queries and assertions
faa = evaluate(duties, "FAA")
easa = evaluate(duties, "EASA")
assert all(not r["breached"] for r in faa) # FAA clears every window
assert any(r["window_hours"] == 336 and r["breached"] for r in easa) # EASA 14-day fails
assert reconcile_caps(faa, easa)["binding_regime"] == "EASA"
The middle assertion is the whole point: the breach lives in the 336-hour (14-day) window, the interval the FAA does not meter, so only the EASA evaluation catches it.
Failure modes and troubleshooting
- Metering FDP hours against EASA caps. EASA counts duty hours, which include ground duties the FDP excludes; using FDP against the EASA cap understates the total and hides breaches. Remediation: carry both
fdpanddutyminutes per duty and meter each regime with its own metric. - Approximating 7 days as 168 hours and calling it the same cap. The window lengths coincide, but the FAA 168-hour cap is 60 FDP hours and the EASA 7-day cap is 60 duty hours; treating them as one check misses duty-only breaches. Remediation: keep the caps separate even when windows align.
- Only checking the shortest and longest windows. Skipping the 14-day EASA window because “the 7-day passed and the 28-day passed” is exactly how this breach hides. Remediation: evaluate every seeded window, never a subset.
- Summing on calendar days instead of rolling windows. A Monday-to-Sunday calendar week can pass while a rolling 7-day window fails. Remediation: use trailing rolling windows anchored on each duty, as the duty time validation rule engines do.
- Local-time window edges. Anchoring the window on local time lets a date-line crossing reorder duties and mis-sum the window. Remediation: anchor every window on the UTC instant.
Frequently Asked Questions
Why does the FAA miss a breach the EASA rules catch?
Because the FAA has no cumulative window between 168 hours (about a week) and 672 hours (about four weeks). A two-week accumulation falls in that gap, where the EASA 14-day cap of 110 hours sits with no American counterpart.
Are the 7-day EASA cap and the 168-hour FAA cap the same rule?
No. The windows are nearly the same length, but the FAA cap counts 60 flight-duty-period hours while the EASA cap counts 60 duty hours. Duty is the broader quantity, so the EASA 7-day cap can bind on a roster the FAA 168-hour cap clears.
How much duty must come out of the roster to pass EASA?
At least two hours from the worst 14-day window, since the block sits at 112 duty hours against the 110-hour cap. The rebuild must target that specific window, not the fortnight total, because the cap is on any rolling 14 days.
Does augmentation change these cumulative caps?
Augmentation raises the daily FDP ceiling, not the cumulative caps evaluated here. The 60/190 FAA hours and the 60/110/190 EASA hours apply regardless of crew complement, so an augmented long-haul block still meters against the same windows.
Related
- FAA vs EASA Scenario Comparisons — the parent section of head-to-head scenarios.
- Maximum FDP for an Early Morning Report — the daily-ceiling counterpart to these cumulative caps.
- Regulatory Reference Tables — the parameterised caps both evaluations read.
- FAA Part 117 Rule Schema Design — the §117.23 rolling-hour windows.
- Duty Time Validation & Rule Engines — the rolling-window aggregation this reuses.
Back to FAA vs EASA Scenario Comparisons.