1/** 2 * Delay past this threshold (either leg) counts as a schedule deviation. 3 */ 4const DEVIATION_THRESHOLD_SECONDS = 60; 5 6/** 7 * @param {Object} flight Flight row from AeroAPI / GuestFlightService 8 * @returns {boolean} 9 */ 10export function isScheduleDeviated(flight) { 11 return Math.abs(flight.departure_delay ?? 0) > DEVIATION_THRESHOLD_SECONDS 12 || Math.abs(flight.arrival_delay ?? 0) > DEVIATION_THRESHOLD_SECONDS; 13} 14 15/** 16 * Classify flight into one of four canonical statuses. Diverted flights are treated as 17 * canceled, since both mean the original itinerary no longer applies. 18 * @param {Object} flight 19 * @returns {{ key: 'canceled'|'landed'|'updated'|'on-time', label: string }} 20 */ 21export function classifyFlightStatus(flight) { 22 if (flight.cancelled) return { key: 'canceled', label: 'Canceled' }; 23 if (flight.diverted) return { key: 'canceled', label: 'Diverted' }; 24 // actual_on refers to the time the flight landed on the runway. progress_percent isn't part 25 // of the AeroAPI webhook payload, so it goes stale after the first webhook update; actual_on 26 // alone is the reliable signal once a flight is webhook-tracked. 27 if (flight.actual_on) return { key: 'landed', label: 'Landed' }; 28 if (isScheduleDeviated(flight)) return { key: 'updated', label: 'Updated' }; 29 return { key: 'on-time', label: 'On time' }; 30} 31 32/** 33 * Build a Google Flights airline logo URL from an IATA carrier code. 34 * Falls back to extracting a prefix from fallbackIdent for rows cached before operating/operator 35 * IATA codes were tracked. 36 * @param {string|null} carrierIata e.g. "YX", the carrier's own IATA code 37 * @param {string|null} fallbackIdent e.g. "DL5713", used only when carrierIata is empty 38 * @returns {string|null} Logo URL, or null if no code can be resolved 39 */ 40export function getAirlineLogoUrl(carrierIata, fallbackIdent) { 41 const code = (carrierIata || '').trim().toUpperCase() 42 || (fallbackIdent || '').match(/^([A-Z0-9]{2})/i)?.[1]?.toUpperCase() 43 || null; 44 return code ? `https://www.gstatic.com/flights/airline_logos/70px/${code}.png` : null; 45} 46 47/** 48 * Resolve the airline identity to display for a flight, always the operating carrier (who 49 * actually flies the plane), per design, not the marketing carrier the EO entered. Falls back 50 * to the marketing carrier's fields for rows cached before operating_carrier* was tracked. 51 * @param {Object} flight 52 * @returns {{ name: string, iata: string }} 53 */ 54export function resolveDisplayAirline(flight) { 55 return { 56 name: flight.operating_carrier_name || flight.airline_name || '', 57 iata: flight.operating_carrier_iata || flight.operator_iata || '', 58 }; 59} 60 61/** 62 * Build the flight-number line per design: the ident the EO entered as-is when they also 63 * entered the operating carrier; otherwise annotated with "(Operated by {operating flight})" 64 * so the EO's own entry still displays but shows who's actually flying it. 65 * @param {Object} flight 66 * @returns {string} 67 */ 68export function formatFlightNumberDisplay(flight) { 69 const entered = flight.ident_iata || flight.ident || ''; 70 const operatedByOther = flight.operator && flight.operating_carrier && flight.operator !== flight.operating_carrier; 71 if (!operatedByOther) return entered; 72 73 const operatingFlight = flight.operating_flight_iata || flight.operating_flight_icao || ''; 74 return operatingFlight ? `${entered} (Operated by ${operatingFlight})` : entered; 75} 76 77/** 78 * Normalize an AeroAPI location field, either a plain IATA string or an object with code/name/city/timezone. 79 * @param {string|Object|null} loc 80 * @returns {{ code: string, name: string, city: string, timezone: string|null }} 81 */ 82export function parseAirportLocation(loc) { 83 if (!loc) return { code: '', name: '', city: '', timezone: null }; 84 if (typeof loc === 'string') return { code: loc, name: '', city: '', timezone: null }; 85 return { 86 code: loc.code_iata || loc.code || '', 87 name: loc.name || '', 88 city: loc.city || '', 89 timezone: loc.timezone ?? null, 90 }; 91}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.