Modeling Life-Limited Parts Back to Birth Records

A life-limited part is only airworthy if its accumulated cycles can be proven, without a gap, all the way back to the manufacturer’s birth record. This page models that genealogy explicitly: every install period contributes a cycle delta, the deltas sum to a total, and each link must be continuous or the part’s remaining life is unknowable. It builds directly on the Parts-Traceability Ledger Design for Aviation Components, reading that ledger’s events and reconstructing the certified life story of a single critical component such as a turbine disk.

Prerequisites & inputs

Life-limited part (LLP) accounting is unforgiving because the consequence of an error is a component flown past its hard limit. Before modeling anything you need the birth record — for a high-pressure turbine disk that is the manufacturer’s release, typically an FAA Form 8130-3, stating the part number PW1234-5678, serial HPT-004521, and its published cycle limit, say 15,000 CSN (cycles since new). You also need the full sequence of install and removal events for that serial, each carrying the aircraft it was fitted to (for example D-AIMA), and the counter readings at install and at removal.

Those counter readings are the trap. Different operators and OEM record systems report engine cycles, flight cycles, and landings inconsistently, and mixing them corrupts the total. Reconcile them first: the technique in Harmonizing Flight Hours, Cycles, and Landings Across Sources must run before genealogy modeling, so that every delta you sum is denominated in the same unit the OEM used to set the limit. An LLP model fed mismatched counters produces a confident, wrong number.

LLP genealogy chain

LLP genealogy chainBirthrecordInstalleventsCycleaccrualBack-to-birth

Step-by-step implementation

1. Anchor the model at birth. The genealogy starts with a BirthRecord fixing the part identity, the certified cycle limit, and the cycles-since-new at manufacture (normally zero, but not always for reworked hardware). This anchor is the only point from which “back to birth” has meaning.

2. Represent each service period as an install span. An LLP does not accrue cycles in storage — only while installed and flown. Model each install→removal pair as a span with a starting airframe counter and an ending airframe counter. The cycles the part gained during that span is the difference between the two.

3. Sum the deltas and check continuity. Total cycles since new is the sum of every span’s delta. But summing is not enough: the model must assert that the part’s own running total is continuous across spans. If the disk left the shop reading 4,200 CSN and the next install record shows it entering service at 4,650 CSN, 450 cycles are unaccounted for and the genealogy is broken.

4. Compute remaining life and status. Remaining cycles is the limit minus the verified total. A part within a small margin of its limit must surface as such so planning can schedule its retirement.

from __future__ import annotations

import logging
from dataclasses import dataclass
from datetime import datetime

logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger("mro.llp_genealogy")


@dataclass(frozen=True)
class BirthRecord:
    part_number: str
    serial_number: str
    cycle_limit: int          # published hard limit, cycles since new
    csn_at_birth: int = 0     # non-zero only for reworked hardware


@dataclass(frozen=True)
class InstallSpan:
    aircraft_registration: str
    installed_at: datetime
    removed_at: datetime | None
    part_csn_at_install: int  # the part's own cycles-since-new when fitted
    part_csn_at_removal: int | None

    @property
    def delta(self) -> int:
        end = self.part_csn_at_removal
        if end is None:
            raise ValueError("open span has no removal reading yet")
        gained = end - self.part_csn_at_install
        if gained < 0:
            raise ValueError("removal CSN precedes install CSN")
        return gained


@dataclass(frozen=True)
class LlpStatus:
    serial_number: str
    total_cycles: int
    remaining_cycles: int
    is_continuous: bool


def build_genealogy(birth: BirthRecord, spans: list[InstallSpan]) -> LlpStatus:
    """Reconstruct provable cycles-since-new for one life-limited part.

    EASA Part-M M.A.305 requires the continuing-airworthiness record system to
    preserve component total accumulated cycles; and 14 CFR 43.9(a)(2) requires
    each maintenance record to state "The date of completion of the work
    performed", so every span below must tie to a dated, signed removal record.
    """
    ordered = sorted(spans, key=lambda s: s.installed_at)
    running = birth.csn_at_birth
    continuous = True

    for span in ordered:
        if span.part_csn_at_install != running:
            logger.warning(
                "genealogy gap for %s: expected install CSN %d, got %d",
                birth.serial_number, running, span.part_csn_at_install,
            )
            continuous = False
        # Advance the running total by whatever this span actually accrued.
        if span.part_csn_at_removal is not None:
            running = span.part_csn_at_removal
        else:
            logger.info("part %s currently installed on %s",
                        birth.serial_number, span.aircraft_registration)

    total = running
    remaining = birth.cycle_limit - total
    if remaining < 0:
        logger.error(
            "LIFE EXCEEDED for %s: %d of %d cycles",
            birth.serial_number, total, birth.cycle_limit,
        )
    logger.info(
        "sn=%s total_csn=%d remaining=%d continuous=%s",
        birth.serial_number, total, remaining, continuous,
    )
    return LlpStatus(
        serial_number=birth.serial_number,
        total_cycles=total,
        remaining_cycles=remaining,
        is_continuous=continuous,
    )

Worked example

Take turbine disk PW1234-5678, serial HPT-004521, ATA 72-53 (turbine section), with a 15,000-cycle limit born at 0 CSN. Its ledger holds three install spans. First fitted to D-AIMA, it accrued from 0 to 4,200 CSN. Overhauled and refitted to a second airframe, it ran 4,200 to 9,850. On its third install, the incoming record reads 9,850 and it is still flying, currently at 12,940.

Running build_genealogy sorts the spans, walks them, and confirms each install CSN equals the prior running total — 0, then 4,200, then 9,850 — so continuity holds. The output:

sn=HPT-004521 total_csn=12940 remaining=2060 continuous=True

The disk has 2,060 cycles left before its hard limit, and because is_continuous is True that figure is defensible back to the birth record. Now suppose the second install had been keyed as entering service at 4,650 CSN instead of 4,200 — a transcription slip. The walk detects that 4,650 does not equal the expected 4,200, logs a genealogy gap, and returns continuous=False. That flag is the whole point: it converts an invisible 450-cycle discrepancy into a hard stop, and it is exactly the kind of finding an EASA Part-M Compliance Mapping review expects the record system to catch before an auditor does.

Edge cases & compliance gotchas

Reworked parts born non-zero. A disk returned to service after a life-extending rework may re-enter with a non-zero csn_at_birth, or with a revised limit. Never assume birth equals zero cycles; read it from the 8130-3, or the remaining-life figure will be optimistic by the whole rework offset.

Counter resets and engine swaps. LLP cycles track the part, not the airframe. When a disk moves between engines or an airframe’s counter is replaced, the airframe reading discontinues but the part’s own CSN must not. Model the part’s running total independently, as above, so an airframe counter reset never leaks into the LLP total.

Open spans and stale reads. A currently-installed part has no removal reading, so its live CSN comes from the airframe’s present counter plus the install offset — a value that changes with every flight. Treat the open-span total as a snapshot with a timestamp, and re-derive it rather than caching it, or a planning system will act on a cycle count that is quietly days out of date.

Unit drift between sources. The single most common corruption is summing engine cycles from one operator with flight cycles from another. The limit is published in one unit; every delta must be in that same unit. Harmonize first, model second — a genealogy that is arithmetically continuous but denominated in mixed units is worse than an obvious gap, because it looks correct. Done properly, back-to-birth modeling turns a scattering of install records into a single provable number: how much life this critical part has left, and whether anyone can trust the answer.