EASA FTL Compliance Frameworks

Modern flight operations and crew scheduling systems cannot rely on static spreadsheets or hardcoded thresholds when managing European Union Aviation Safety Agency Flight Time Limitations. An effective EASA FTL compliance framework requires a modular, constraint-driven architecture that translates regulatory text into deterministic, executable logic. This work sits inside the broader Core Architecture & Regulatory Mapping domain and is the European counterpart to the FAA Part 117 rule schema design that governs US carriers. By treating Subpart FTL provisions as versioned rule sets rather than immutable business constants, scheduling platforms gain the agility required to adapt to AMC/GM updates, operator-specific ORO.FTL.110 deviations, and cross-jurisdiction harmonisation efforts.

The Compliance Challenge This Framework Solves

The scoped problem is narrow but unforgiving: given a proposed roster and each crew member’s recent duty history, decide — deterministically and in milliseconds — whether every flight duty period, rest period, and cumulative window conforms to Regulation (EU) No 965/2012, Annex III, Subpart FTL. Unlike a simple daily cap, EASA FTL is stateful. The maximum flight duty period (FDP) permitted for a single report is a function of the crew member’s acclimatisation state, the local report time, and the number of sectors flown, while the legality of that same duty also depends on rolling totals stretching back a full year. A pairing that looks legal in isolation can be illegal because of what the crew member did six days ago.

Three properties make this hard to encode. First, the reference time that keys the FDP table shifts as crew cross time zones, so the “same” 06:00 report can carry a 13-hour ceiling for an acclimatised pilot and a reduced ceiling for one whose acclimatisation state is unknown. Second, the limits are layered: a duty can satisfy the daily FDP table under ORO.FTL.205 yet breach a cumulative cap under ORO.FTL.210. Third, rest requirements under ORO.FTL.235 are relative to the preceding duty, so every verdict is path-dependent. A framework that hopes to pass an authority inspection must model all three as first-class data, not as branches buried in a scheduling loop.

Schema and Data Structure Design

The framework’s data model separates three concerns that operators routinely conflate: raw operational events, derived duty structures, and the versioned regulatory parameters that evaluate them. Keeping regulatory constants in their own effective-dated table is what allows an AMC/GM revision to be diffed and rolled back without touching a line of scheduling code.

The core entities are the crew_member and their time-varying acclimatisation_state; the duty_period bounded by rest; the flight_duty_period (the FDP subset that the ORO.FTL.205 table constrains); the sector rows that carry block-out and block-on instants; the rest_period that gates the next duty; and the effective-dated ftl_ruleset that supplies FDP tables, cumulative caps, and rest minima keyed by revision date. Every temporal column is stored in UTC with an explicit originating zone as metadata, so local report time is derived at evaluation, never stored ambiguously.

EASA FTL schema entity-relationship diagram A crew_member has many time-varying acclimatisation_state rows and many duty_period rows. Each duty_period has one flight_duty_period, which owns many sector rows, and is gated by one following rest_period whose minimum equals the preceding duty. A separate effective-dated ftl_ruleset supplies FDP tables, cumulative caps, and rest minima that the evaluator reads when it measures a flight duty period. Every timestamp column is stored in UTC with the originating zone kept as metadata, so local report time is derived at evaluation rather than stored. crew_member crew_id PK home_base base_zone (tz) acclimatisation_state crew_id FK state B / D / X effective_utc duty_period duty_id PK crew_id FK start_utc · end_utc flight_duty_period fdp_id PK duty_id FK report_utc · sectors sector sector_id PK fdp_id FK block_out/on_utc rest_period rest_id PK after duty_id FK min = prev duty ftl_ruleset ruleset_id PK operator · rev_date effective_from/to 1 : N 1 : N 1 : 1 1 : N 1 : 1 · following ftl_ruleset supplies limits at evaluation Temporal storage rule Every *_utc column is stored in UTC; the originating zone is kept as metadata. Local report time is derived at evaluation — it is never stored ambiguously.
The EASA FTL schema: identity and acclimatisation (blue), the duty chain and its sectors (teal), the following rest that gates the next duty (green), and the effective-dated ruleset (amber) the evaluator reads at run time.

Field naming, unit standardisation, and audit lineage across the scheduling, payroll, and compliance modules all follow the shared crew duty time taxonomy mapping. Anchoring the schema to that taxonomy is what prevents a rule engine from misclassifying a positioning segment as a sector, or a standby callout as the start of an FDP — the two most common sources of false violations.

Regulatory Mapping

Every schema decision above is driven by a specific Subpart FTL provision. The mapping below is the ground truth the evaluator encodes; the authoritative wording lives in the EASA Air Operations Regulation (Subpart FTL).

The precise conditions that flip a duty between the reduced and standard ceilings — standby timing, sector count, and acclimatised windows — are worked through in EASA FTL 8 vs 10 hour duty periods explained.

Constraint-Driven Rule Engine

Rule engine implementation begins with a formalised constraint model. EASA FTL provisions map naturally to temporal state machines and interval algebra, where duty periods, FDPs, rest requirements, and split-duty configurations are evaluated as overlapping or sequential time blocks. The engineering challenge lies in translating regulatory nuance into deterministic evaluation paths: the distinction between a standard and an extended FDP hinges on acclimatisation state, sector count, and local report time, all of which must be normalised into a unified temporal schema before evaluation.

Rather than applying post-hoc validation, the framework embeds constraint evaluation directly into the pairing optimisation loop, acting as a deterministic gatekeeper during schedule generation and disruption recovery.

FTL evaluation inside the pairing loop A proposed pairing is normalised to UTC, then split across three FTL constraint checks — the FDP table keyed on acclimatisation and sectors, the minimum-rest rule, and the cumulative rolling-window caps. Their results converge into a single compliance verdict. A clear verdict publishes the roster; a verdict with violations routes the optimiser to reroute and re-submit the pairing, closing the loop rather than auditing after the fact. Proposed pairing Normalize to UTC FTL constraints FDP table acclimatisation · sectors Minimum rest ORO.FTL.235 · prev duty Cumulative 7 / 14 / 28 d · 28 d / 12 mo Compliance verdict Publish roster Optimizer reroutes clear violations re-submit pairing
FTL evaluation embedded in the pairing loop: violations route the optimizer back to reroute rather than relying on post-hoc auditing.

Python Implementation Walkthrough

Production implementations express regulatory parameters as typed models so that a malformed payload fails at the boundary rather than deep inside the evaluator. Using pydantic for the regulatory contract guarantees that FDP ceilings, sector counts, and acclimatisation states are validated before they reach the constraint solver. The model below captures the inputs the ORO.FTL.205 lookup needs.

from datetime import datetime, timezone
from enum import Enum
from pydantic import BaseModel, field_validator


class AcclimatisationState(str, Enum):
    ACCLIMATISED = "B"      # to home base reference time
    LOCAL = "D"             # to local departure time
    UNKNOWN = "X"           # state cannot be determined


class FlightDutyPeriod(BaseModel):
    crew_id: str
    report_time_utc: datetime
    report_zone: str                 # e.g. "Europe/Amsterdam"
    sectors: int
    state: AcclimatisationState

    @field_validator("report_time_utc")
    @classmethod
    def must_be_utc(cls, v: datetime) -> datetime:
        if v.tzinfo is None or v.utcoffset() != timezone.utc.utcoffset(None):
            raise ValueError("report_time_utc must be timezone-aware UTC")
        return v

The FDP ceiling is a pure function of the local report hour, the sector count, and the acclimatisation state. Keeping it pure is what makes every verdict reproducible from the audit log months later. The reference time and table follow CS FTL.1.205; timezone handling relies on the standard library zoneinfo and datetime modules documented in the Python datetime documentation.

from datetime import timedelta
from zoneinfo import ZoneInfo

# CS FTL.1.205 Table 2, acclimatised, banded by local report hour.
# Values are the 1-2 sector ceiling in minutes; each extra sector -30, floor 540.
_BASE_CEILING = {range(6, 14): 780, range(14, 18): 750, range(18, 22): 690}


def max_fdp_minutes(fdp: FlightDutyPeriod) -> int:
    local = fdp.report_time_utc.astimezone(ZoneInfo(fdp.report_zone))
    base = 660  # conservative night-band default
    for band, ceiling in _BASE_CEILING.items():
        if local.hour in band:
            base = ceiling
            break
    reduced = base - max(0, fdp.sectors - 2) * 30
    ceiling = max(reduced, 540)               # 9-hour floor
    if fdp.state is AcclimatisationState.UNKNOWN:
        ceiling = min(ceiling, 660)           # unknown-state reduction
    return ceiling

An FDP is legal when its actual length does not exceed this ceiling. Split-duty extensions under ORO.FTL.220 and standby uplift under ORO.FTL.225 are applied as adjustments to ceiling before the comparison, keeping each regulatory provision an isolated, testable term. The compliance verdict returned to the optimiser carries structured violation metadata so schedulers resolve conflicts algorithmically rather than auditing pairings by hand.

Rolling Window and Temporal Aggregation

The daily FDP check is stateless, but ORO.FTL.210 is not: it requires rolling totals over 7, 14, and 28 days for duty, and 28-day, calendar-year, and 12-month windows for flight time. Timezone drift is the main source of cumulative error, so every input is stamped in ISO 8601 UTC before it enters the aggregation layer. Where the data lives in PostgreSQL, frame-bounded window functions express these caps directly; see the PostgreSQL window functions reference for the frame semantics.

-- Rolling 7-day duty total per crew member, evaluated at each duty start.
SELECT
    crew_id,
    duty_start_utc,
    SUM(duty_minutes) OVER (
        PARTITION BY crew_id
        ORDER BY duty_start_utc
        RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW
    ) AS rolling_7d_minutes
FROM duty_period
ORDER BY crew_id, duty_start_utc;

For pre-flight validation inside the optimiser, the same logic runs in memory over Polars or Pandas frames, where a rolling group-by keyed on the duty timestamp reproduces the SQL frame without a database round trip. Whichever engine is used, the window boundaries must be inclusive of the exact regulatory span — an off-by-one on the 28-day boundary silently under-counts duty and lets an illegal roster through.

Integration Points

This framework is one stage in a longer chain and depends on clean contracts with its neighbours:

Testing and Edge Cases

Because FTL logic is stateful and time-sensitive, example-based tests miss the cases that matter. Property-based testing with hypothesis generates thousands of synthetic rosters and asserts invariants — for instance, that no accepted FDP ever exceeds its ORO.FTL.205 ceiling, and that no accepted rest period is shorter than the preceding duty subject to the ORO.FTL.235 floor. The boundary conditions that most often break EASA implementations are specific:

Predicate logic is validated against the latest published AMC/GM interpretations before every deployment, with the regulatory constants version-controlled so a rule revision is a reviewable diff rather than a code change.

Explore This Topic in Depth

Back to Core Architecture & Regulatory Mapping.

Explore this section