{
  "name": "Flight bookings",
  "nodes": [
    {
      "id": "n_airline",
      "name": "Airline",
      "labels": ["Airline", "Carrier"],
      "shape": "circle",
      "description": "A carrier licensed to sell and operate air services under its own two-letter designator. Appears twice in this model on purpose: as the airline that markets a segment, and as the airline that operates the flight.",
      "properties": [
        { "name": "iataCode", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "Two-character IATA airline designator, such as BA. Short, stable and printed on every ticket, which makes it the natural key." },
        { "name": "icaoCode", "type": "string", "unique": true, "description": "Three-letter ICAO designator, used in air traffic control rather than in retail. Held alongside the IATA code because operational feeds key on it." },
        { "name": "name", "type": "string", "required": true, "description": "Registered trading name of the carrier." },
        { "name": "countryCode", "type": "string", "required": true, "description": "ISO 3166-1 alpha-2 code of the state that issued the carrier's operating licence." },
        { "name": "allianceName", "type": "enum", "default": "none", "description": "Global alliance the carrier belongs to, if any: none, star_alliance, oneworld or skyteam. Drives interline and loyalty rules." }
      ]
    },
    {
      "id": "n_airport",
      "name": "Airport",
      "labels": ["Airport", "Location"],
      "shape": "circle",
      "description": "A civil airfield with a published identifier that flights are scheduled to and from. Deliberately not connected to another airport by any relationship — the thing that connects two airports is a Flight, and that is the point this model makes.",
      "properties": [
        { "name": "iataCode", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "Three-letter IATA location identifier, such as LHR. The code passengers see, and the natural key." },
        { "name": "icaoCode", "type": "string", "unique": true, "description": "Four-letter ICAO location indicator, such as EGLL. Unlike the IATA code it is assigned to every aerodrome, not only those with commercial service." },
        { "name": "name", "type": "string", "required": true, "description": "Published name of the airport." },
        { "name": "city", "type": "string", "description": "The city the airport serves, which is not always the city it sits in." },
        { "name": "countryCode", "type": "string", "required": true, "description": "ISO 3166-1 alpha-2 code of the state the airport is in." },
        { "name": "location", "type": "point", "description": "WGS84 position of the airport reference point. Enough to compute great-circle distance between two airports without storing route lengths." },
        { "name": "timeZone", "type": "string", "required": true, "description": "IANA time zone name, such as Europe/London. Required rather than optional: scheduled times are published in local time, so elapsed journey time cannot be derived without it." }
      ]
    },
    {
      "id": "n_terminal",
      "name": "Terminal",
      "labels": ["Terminal"],
      "shape": "circle",
      "description": "A passenger building at an airport, with its own check-in area and its own set of gates. Modelled separately from the airport because connection times depend on which terminals a transfer runs between.",
      "properties": [
        { "name": "terminalId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier. A terminal's own code is unique only within its airport, so it cannot serve as the key on its own." },
        { "name": "code", "type": "string", "required": true, "description": "Terminal designation as published to passengers, such as 5 or S. Unique within the airport only." },
        { "name": "name", "type": "string", "description": "Marketing name where the terminal has one distinct from its code." },
        { "name": "minConnectionMinutes", "type": "integer", "description": "Shortest connection the airport will sell for a transfer arriving into this terminal. Drives whether an itinerary is legal to book." }
      ]
    },
    {
      "id": "n_gate",
      "name": "Gate",
      "labels": ["Gate"],
      "shape": "circle",
      "description": "A boarding position in a terminal where a flight's passengers join the aircraft. A scarce resource shared by many flights across a day, which is why the flight-to-gate assignment carries its own facts.",
      "properties": [
        { "name": "gateId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier. Gate codes repeat across terminals and airports, so they are not unique by themselves." },
        { "name": "code", "type": "string", "required": true, "description": "Gate designation shown on departure boards, such as A10." },
        { "name": "isContact", "type": "boolean", "default": "true", "description": "True when the gate has an air bridge onto the aircraft; false for a remote stand reached by bus." },
        { "name": "maxWingspanMetres", "type": "float", "description": "Largest wingspan the stand can accept. Determines which aircraft types can be assigned here." }
      ]
    },
    {
      "id": "n_aircraft_type",
      "name": "AircraftType",
      "labels": ["AircraftType"],
      "shape": "square",
      "description": "A certified airframe model. Fixes the performance limits and the baseline cabin layout that every aircraft of that type shares. Reference data: it changes when a manufacturer certifies a variant, not when an airline does anything.",
      "properties": [
        { "name": "icaoTypeCode", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "ICAO aircraft type designator, such as A320 or B789. The industry-standard identifier for a model." },
        { "name": "manufacturer", "type": "string", "required": true, "description": "Company that builds the type, such as Airbus or Boeing." },
        { "name": "model", "type": "string", "required": true, "description": "Marketing model name, such as A320neo." },
        { "name": "typicalSeatCount", "type": "integer", "description": "Representative seat count for the type. Indicative only — the fitted count belongs to the individual aircraft." },
        { "name": "maxRangeKm", "type": "integer", "description": "Maximum still-air range in kilometres. Bounds which routes the type can be scheduled on." }
      ]
    },
    {
      "id": "n_aircraft",
      "name": "Aircraft",
      "labels": ["Aircraft", "Asset"],
      "shape": "circle",
      "description": "A single physical airframe. Kept distinct from its type because two aircraft of one type routinely differ in the ways that matter to a booking: fitted seat count, cabin configuration and whether the airframe is currently airworthy.",
      "properties": [
        { "name": "registration", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "National registration painted on the airframe, such as G-EUUU. Unique worldwide and stable for the life of the aircraft in that register." },
        { "name": "serialNumber", "type": "string", "unique": true, "description": "Manufacturer's serial number. Survives re-registration, so it identifies the airframe across changes of owner and country." },
        { "name": "deliveredOn", "type": "date", "description": "Date the airframe entered service with its first operator." },
        { "name": "fittedSeatCount", "type": "integer", "description": "Seats actually installed on this airframe, which can differ from the type's typical count after a cabin refit." },
        { "name": "isAirworthy", "type": "boolean", "default": "true", "description": "False while the aircraft is grounded for maintenance, which removes it from the pool a flight can be assigned from." }
      ]
    },
    {
      "id": "n_flight",
      "name": "Flight",
      "labels": ["Flight", "Operation"],
      "shape": "diamond",
      "description": "One operation of a route on one day. This is the node the model exists to argue for: a flight looks like a relationship between two airports and cannot be one, because it carries its own facts, its own identity and its own relationships — an aircraft, a crew, a gate, a status, and every segment sold on it. Two flights can run the same city pair on the same day, which a single edge could not represent.",
      "properties": [
        { "name": "flightId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier. The published designator repeats every day the route runs, so it identifies a schedule rather than an operation." },
        { "name": "designator", "type": "string", "required": true, "indexed": true, "description": "Operating carrier code plus flight number, such as BA117. Identifies the schedule, not this particular operation of it." },
        { "name": "departureDate", "type": "date", "required": true, "indexed": true, "description": "Date of operation at the origin airport. Together with the designator this is how the industry names a single flight." },
        { "name": "scheduledDepartureAt", "type": "datetime", "required": true, "indexed": true, "description": "Published off-blocks time, in the origin airport's local time zone." },
        { "name": "scheduledArrivalAt", "type": "datetime", "required": true, "description": "Published on-blocks time, in the destination airport's local time zone. Cannot be compared with the departure time without both airports' zones." },
        { "name": "scheduledBlockTime", "type": "duration", "description": "Gate-to-gate time the schedule allows. Stored rather than derived, because it is the number the schedule was built against." },
        { "name": "actualDepartureAt", "type": "datetime", "description": "Time the aircraft actually left the stand. Null until it does." },
        { "name": "status", "type": "enum", "required": true, "default": "scheduled", "description": "Operational state: scheduled, boarding, departed, arrived, delayed, cancelled or diverted. A cancellation here does not cancel the segments sold on it — those need reaccommodating, which is why they are separate things." }
      ]
    },
    {
      "id": "n_passenger",
      "name": "Passenger",
      "labels": ["Passenger", "Person"],
      "shape": "circle",
      "description": "A person carried, or booked to be carried, on a flight. Distinct from the booking that pays for the carriage: one person travels on many bookings over time, and one booking covers several people.",
      "properties": [
        { "name": "passengerId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier. Names are neither unique nor stable, and not every passenger holds a loyalty number." },
        { "name": "givenName", "type": "string", "required": true, "description": "Given name as it appears in the travel document." },
        { "name": "familyName", "type": "string", "required": true, "description": "Family name as it appears in the travel document. Must match the document or the passenger is refused at check-in." },
        { "name": "dateOfBirth", "type": "date", "description": "Required for international carriage and for infant and child fares." },
        { "name": "frequentFlyerNumber", "type": "string", "indexed": true, "description": "Loyalty programme membership, where the passenger has one. Indexed because it is a common lookup path, but optional and so not a key." }
      ]
    },
    {
      "id": "n_booking",
      "name": "Booking",
      "labels": ["Booking", "Contract"],
      "shape": "diamond",
      "description": "A reservation — the contract of carriage. Groups the passengers travelling together with the segments they bought and the money that was taken. Reified for the same reason as Flight: it has an identity passengers quote, a lifecycle, and facts of its own.",
      "properties": [
        { "name": "recordLocator", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "Six-character passenger name record locator, such as X4F2QP. The reference a passenger is given and quotes back, which makes it the natural key." },
        { "name": "bookedAt", "type": "datetime", "required": true, "description": "When the reservation was confirmed. Fixes which fare rules and currency conversion applied." },
        { "name": "channel", "type": "enum", "required": true, "default": "direct", "description": "How the booking was made: direct, agency, interline or corporate. Determines who owns the customer relationship and who is owed commission." },
        { "name": "totalAmount", "type": "float", "required": true, "description": "Total charged for the reservation, including taxes and surcharges." },
        { "name": "currencyCode", "type": "string", "required": true, "description": "ISO 4217 code of the currency actually charged, which follows the point of sale rather than the route." },
        { "name": "status", "type": "enum", "required": true, "default": "confirmed", "description": "Reservation state: held, confirmed, ticketed, cancelled or refunded." }
      ]
    },
    {
      "id": "n_segment",
      "name": "Segment",
      "labels": ["Segment"],
      "shape": "diamond",
      "description": "One flight's worth of carriage as it was sold. The second half of this model's argument: a segment is what the passenger bought, a flight is what actually operates, and they are not the same thing. A codeshare segment is marketed by one airline and flown by another; a cancelled flight leaves its segments still sold and needing reaccommodation.",
      "properties": [
        { "name": "segmentId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier for the sold item." },
        { "name": "sequence", "type": "integer", "required": true, "description": "Position of this segment within the booking's itinerary, counting from 1. Order matters: it is what makes a set of segments a journey." },
        { "name": "cabin", "type": "enum", "required": true, "default": "economy", "description": "Cabin sold: economy, premium_economy, business or first. A property of the sale, not of the flight — one flight sells several cabins." },
        { "name": "status", "type": "enum", "required": true, "default": "confirmed", "description": "State of the sold carriage: confirmed, waitlisted, flown, cancelled or reaccommodated. Moves independently of the flight's own status." },
        { "name": "checkedBagAllowance", "type": "integer", "default": "0", "description": "Bags included on this segment, which can differ from the fare's baseline after a loyalty or card benefit is applied." }
      ]
    },
    {
      "id": "n_fare",
      "name": "Fare",
      "labels": ["Fare", "Product"],
      "shape": "square",
      "description": "The priced conditions a segment was sold under: what it cost and what may afterwards be changed. Reference data filed by the carrier, referenced by many segments and rarely edited once published.",
      "properties": [
        { "name": "fareBasisCode", "type": "string", "key": true, "required": true, "unique": true, "indexed": true, "description": "Fare basis code, such as YFLEX. The carrier's own identifier for a set of conditions, printed on the ticket." },
        { "name": "cabin", "type": "enum", "required": true, "default": "economy", "description": "Cabin the fare is filed for: economy, premium_economy, business or first." },
        { "name": "amount", "type": "float", "required": true, "description": "Base fare before taxes, fees and surcharges." },
        { "name": "currencyCode", "type": "string", "required": true, "description": "ISO 4217 code the fare is filed in, which is set by the country of sale." },
        { "name": "isRefundable", "type": "boolean", "required": true, "default": "false", "description": "Whether the fare may be refunded rather than only changed. The single condition passengers ask about most." },
        { "name": "changeFee", "type": "float", "default": "0", "description": "Charge to alter travel dates, in the fare's own currency. Zero on a fully flexible fare." },
        { "name": "advancePurchaseDays", "type": "integer", "default": "0", "description": "Minimum days before departure the fare may be sold. The main reason an otherwise-available fare cannot be booked." }
      ]
    },
    {
      "id": "n_seat",
      "name": "Seat",
      "labels": ["Seat"],
      "shape": "square",
      "description": "A seat position in a cabin layout. Belongs to the aircraft type rather than to a flight: 14A is the same physical place on every airframe built to that layout, and it is the boarding pass that ties a person to it for one journey.",
      "properties": [
        { "name": "seatId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier. A seat number repeats across every aircraft type, so it is not unique by itself." },
        { "name": "seatNumber", "type": "string", "required": true, "description": "Row and column as printed on the seat, such as 14A." },
        { "name": "cabin", "type": "enum", "required": true, "default": "economy", "description": "Cabin the seat sits in: economy, premium_economy, business or first." },
        { "name": "isWindow", "type": "boolean", "default": "false", "description": "True for a window seat. Stored rather than derived from the column letter, which varies by layout." },
        { "name": "isExitRow", "type": "boolean", "default": "false", "description": "True where the seat is at an emergency exit, which carries occupancy restrictions and usually a fee." },
        { "name": "pitchInches", "type": "float", "description": "Distance to the seat in front. The number seat-selection tools compare." }
      ]
    },
    {
      "id": "n_boarding_pass",
      "name": "BoardingPass",
      "labels": ["BoardingPass"],
      "shape": "diamond",
      "description": "Authority for one passenger to board one segment. Reified because it is where three things finally meet that were separate until check-in: the person, the sold segment, and a specific seat.",
      "properties": [
        { "name": "boardingPassId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier for the issued pass." },
        { "name": "sequenceNumber", "type": "integer", "required": true, "description": "Check-in order for the segment, counting from 1. Used to decide who is offloaded first when a flight is oversold." },
        { "name": "issuedAt", "type": "datetime", "required": true, "description": "When check-in completed and the pass was issued." },
        { "name": "boardingGroup", "type": "string", "description": "Group called to board, such as 2. Derived at issue from cabin and loyalty status, then fixed." },
        { "name": "barcodeData", "type": "string", "description": "IATA BCBP payload encoded in the PDF417 symbol. Held as the exact string that was encoded, because a re-derived one may not scan." },
        { "name": "scannedAt", "type": "datetime", "description": "When the pass was scanned at the gate. Null until the passenger boards, which is how a no-show is detected." }
      ]
    },
    {
      "id": "n_crew_member",
      "name": "CrewMember",
      "labels": ["CrewMember", "Person"],
      "shape": "circle",
      "description": "A member of flight or cabin crew employed by an airline. Holds what is true of the person; what is true only of one rostering — the role worked, the report time — belongs on the assignment instead.",
      "properties": [
        { "name": "crewId", "type": "uuid", "key": true, "required": true, "unique": true, "indexed": true, "description": "Surrogate identifier for the crew member." },
        { "name": "employeeNumber", "type": "string", "required": true, "unique": true, "description": "The airline's own staff number. Unique within the carrier, and the reference rostering systems use." },
        { "name": "rank", "type": "enum", "required": true, "default": "cabin_crew", "description": "Substantive rank held: captain, first_officer, purser or cabin_crew. What the person is qualified as, which is not always what they work on a given flight." },
        { "name": "licenceExpiresOn", "type": "date", "description": "Expiry of the licence or certificate the rank depends on. A crew member cannot be rostered past it." },
        { "name": "baseAirportCode", "type": "string", "description": "IATA code of the airport the crew member starts and ends duty at. Determines whether a rostering needs positioning travel." }
      ]
    }
  ],
  "relationships": [
    {
      "type": "DEPARTS_FROM", "from": "n_flight", "to": "n_airport", "cardinality": "n-1",
      "description": "The airport the flight is scheduled to leave from. One of the two edges that a naive model would have collapsed into a single Airport-to-Airport relationship."
    },
    {
      "type": "ARRIVES_AT", "from": "n_flight", "to": "n_airport", "cardinality": "n-1",
      "description": "The airport the flight is scheduled to reach. Separate from the departure edge because a flight points at each end for a different reason, and a diversion changes only this one."
    },
    {
      "type": "OPERATED_BY", "from": "n_flight", "to": "n_airline", "cardinality": "n-1",
      "description": "The carrier that actually flies the aircraft and holds the operating licence for the service. Not necessarily the carrier that sold the seat."
    },
    {
      "type": "FLOWN_BY", "from": "n_flight", "to": "n_aircraft", "cardinality": "n-1",
      "description": "The airframe rostered to operate this flight. Reassigned freely up to departure, which is why it is a relationship on the flight rather than an attribute of the schedule."
    },
    {
      "type": "IS_TYPE", "from": "n_aircraft", "to": "n_aircraft_type", "cardinality": "n-1",
      "description": "The certified model the airframe is built to, and therefore the cabin layout its seats come from."
    },
    {
      "type": "OPERATES", "from": "n_airline", "to": "n_aircraft", "cardinality": "1-n",
      "description": "The carrier whose fleet the airframe currently belongs to."
    },
    {
      "type": "HAS_TERMINAL", "from": "n_airport", "to": "n_terminal", "cardinality": "1-n",
      "description": "The airport a terminal building stands at."
    },
    {
      "type": "HAS_GATE", "from": "n_terminal", "to": "n_gate", "cardinality": "1-n",
      "description": "The terminal a gate is inside. Two-step rather than airport-to-gate directly, because a transfer's legality depends on the terminals involved."
    },
    {
      "type": "BOARDS_AT", "from": "n_flight", "to": "n_gate", "cardinality": "n-1",
      "description": "The gate a flight boards from. The facts below sit on the assignment rather than on either side: the same gate serves many flights a day, and a flight's gate can change after it is published.",
      "properties": [
        { "name": "assignedAt", "type": "datetime", "required": true, "description": "When this gate was assigned to the flight. A later assignment supersedes an earlier one." },
        { "name": "isFinal", "type": "boolean", "default": "false", "description": "True once the assignment is confirmed and will not change again, which is when it is safe to print on a boarding pass." },
        { "name": "changeReason", "type": "string", "description": "Why the gate moved, where it did: aircraft_swap, delay, stand_conflict. Blank on a first assignment." }
      ]
    },
    {
      "type": "INCLUDES", "from": "n_booking", "to": "n_segment", "cardinality": "1-n",
      "description": "The segments bought on this reservation. Ordered by the segment's own sequence, which is what turns a set of flights into an itinerary."
    },
    {
      "type": "CARRIED_ON", "from": "n_segment", "to": "n_flight", "cardinality": "n-1",
      "description": "Ties what was sold to what operates, and the single most important edge in this model. Many segments, drawn from many separate bookings, ride one flight; each segment points at exactly one. This is the relationship that becomes impossible to express if a flight is modelled as an edge between airports."
    },
    {
      "type": "PRICED_AT", "from": "n_segment", "to": "n_fare", "cardinality": "n-1",
      "description": "The filed fare basis the segment was sold under, and therefore the conditions that govern changing or refunding it."
    },
    {
      "type": "MARKETED_BY", "from": "n_segment", "to": "n_airline", "cardinality": "n-1",
      "description": "The carrier whose designator the passenger bought under. On a codeshare this differs from the operating carrier on the flight — the clearest everyday proof that a sold segment cannot simply be an attribute of a flight."
    },
    {
      "type": "COVERS", "from": "n_booking", "to": "n_passenger", "cardinality": "n-n",
      "description": "The passengers travelling on this reservation. Many-to-many in both directions: one booking carries a family, and one person accumulates bookings over years."
    },
    {
      "type": "ISSUED_TO", "from": "n_boarding_pass", "to": "n_passenger", "cardinality": "n-1",
      "description": "The passenger the pass admits to the aircraft. Named on the pass and checked against the travel document."
    },
    {
      "type": "FOR_SEGMENT", "from": "n_boarding_pass", "to": "n_segment", "cardinality": "n-1",
      "description": "The sold segment the pass is authority to travel. One pass per passenger per segment, so a family of four on a two-leg trip generates eight."
    },
    {
      "type": "ASSIGNED_SEAT", "from": "n_boarding_pass", "to": "n_seat", "cardinality": "n-1",
      "description": "The seat position the passenger holds for this segment. Sits on the boarding pass rather than on the segment because a seat is only fixed at check-in, and the same position is reused on every later flight."
    },
    {
      "type": "LOCATED_ON", "from": "n_seat", "to": "n_aircraft_type", "cardinality": "n-1",
      "description": "The cabin layout the seat position belongs to. Anchoring seats to the type rather than to individual aircraft keeps one layout shared across a fleet."
    },
    {
      "type": "CREWED_BY", "from": "n_flight", "to": "n_crew_member", "cardinality": "n-n",
      "description": "The crew rostered to work a flight. The role sits on the assignment, not on the person: a qualified captain can be rostered as a relief pilot, and a cabin crew member can work as purser on one sector and not the next.",
      "properties": [
        { "name": "roleOnFlight", "type": "enum", "required": true, "default": "cabin_crew", "description": "Role actually worked on this flight: captain, first_officer, relief_pilot, purser or cabin_crew. Can sit below the person's substantive rank." },
        { "name": "reportingTimeAt", "type": "datetime", "required": true, "description": "When the crew member must report for duty, which is earlier than departure and differs by role." },
        { "name": "isPositioning", "type": "boolean", "default": "false", "description": "True when the crew member is carried as a passenger to reach another duty rather than working this flight." }
      ]
    }
  ]
}
