Skip to content

Bug: flight_plan_utils.addRunway only handles 8-char xxx(yyy) form, garbling shorter pairings #466

Description

@kevinelliott

Summary

addRunway in lib/utils/flight_plan_utils.ts parses the R: flight-plan element with hard-coded offsets that assume the value is exactly 8 chars (xxx(yyy)). For any value of length 1–7 the function (a) skips the arrival runway entirely and (b) emits a wrong departure-runway string sliced out of whatever value.substring(0, 3) happens to land on. For length 1–3 single-runway values it happens to produce a correct departure, but for the common single-digit-runway shape 9L(27R) (7 chars) or 9(27R) (6 chars) the departure runway is set to '9L(' / '9(2' and the arrival side is silently dropped.

Real ACARS H1 FPN messages routinely carry runway pairings shorter than 8 chars (any runway whose number is a single digit — 1, 9, etc., or whose suffix is missing), so this is observable on production traffic, not just hand-rolled input.

Affected code

lib/utils/flight_plan_utils.ts:157-164

function addRunway(decodeResult: DecodeResult, value: string) {
  // xxx(yyy) where xxx is the departure runway and yyy is the arrival runway
  if (value.length === 8) {
    ResultFormatter.arrivalRunway(decodeResult, value.substring(4, 7));
  }

  ResultFormatter.departureRunway(decodeResult, value.substring(0, 3));
}

The function is dispatched from FlightPlanUtils.processFlightPlan at line 51 whenever the H1 flight-plan stream contains an R:<value> element. The same path is exercised by arinc_702_helper.ts:134 for RP/RS envelopes — so this affects every H1 FPN/POS decode whose route segment carries a runway field.

Reproduction

// 1. Single-letter runway pair (7 chars) — observed in real-world traffic.
addRunway(r, '9L(27R)');
// departure_runway: '9L('   ← garbage
// arrival_runway:   undefined

// 2. Single-digit dep / 3-char arrival (6 chars).
addRunway(r, '9(27R)');
// departure_runway: '9(2'
// arrival_runway:   undefined

// 3. 3-char departure / single-digit arrival (7 chars).
addRunway(r, '27L(9R)');
// departure_runway: '27L'   ← happens to be correct
// arrival_runway:   undefined ← silently dropped

// 4. Single-letter departure only (1-3 chars) — also goes through addRunway
//    via 'R:11O'-style elements like the one in Label_H1_FPN.test.ts:285:
addRunway(r, '11O');
// departure_runway: '11O'   ✓
// arrival_runway:   undefined ✓ (intentional — no arrival side)

// 5. Empty value (truncated message) — still emits an empty departure_runway entry.
addRunway(r, '');
// departure_runway: ''
// → ResultFormatter.departureRunway adds an items[] entry with value: ''

The existing test Label_H1_FPN.test.ts:15 carries R:27L(26O) (8 chars), which is the only shape the current code handles correctly, so the tests pass.

For the 8-char case the code also happens to do the right thing only because the ( lands at index 3 and the ) at index 7 — both single-character delimiters that the substrings naturally skip. Any deviation from that exact width breaks it.

Why it matters

Runway designators show up in route displays, ATC correlation, and any downstream lookup keyed on runway. Putting '9L(' (a non-runway string with an unbalanced paren) into raw.departure_runway corrupts those consumers, and silently dropping the arrival side hides half the information the flight crew filed. The truncated/empty-input case also pushes an empty items[] entry, which renders as a stray "Departure Runway: " line.

Suggested fix

Parse the format by structure rather than by fixed offset — value is "" or "()":

function addRunway(decodeResult: DecodeResult, value: string) {
  if (!value) return;
  const m = /^([^()]+)(?:\(([^()]+)\))?$/.exec(value);
  if (!m) return;
  const [, dep, arr] = m;
  ResultFormatter.departureRunway(decodeResult, dep);
  if (arr) {
    ResultFormatter.arrivalRunway(decodeResult, arr);
  }
}

That handles 3-char single-runway (11O, 27R), 6-char (9(27R)), 7-char in either direction (9L(27R), 27L(9R)), 8-char (27L(26O)), and rejects empty input. The early return on empty value also fixes the "stray empty Departure Runway entry" side effect.

Test coverage

There is no dedicated flight_plan_utils.test.ts. The only coverage of addRunway today is the indirect 8-char case via Label_H1_FPN.test.ts:285 (R:11O) — and that one only exercises the single-runway branch. A small set of targeted tests asserting:

  • R:9L(27R)departure_runway === '9L' and arrival_runway === '27R'
  • R:9(27R)departure_runway === '9' and arrival_runway === '27R'
  • R:27L(9R)departure_runway === '27L' and arrival_runway === '9R'
  • R:27L(26O) (regression for current happy path)
  • R:11O (regression for the existing single-runway case)
  • R: (no items emitted)

…would lock the fix in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions