* fix(seeds): extend TTL on stale data instead of crashing on fetch errors
Seed scripts crashed with process.exit(1) when upstream APIs returned
errors (e.g., Wingbits 401), causing Redis keys to expire and panels
to lose data. Now all seeds gracefully extend TTL on existing keys and
exit 0, keeping stale data alive until the API recovers.
- Add shared extendExistingTtl() helper to _seed-utils.mjs
- Update runSeed() catch block (fixes 24 scripts using it)
- Fix fetch-gpsjam.mjs, seed-airport-delays.mjs,
seed-military-flights.mjs, seed-service-statuses.mjs
* fix(seeds): preserve per-key TTLs when extending stale military data
THEATER_POSTURE_BACKUP_KEY has a 7-day TTL (604800s) but was being
extended with STALE_TTL (86400s), shortening it from 7 days to 1 day
during upstream outages. Now each key group gets its original TTL.
* feat(gpsjam): migrate GPS jamming from gpsjam.org to Wingbits API
Replace gpsjam.org CSV scraping with Wingbits customer API for GPS/GNSS
interference data. This is a proper API with structured JSON responses
instead of fragile web scraping.
Key changes:
- Rewrite fetch-gpsjam.mjs seeder for Wingbits API (x-api-key auth)
- Delete ~150-line gpsjam seed loop from ais-relay.cjs (now standalone cron)
- Simplify api/gpsjam.js to Redis-only reads with v1→v2 fallback
- Update data shape: pct/good/bad/total → npAvg/sampleCount/aircraftCount
- Redis key: intelligence:gpsjam:v1 → v2 (with dual-write transition)
- Add vite dev plugin for local development
- Update all frontend components (MapPopup, DeckGLMap, GlobeMap, locales)
Zero-downtime: seeder dual-writes both v1 and v2 keys, edge handler
falls back to v1 with shape normalization. Remove v1 code after 24-72h.
* fix(gpsjam): improve v1 fallback normalization and update all locale files
- v1 fallback now derives npAvg from severity thresholds (high: 0.3,
medium: 0.8) instead of hardcoding 0, uses bad/total for counts
- Update all 20 non-English locale files to use new gpsJamming keys
(navPerformance, samples, aircraft) with English fallback values
* fix(gpsjam): dual-write v1 in old schema shape and catch Redis errors
- Seeder now converts v2 data back to v1 shape (pct/good/bad/total)
for the dual-write, so old deployments and rollbacks parse correctly
- Edge handler wraps readFromRedis calls in try-catch so network/timeout
errors return graceful 503 instead of platform 500s
* feat(seeds): add BIS data seed job and relax health thresholds
Add seed-bis-data.mjs that fetches all 3 BIS datasets (policy rates,
exchange rates, credit-to-GDP) in parallel and writes to Redis. This
keeps the cache warm instead of relying on on-demand RPC calls.
Relax BIS health thresholds from 1440min (24h) to 2880min (48h) since
BIS data is monthly/quarterly — 24h was too aggressive.
* fix(health): relax minerals and giving thresholds to 7 days
Both are static/hardcoded data with no external API calls.
2880min (48h) was too aggressive for annual data.
* fix(gpsjam): write seed-meta for health freshness tracking
The fetch-gpsjam script seeded Redis data but never wrote
seed-meta:intelligence:gpsjam, causing health to report STALE_SEED.
* feat: add GPS/GNSS jamming data ingestion from gpsjam.org
- scripts/fetch-gpsjam.mjs: standalone fetcher that downloads daily H3
hex data, filters medium/high interference, converts to lat/lon via
h3-js, and writes JSON. Can be run on cron.
- api/gpsjam.js: Vercel Edge Function that proxies gpsjam.org data with
1hr cache, returns medium/high hexes for frontend consumption.
- src/services/gps-interference.ts: frontend service that fetches from
the Edge API, converts H3→lat/lon, and classifies by conflict region.
- h3-js added as dependency for hex→coordinate conversion.
* feat: add GPS jamming map layer, CII integration, and country brief signals
Wire gpsjam.org data into map visualization, instability scoring, and
country intelligence. ScatterplotLayer renders high (red) and medium
(orange) interference hexes. CII security score incorporates jamming
counts per country via h3→country geocoding with cache. Country briefs
show jamming zone chip. Full i18n across 18 locales including popup
labels. Data loads with intelligence signals cycle (15min), gated by
1hr client-side cache.