ADS-B Entity Model

Table of Contents

Core Principle: Everything Changes

Aviation data is fundamentally temporal:

  • Aircraft change registration
  • Registrations change transponder codes
  • Airports change codes
  • Airlines merge, rename, cease operations
  • Flight numbers are reused daily

Rule: Every relationship has valid_from and valid_to timestamps.

Entity Hierarchy

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│                          IMMUTABLE (Physical)                               │
│                                                                             │
│    ┌──────────────┐                         ┌──────────────┐                │
│    │   AIRCRAFT   │                         │   AIRPORT    │                │
│    │──────────────│                         │──────────────│                │
│    │ serial_number│ ◄── Only truly fixed    │ coordinates  │                │
│    │ manufacturer │     identifier          │ name         │                │
│    │ model        │                         │ facility_type│                │
│    └──────────────┘                         └──────────────┘                │
│           │                                        │                        │
│           │ 1:many (changes over time)             │ 1:many                 │
│           ▼                                        ▼                        │
│    ┌──────────────┐                         ┌──────────────┐                │
│    │  IDENTITY    │                         │ AIRPORT_CODE │                │
│    │──────────────│                         │──────────────│                │
│    │ registration │                         │ code (ICAO)  │                │
│    │ valid_from   │                         │ valid_from   │                │
│    │ valid_to     │                         │ valid_to     │                │
│    └──────────────┘                         └──────────────┘                │
│           │                                                                 │
│           │ 1:many                                                          │
│           ▼                                                                 │
│    ┌──────────────┐                                                         │
│    │ TRANSPONDER  │                                                         │
│    │──────────────│                                                         │
│    │ mode_s_hex   │ ◄── What we see in ADS-B                                │
│    │ valid_from   │                                                         │
│    │ valid_to     │                                                         │
│    └──────────────┘                                                         │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                         OBSERVED (What we receive)                          │
│                                                                             │
│    ┌──────────────┐       ┌──────────────┐       ┌──────────────┐           │
│    │   SIGHTING   │──────►│   POSITION   │──────►│    FLIGHT    │           │
│    │──────────────│       │──────────────│       │──────────────│           │
│    │ mode_s_hex   │       │ coordinates  │       │ identifiers  │           │
│    │ timestamp    │       │ confidence   │       │ route        │           │
│    │ receiver_id  │       │ spoof_flag   │       │ timestamps   │           │
│    └──────────────┘       └──────────────┘       └──────────────┘           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

The Identity Resolution Problem

What we OBSERVE (ADS-B):          What we WANT TO KNOW:
═══════════════════════           ═════════════════════

mode_s_hex: "A12345"    ────────► Which AIRCRAFT is this?
callsign: "UAL123"      ────────► Which FLIGHT is this?
position: 42.3, -71.0   ────────► Is this REAL or SPOOFED?


RESOLUTION CHAIN:

mode_s_hex ──► TransponderAssignment ──► Aircraft ──► Registration
     │                                       │
     │                                       └──► Current Operator
     │
     └──────► Sighting ──► Flight (if callsign matches schedule)

The Stale Transponder Problem

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  REALITY:                                                                   │
│                                                                             │
│  Aircraft Serial: 12345                                                     │
│  Current Registration: N67890 (since 2020-06-16)                            │
│  Current Mode S (per FAA): A00003                                           │
│                                                                             │
│  WHAT WE SEE IN ADS-B:                                                      │
│                                                                             │
│  Mode S: A00002 (OLD! From previous registration!)                          │
│  Callsign: "N67890" (pilot entered current registration)                    │
│                                                                             │
│  MISMATCH DETECTED - but this is VALID:                                     │
│  Transponder just wasn't reprogrammed after re-registration                 │
│                                                                             │
│  RESOLUTION:                                                                │
│  - Record the conflict                                                      │
│  - Trust the callsign (pilot knows their registration)                      │
│  - DON'T assume either is "wrong"                                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Confidence Scoring

Every piece of data has a confidence score:

Level Score Meaning
VERIFIED 1.0 Multiple authoritative sources agree
HIGH 0.8 Single authoritative source
MEDIUM 0.5 Derived or inferred from other data
LOW 0.3 Single non-authoritative source
UNVERIFIED 0.1 Raw input, not yet validated
CONFLICTING 0.0 Sources actively disagree

Examples

  • Position from ADS-B with MLAT confirmation: VERIFIED (1.0)
  • Position from single receiver, normal signal: HIGH (0.8)
  • Position estimated from last known + velocity: MEDIUM (0.5)
  • Position from single receiver, weak signal: LOW (0.3)
  • Position differs between receivers: CONFLICTING (0.0)

Audit Trail: Never Delete

Rule: Never delete, only supersede.

Record A (original)
├── id: 001
├── callsign: "UAL123"
├── created_at: 2025-12-28T19:00:00Z
├── supersedes_id: null
└── superseded_by_id: 002  ◄─── Points to correction

Record B (correction)
├── id: 002
├── callsign: "UAL124"     ◄─── Corrected value
├── created_at: 2025-12-28T19:05:00Z
├── supersedes_id: 001     ◄─── Points to original
└── superseded_by_id: null

Query Patterns

-- Current value
SELECT * FROM records WHERE superseded_by_id IS NULL;

-- History of changes
SELECT * FROM records
WHERE id = 001 OR supersedes_id = 001
ORDER BY created_at;

-- Value at specific time
SELECT * FROM records
WHERE created_at <= '2025-12-28T19:02:00Z'
  AND (superseded_at IS NULL
       OR superseded_at > '2025-12-28T19:02:00Z');

Key Falsehoods Addressed

From FlightAware's Falsehoods article:

Flights

  • Flight numbers are NOT unique (reused daily)
  • Flights can have MULTIPLE identifiers (codeshares)
  • Flight numbers can CHANGE mid-flight

Aircraft

  • Registration is NOT permanent (aircraft re-register)
  • Mode S address can be STALE (not updated after re-reg)
  • "NULL" is a valid callsign (someone set their transponder to it)

Airports

  • ICAO codes are NOT permanent (airports move, close)
  • Not everything with an ICAO code is an airport (Mars has one: JZRO)
  • Railway stations have IATA codes

Positions

  • GPS positions can be SPOOFED
  • Positions can "teleport" due to errors
  • Multiple altitude definitions exist (barometric, geometric, flight level)

Base Classes

@dataclass
class TemporalMixin:
    """All aviation data is temporal."""
    valid_from: datetime
    valid_to: Optional[datetime] = None  # None = current

    @property
    def is_current(self) -> bool:
        return self.valid_to is None


@dataclass
class AuditMixin:
    """Track provenance of every piece of data."""
    id: UUID
    created_at: datetime
    source: str
    supersedes_id: Optional[UUID] = None
    superseded_by_id: Optional[UUID] = None
    confidence: float = 0.5

Implementation Order

Week Focus
1 Core entities: RawSBSMessage, MergedSighting, Quarantine
2 Validation: Callsign, Position, Spoof detection
3 Temporal model: TemporalMixin, AuditMixin, ConfidenceScore
4 Identity resolution: Aircraft, Identity, Transponder
5 Flight model: Flight, FlightIdentifier, FlightLeg
6 Enrichment: OpenSky integration, Operator resolution

Author: Jason Walsh

jwalsh@nexus

Last Updated: 2025-12-29 17:08:19

build: 2026-04-17 18:35 | sha: 792b203