Three stacked filters meant the gps_jamming layer almost never lit up:
1. nac_p == 0 aircraft were dropped on the theory that "0 = old transponder."
That's only half right — modern Mode-S Enhanced Surveillance transponders
also fall back to nac_p=0 when they lose GPS lock entirely, which IS the
jamming signature we want to catch. Discarding them was discarding the
strongest signal. None (no field at all — typical for OpenSky-sourced
records) is still skipped because absence-of-data isn't evidence.
2. GPS_JAMMING_MIN_AIRCRAFT was 5 per 1°x1° cell. Jamming hotspots
(eastern Med, Russia/Ukraine border, Iran/Iraq) tend to have sparser
traffic because pilots avoid them. Lowered to 3.
3. GPS_JAMMING_MIN_RATIO was 0.30. Combined with the (preserved) -1 noise
cushion that made the effective bar high. Lowered to 0.20.
The 1-aircraft noise cushion is intact so a single quirky transponder
still can't flag a zone alone.
Also extracted the detector loop into a pure ``detect_gps_jamming_zones()``
function at module scope so it's testable in isolation (was previously
inlined inside ``_classify_and_publish``). The public signature accepts
threshold overrides for ad-hoc re-tuning without code edits.
16 new tests cover nac_p=0 inclusion, None-skip preservation, MIN_AIRCRAFT
lowering, MIN_RATIO lowering, noise cushion preservation, constant pinning,
override behavior, lon/lng key compatibility, and robustness to empty/None
inputs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>