Skip to content

fix(flights): match aircraft types exactly, not as substrings (609 -> 27 false 'military' contacts) - #32

Open
ihubanov wants to merge 1 commit into
NoblerWorks-HQ:mainfrom
ihubanov:fix/exact-aircraft-type-matching
Open

fix(flights): match aircraft types exactly, not as substrings (609 -> 27 false 'military' contacts)#32
ihubanov wants to merge 1 commit into
NoblerWorks-HQ:mainfrom
ihubanov:fix/exact-aircraft-type-matching

Conversation

@ihubanov

@ihubanov ihubanov commented Aug 17, 2026

Copy link
Copy Markdown

isMilitaryType() matches type designators as substrings:

return MILITARY_AIRCRAFT_TYPES.some(mt => upper.includes(mt));

The list contains short codes (C17, C5, C2, E3, B2), so any ICAO type that merely contains one of them matches. A lot of ordinary civil traffic ends up counted as military.

I pulled a live regional feed for the Iran/Israel theater (lat/30/lon/48/dist/2500, 2754 aircraft) and compared what the heuristics flag against adsb.lol's own dbFlags military bit:

  21   carry the dbFlags military bit
 588   added by the heuristics
~609   reported by the panel

What the substring rule was actually catching:

490x  B738        ordinary 737-800s
 17x  A332
 12x  C172   ->   matched C17, rendered "Strategic Airlift (C-17)"
 11x  C56X   ->   matched C5,  rendered "Strategic Airlift (C-5)"
  6x  E35L   ->   matched E3,  rendered "AWACS"
  8x  C25A/B ->   matched C2
  3x  PC24   ->   matched C2

So Cessna 172s were showing up as C-17 Globemasters and Embraer Legacy bizjets as E-3 AWACS. classifyAircraft() has the same problem; its t.includes('B2') branch labels any Beechcraft King Air (B200) a bomber.

isMilitaryCallsign() matches bare prefixes, so SAMU42 and SAMU06 (French SAMU air ambulances) match the US SAM Special Air Mission prefix.

Changes

MILITARY_AIRCRAFT_TYPES is a Set now, matched exactly. Converting it turned up several entries that aren't real ICAO Doc 8643 designators and so never matched anything: KC135 should be K35R, RC135 is R135, CH47 is H47, AH64 is H64, MQ9 is Q9, RQ4 is Q4. The list was over-matching civil types and missing real military ones at the same time.

Dropped the dual-use codes: B737/B738/B739, A332, A310, GLF5, GLF6, A124, C40. Military examples of those airframes already carry the dbFlags bit, so the heuristic wasn't buying anything. Concretely: PLF110, a Polish Air Force 737, is still picked up after removing B737 from the list. That's the case the entry was there for.

classifyAircraft()'s type chain is an exact TYPE_ROLES lookup instead of the includes() ladder.

isMilitaryCallsign() now requires a digit after the prefix. RCH471 and PLF110 still match, SAMU42 doesn't. I also dropped the bare 5-6 digit rule, which was a guess and didn't match anything in the sample.

Removed the A00000-AFFFFF + type branch. The unconditional isMilitaryType() check above it already returns for anything that branch could catch, so it was dead.

Result

609 -> 27 on the same feed, 25 of them dbFlags-confirmed. What's left is Spanish A400Ms, a German A321neo, Italian and Belgian air force aircraft, US Army CH-47s and a UH-72, a KC-135R (57-1468), French Sécurité Civile Dash-8s, a Croatian CL-415. No Cessnas or business jets.

No API or response-shape changes, only the classification predicates. next build passes.

One thing I left alone: several entries in MILITARY_CALLSIGN_PREFIXES are broad enough to collide with civil callsigns even with the digit boundary (MAG, KING, GOLD, CLUB, TANGO). Probably worth a separate look.

isMilitaryType() used upper.includes(mt) against a list containing
short designators, so any type merely CONTAINING one matched. Measured
against a live adsb.lol regional feed of 2754 aircraft, this reported
609 military when only 21 carried the database military flag -- 96%
false positives:

  490x B738  ordinary 737-800 airliners
   12x C172  Cessna 172 -> matched 'C17' -> 'Strategic Airlift (C-17)'
   11x C56X  Citation Excel -> matched 'C5' -> 'Strategic Airlift (C-5)'
    6x E35L  Embraer Legacy -> matched 'E3' -> 'AWACS'
    5x C25A  Citation CJ2 -> matched 'C2' -> 'C-2 Greyhound'

(B200 King Air -> 'B2' -> Bomber was reachable by the same rule.)

Changes:
- MILITARY_AIRCRAFT_TYPES is now a Set of exact ICAO Doc 8643
  designators, corrected where the old entries were not real codes
  (KC135 -> K35R, RC135 -> R135, CH47 -> H47, MQ9 -> Q9, ...).
- Dual-use designators dropped: B737/B738/B739, A332, A310, GLF5,
  GLF6, A124, C40. Genuine military examples already carry adsb.lol's
  dbFlags military bit -- verified: Polish Air Force 737 PLF110 is
  still detected after B737 was removed from the heuristic list.
- classifyAircraft() type chain replaced with an exact TYPE_ROLES
  lookup, same substring flaw.
- isMilitaryCallsign() requires a digit after the prefix, so the
  French air ambulances SAMU42/SAMU06 no longer match the US 'SAM'
  Special Air Mission prefix. Dropped the bare 5-6 digit rule.
- Removed an unreachable US-hex+type branch already covered above.

Live result: 609 -> 27 military, 25 of them dbFlags-confirmed.
@ihubanov
ihubanov force-pushed the fix/exact-aircraft-type-matching branch from bdb1dff to 2df4a06 Compare August 17, 2026 18:33
Knibbaz added a commit to Knibbaz/IRONSIGHT that referenced this pull request Aug 30, 2026
A substring match turned a Cessna 172 into a C-17, a Citation C56X into a
C-5 and a King Air B200 into a B-2 (609 contacts -> 27 true military).
Military types are now matched by exact ICAO designator, military
callsigns require a numeric mission number, and aircraft roles come from
an explicit type table. Applies upstream PR NoblerWorks-HQ#32.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant