Classifying Standby and Reserve Duty in Python
The exact task this guide solves is deciding, for a block of standby or reserve time, how much of it counts as duty, whether it contributes to the flight duty period, and whether any of it counts as rest. This is the classification that most often over- or under-credits a crew member, because “reserve” is not one thing: airport standby is fully duty, short-call reserve partially erodes the subsequent FDP, and long-call reserve at home can count as rest until the crew is called. Getting the mapping wrong either rejects a legal pairing or, worse, lets an illegal one through. This page builds a classifier over the shared crew duty time taxonomy, maps each category to its §117.21 and EASA treatment, and pins the boundaries. Its output feeds the duty time validation rule engines and depends on clean events from flight data ingestion.
Prerequisites
- Python 3.11+ with typed enums and dataclasses.
- A normalised event stream — standby start/end and any call-out event, in UTC.
- The standby rules seeded — the §117.21 reserve provisions and the EASA ORO.FTL.225 standby definitions as parameters.
- Facility classification — whether standby is served at the airport or at a suitable accommodation.
The categories and their treatment
Four categories cover almost all real rosters, and each maps to a distinct treatment. Airport standby is duty in full and counts toward the FDP the moment it begins. Short-call reserve — the crew can be called with little notice — is not fully duty, but the reserve period reduces the maximum FDP available once a call arrives. Long-call reserve with adequate notice can count as rest until the call, provided a protected rest opportunity is preserved. Home reserve below the notice threshold behaves like long-call. The classifier’s job is to turn a raw standby block plus its facility and notice metadata into the correct trio of durations: duty minutes, FDP contribution, and rest credit.
Step 1 — Model the categories and their rules
Each category carries three booleans-in-effect: does it count as duty, does it contribute to FDP, and can it be credited as rest. Expressing them as data rather than branches keeps the classifier auditable.
from dataclasses import dataclass
from enum import Enum
class StandbyKind(Enum):
AIRPORT = "airport_standby"
SHORT_CALL = "short_call_reserve"
LONG_CALL = "long_call_reserve"
HOME_RESERVE = "home_reserve"
@dataclass(frozen=True)
class StandbyRule:
counts_as_duty: bool
contributes_to_fdp: bool
rest_creditable: bool
fdp_reduction_ratio: float # fraction of standby that erodes later FDP
STANDBY_RULES = {
StandbyKind.AIRPORT: StandbyRule(True, True, False, 1.0),
StandbyKind.SHORT_CALL: StandbyRule(True, False, False, 0.25),
StandbyKind.LONG_CALL: StandbyRule(False, False, True, 0.0),
StandbyKind.HOME_RESERVE: StandbyRule(False, False, True, 0.0),
}
Verify: STANDBY_RULES[StandbyKind.AIRPORT].contributes_to_fdp is True and STANDBY_RULES[StandbyKind.LONG_CALL].rest_creditable is True — the two extremes of the spectrum.
Step 2 — Classify a block into its durations
The classifier takes a standby block and, given the applicable rule, returns the duty, FDP-contribution, and rest-credit minutes. Airport standby contributes its full length to the FDP; short-call reserve reduces the later FDP by a fraction of the reserve served; long-call reserve credits its length as rest until a call arrives.
from datetime import datetime
@dataclass(frozen=True)
class StandbyBlock:
kind: StandbyKind
start_utc: datetime
end_utc: datetime
facility_is_accommodation: bool
def classify(block: StandbyBlock) -> dict:
rule = STANDBY_RULES[block.kind]
minutes = (block.end_utc - block.start_utc).total_seconds() / 60.0
return {
"kind": block.kind.value,
"duty_minutes": minutes if rule.counts_as_duty else 0.0,
"fdp_contribution_minutes": minutes if rule.contributes_to_fdp else 0.0,
"fdp_reduction_minutes": minutes * rule.fdp_reduction_ratio,
"rest_credit_minutes": (
minutes if rule.rest_creditable and block.facility_is_accommodation else 0.0
),
}
Verify: an eight-hour airport standby returns 480 duty minutes and 480 FDP-contribution minutes; an eight-hour long-call reserve at a suitable accommodation returns 0 duty minutes and 480 rest-credit minutes; the same long-call block not at an accommodation credits no rest.
Step 3 — Apply the FDP reduction when a call arrives
Short-call reserve does not add to the FDP directly, but when the crew is called out, a fraction of the reserve already served erodes the FDP they can then fly. Applying that reduction at the call event keeps the subsequent duty limit correct.
def reduced_fdp_limit(base_fdp_minutes: int, reserve_result: dict) -> int:
"""Erode the base FDP ceiling by the reserve reduction, never below zero."""
return max(0, int(base_fdp_minutes - reserve_result["fdp_reduction_minutes"]))
Failure modes and troubleshooting
- Crediting home reserve as rest without an accommodation. Rest credit requires a suitable accommodation; crediting a call-liable home reserve as rest over-credits recovery. Remediation: gate the rest credit on the facility flag.
- Treating short-call reserve as free. Short-call reserve erodes the subsequent FDP; ignoring the reduction overstates the duty the crew can then fly. Remediation: apply
reduced_fdp_limitat the call event. - Airport standby not started at report. Airport standby is duty from the moment it begins, not from block-out; anchoring it to the first flight understates duty. Remediation: start the duty clock at standby commencement.
- Losing the call-out event. A reserve block with no recorded call-out cannot be split into reserve-then-duty. Remediation: require the call event from flight data ingestion before classifying.
- Fixed reduction ratio across jurisdictions. The FDP-reduction fraction differs by regime and agreement; hardcoding one value misclassifies the other. Remediation: source the ratio from the seeded rule, not a constant.
Frequently Asked Questions
Is all reserve time duty time?
No. Airport standby is duty in full, and short-call reserve counts as duty while eroding later FDP, but long-call and home reserve generally are not duty and can be credited as rest until the crew is called, provided the rest conditions are met.
Why does short-call reserve reduce the FDP instead of adding to it?
Because the crew has been on call and partially fatigued without flying. The regulation reflects this by shrinking the duty they may then perform, rather than counting the reserve as flown duty. The reduction is a fraction of the reserve served.
What makes standby count as rest?
Two conditions: the category must be one the regulation allows to be rest (long-call or home reserve), and it must be served at a suitable accommodation with a protected, uninterrupted rest opportunity. Airport standby never qualifies.
How does a call-out split a reserve block?
The reserve time before the call is classified as reserve, and the assignment after the call is a normal flight duty period whose ceiling has been reduced by the reserve served. The call event is the split point, which is why the ingestion feed must carry it.
Related
- Crew Duty Time Taxonomy Mapping — the parent section and shared definitions.
- Modeling Split-Duty Periods in Python — the sibling case of in-duty rest crediting.
- Rest Period Compliance Checks — how the rest credit feeds the rest validator.
- Flight Data Ingestion & System Sync — the source of standby and call-out events.
Back to Crew Duty Time Taxonomy Mapping.