Modeling Split-Duty Periods in Python

The exact task this guide solves is crediting a rest opportunity taken inside a flight duty period — a split duty — correctly, so the engine neither rejects a legal long day nor authorises one the break does not actually unlock. Under FAA §117.15 and the EASA split-duty provisions, a qualifying break in a suitable accommodation during the FDP can be partly excluded from the duty, extending the maximum permissible FDP. The subtlety is that only breaks meeting a minimum length and accommodation class qualify, and only part of the break credits back. A validator that inspects only the gaps between duties misses this entirely. This page models the split, computes the credit, and pins the qualifying boundaries. It extends the crew duty time taxonomy and feeds the extended ceiling to the duty time validation rule engines.

Prerequisites

What qualifies as a split duty

Not every mid-duty pause is a split duty. To qualify under §117.15, the break must be a scheduled rest opportunity of at least a minimum length, taken in a suitable accommodation, and the crew must be relieved of all duties during it. When those conditions hold, a portion of the break is excluded from the FDP, so the duty may run longer in wall-clock terms while the counted FDP stays within its ceiling. A break that is too short, or taken in a seat rather than an accommodation, is ordinary duty time and extends nothing. The classifier’s job is to test the qualifying conditions and, when they pass, compute the credited exclusion.

Step 1 — Model the break and its qualifying test

The break carries its length and accommodation class; the rule carries the minimum length and the set of qualifying classes. Testing eligibility from data keeps the rule auditable and jurisdiction-swappable.

from dataclasses import dataclass
from datetime import datetime
from enum import Enum


class Accommodation(Enum):
    NONE = "none"                 # a seat — never qualifies
    QUIET_ROOM = "quiet_room"     # suitable accommodation
    FLAT_BUNK = "flat_bunk"       # suitable accommodation, highest class


@dataclass(frozen=True)
class SplitRule:
    min_break_minutes: int              # e.g. 180 under §117.15
    qualifying: frozenset[Accommodation]
    credit_ratio: float                 # fraction of the break excluded from FDP


FAA_SPLIT = SplitRule(
    min_break_minutes=180,
    qualifying=frozenset({Accommodation.QUIET_ROOM, Accommodation.FLAT_BUNK}),
    credit_ratio=0.5,
)


@dataclass(frozen=True)
class InDutyBreak:
    start_utc: datetime
    end_utc: datetime
    accommodation: Accommodation

    @property
    def minutes(self) -> float:
        return (self.end_utc - self.start_utc).total_seconds() / 60.0

Verify: a 200-minute break in a QUIET_ROOM clears both conditions of FAA_SPLIT; a 200-minute break in a NONE seat fails the accommodation test; a 120-minute break in a FLAT_BUNK fails the minimum-length test.

Step 2 — Compute the credited exclusion and extended ceiling

When the break qualifies, a fraction of it is excluded from the FDP. The extended ceiling is the base Table B limit plus the credited exclusion, so the crew may fly a longer wall-clock day while the counted FDP obeys the original limit.

def split_duty_credit(brk: InDutyBreak, rule: SplitRule) -> float:
    """Minutes of the break excluded from the FDP, or 0 if it does not qualify."""
    qualifies = brk.minutes >= rule.min_break_minutes and brk.accommodation in rule.qualifying
    return brk.minutes * rule.credit_ratio if qualifies else 0.0


def extended_fdp_ceiling(base_ceiling_minutes: int, brk: InDutyBreak, rule: SplitRule) -> int:
    return base_ceiling_minutes + int(split_duty_credit(brk, rule))

Verify: with a base ceiling of 720 minutes and a qualifying 200-minute break at a 0.5 credit ratio, extended_fdp_ceiling(720, brk, FAA_SPLIT) returns 820 — the 100-minute credited exclusion lifts the ceiling; a non-qualifying break leaves it at 720.

Step 3 — Count the FDP against the extended ceiling

The counted FDP is the wall-clock duty minus the credited exclusion. Comparing it against the base ceiling — not the extended one — keeps the arithmetic honest: the exclusion is subtracted from the duty, not added to the limit, and the two framings must agree.

def counted_fdp(duty_minutes: float, brk: InDutyBreak, rule: SplitRule) -> float:
    """Wall-clock duty minus the credited in-duty rest."""
    return duty_minutes - split_duty_credit(brk, rule)


def is_compliant(duty_minutes: float, base_ceiling: int, brk: InDutyBreak, rule: SplitRule) -> bool:
    return counted_fdp(duty_minutes, brk, rule) <= base_ceiling
Split-duty credit extending the wall-clock FDP A timeline shows a flight duty period split by an in-duty rest break. Half of the qualifying break is credited as an exclusion, so the counted flight duty period stays within the base ceiling while the wall-clock duty runs longer by the credited amount. Base ceiling (Table B) Wall-clock duty flight before break (accommodation) flight after Counted FDP excluded ≤ base ceiling ✓
A qualifying break lets the wall-clock duty overrun the base ceiling because the credited half of the break is excluded — the counted FDP still lands within the original limit.

Failure modes and troubleshooting

Frequently Asked Questions

How much of a split-duty break credits back?

A fraction defined by the rule — commonly half of the qualifying break under the FAA provision. The credited portion is excluded from the counted flight duty period, which is what permits the longer wall-clock day.

Does the break need to be scheduled in advance?

Yes. A qualifying split-duty rest is a scheduled opportunity in a suitable accommodation with the crew relieved of all duties. An unscheduled ground delay in the aircraft is not a split-duty break and credits nothing.

Can any accommodation qualify?

Only classes the regulation recognises as suitable — a quiet room or a flat bunk, not a passenger seat. The accommodation class is part of the qualifying test, alongside the minimum length.

How does split duty interact with the base Table B ceiling?

Table B sets the base ceiling; the split-duty credit extends how long the wall-clock duty may run while keeping the counted FDP within that base ceiling. The two are resolved in sequence: base ceiling first, then the credited extension.

Back to Crew Duty Time Taxonomy Mapping.