Mapping Airbus vs Boeing Part-Number Schemes in Python

Airbus and Boeing describe the same physical part with incompatible identifiers, effectivity tokens, and cataloguing conventions, and a single traceability ledger cannot store both verbatim without eventually mismatching a component. This page builds a deterministic Python resolver that folds Airbus part numbers with their Functional Item Numbers and Boeing Illustrated Parts Catalog figure-item references into one canonical part identity. It is a concrete sub-problem within Data Normalization Across OEM Formats, where divergent manufacturer nomenclature must collapse to one schema before compliance engines consume it.

Prerequisites & inputs

The resolver takes part references exactly as they arrive in each OEM feed and must never lose the native form — regulators and warranty desks still work in the manufacturer’s own catalogue. Airbus feeds typically present a compact alphanumeric part number alongside a Functional Item Number such as 24VU that locates the item functionally in the aircraft. Boeing feeds present a base part number plus an IPC figure-item coordinate like 27-31-04 that locates the part in the illustrated catalogue. The two schemes also print separators differently: Boeing embeds hyphens for legibility while Airbus omits them, so AS-9410-22 and AS941022 are the same token once normalized.

To keep the example grounded, the scenario tracks a spoiler actuator on G-EZBK under ATA 27-64 (spoiler/speedbrake control), cross-referenced between an Airbus native part number D5837500020000 and an equivalent Boeing-catalogued alias BACA20AP12 supplied by a third-party MRO feed. The canonical identity your resolver emits should align with the field conventions established in MRO Data Schema Design, so that the resolved key drops directly into the traceability store.

Cross-OEM part-number resolution

Cross-OEM part-number resolutionAirbusP/NBoeingP/NAliasresolverCanonicalidentity

Both native forms enter an alias resolver that indexes every known token to a single canonical key. Once any token is seen, later references — from either manufacturer — collapse onto the same identity rather than spawning a duplicate.

Step-by-step implementation

  1. Canonicalize the native token. Uppercase and strip separators so print variants converge, while retaining the original in the emitted record.
  2. Validate the OEM-specific decorations. A malformed Airbus FIN or Boeing figure-item is a signal that the feed is corrupt, not something to silently accept.
  3. Index every alias to one key. When a new reference shares any token with a known part, reuse the existing canonical key; otherwise mint a new one keyed on the OEM plus native token, per ATA iSpec 2200 part-identification practice.
  4. Emit an immutable canonical record. The resolved CanonicalPart holds both manufacturers’ native part numbers and the full alias set for audit.
from __future__ import annotations

import logging
import re
from enum import Enum
from typing import Optional

from pydantic import BaseModel, ConfigDict, Field, field_validator

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

_SEPARATORS = re.compile(r"[\s\-/.]")
_AIRBUS_FIN = re.compile(r"^\d{1,3}[A-Z]{2}\d?$")       # e.g. 24VU
_BOEING_FIGITEM = re.compile(r"^\d{2}-\d{2}-\d{2}$")    # ATA-aligned figure-item


class OEM(str, Enum):
    AIRBUS = "AIRBUS"
    BOEING = "BOEING"


class RawPartRef(BaseModel):
    """A part reference exactly as it appears in one OEM's source feed."""

    model_config = ConfigDict(strict=True, extra="forbid")

    oem: OEM
    native_pn: str = Field(min_length=4, max_length=32)
    fin: Optional[str] = None          # Airbus Functional Item Number
    fig_item: Optional[str] = None     # Boeing IPC figure-item coordinate
    ata_chapter: str = Field(pattern=r"^\d{2}(-\d{2}){0,2}$")

    @field_validator("native_pn", mode="before")
    @classmethod
    def canonicalize_pn(cls, v: str) -> str:
        # Boeing prints hyphens; Airbus omits them. Per ATA iSpec 2200 part
        # identification, both index to one token after case/separator folding.
        return _SEPARATORS.sub("", v.upper())

    @field_validator("fin")
    @classmethod
    def check_fin(cls, v: Optional[str]) -> Optional[str]:
        if v is not None and not _AIRBUS_FIN.match(v.upper()):
            raise ValueError(f"malformed Airbus FIN: {v!r}")
        return v.upper() if v else v

    @field_validator("fig_item")
    @classmethod
    def check_fig_item(cls, v: Optional[str]) -> Optional[str]:
        if v is not None and not _BOEING_FIGITEM.match(v):
            raise ValueError(f"malformed Boeing figure-item: {v!r}")
        return v


class CanonicalPart(BaseModel):
    model_config = ConfigDict(extra="forbid")

    canonical_key: str
    primary_oem: OEM
    airbus_pn: Optional[str] = None
    boeing_pn: Optional[str] = None
    ata_chapter: str
    aliases: tuple[str, ...] = ()


class PartIdentityResolver:
    """Any known native token resolves to one canonical identity, regardless
    of which manufacturer feed introduced it first."""

    def __init__(self) -> None:
        self._alias_index: dict[str, str] = {}
        self._parts: dict[str, CanonicalPart] = {}

    @staticmethod
    def _key(ref: RawPartRef) -> str:
        return f"{ref.oem.value}:{ref.native_pn}"

    def resolve(
        self, ref: RawPartRef, equivalents: Optional[list[RawPartRef]] = None
    ) -> CanonicalPart:
        all_refs = [ref, *(equivalents or [])]
        existing = next(
            (self._alias_index[self._key(r)] for r in all_refs if self._key(r) in self._alias_index),
            None,
        )
        canonical_key = existing or self._key(ref)

        airbus = next((r.native_pn for r in all_refs if r.oem is OEM.AIRBUS), None)
        boeing = next((r.native_pn for r in all_refs if r.oem is OEM.BOEING), None)
        aliases = tuple(sorted({self._key(r) for r in all_refs}))

        part = CanonicalPart(
            canonical_key=canonical_key,
            primary_oem=ref.oem,
            airbus_pn=airbus,
            boeing_pn=boeing,
            ata_chapter=ref.ata_chapter,
            aliases=aliases,
        )
        for alias in aliases:
            self._alias_index[alias] = canonical_key
        self._parts[canonical_key] = part
        logger.info("resolved %s -> %s (%d aliases)", ref.native_pn, canonical_key, len(aliases))
        return part

Worked example

Feeding the spoiler-actuator case through the resolver:

resolver = PartIdentityResolver()
airbus_ref = RawPartRef(oem="AIRBUS", native_pn="D5-8375-0002-0000", fin="24VU", ata_chapter="27-64")
boeing_ref = RawPartRef(oem="BOEING", native_pn="BACA20AP12", fig_item="27-31-04", ata_chapter="27-64")
part = resolver.resolve(airbus_ref, equivalents=[boeing_ref])

The Airbus native_pn normalizes from D5-8375-0002-0000 to D5837500020000; the Boeing token stays BACA20AP12. The emitted CanonicalPart carries canonical_key="AIRBUS:D5837500020000", both airbus_pn and boeing_pn populated, and aliases=("AIRBUS:D5837500020000", "BOEING:BACA20AP12"). When a later work order references only the Boeing token, resolver.resolve(RawPartRef(oem="BOEING", native_pn="BACA 20 AP 12", ata_chapter="27-64")) finds BOEING:BACA20AP12 already indexed and returns the same canonical key — no duplicate identity is created. Downstream, this single record can be handed to the compliance gate described in Schema Validation & Error Handling with confidence that both manufacturers’ catalogues point at one lifecycle history.

Edge cases & compliance gotchas

  • Dash-splitting inside Airbus part numbers. Some Airbus feeds break long part numbers across fields; concatenate before normalization or the separator strip will not reunite them, producing two canonical keys for one part.
  • Boeing figure-item is location, not identity. The IPC figure-item 27-31-04 describes where a part appears in a catalogue, not the part itself; never fold it into the canonical key or an interchangeable part on a different figure will be lost.
  • Vendor-code collisions. A raw base part number like 20AP12 may exist under multiple vendor codes; keep the OEM prefix in the canonical key so two vendors’ identically-numbered parts stay distinct.
  • Effectivity narrowing. A part interchangeable on early MSNs may be superseded on later ones. Store the ATA chapter and effectivity with the canonical record rather than assuming one alias set holds for the aircraft’s whole life.

A deterministic resolver is what lets a multi-fleet MRO trust that an Airbus removal and a Boeing installation of the same rotable read as one continuous history. By preserving native forms, validating each manufacturer’s decorations, and indexing every token to a single canonical key, the pipeline eliminates the silent duplicates that otherwise surface only during an audit — when a component’s life cannot be reconciled and the record loses its evidentiary value.