Switch from volume-only market fetching to tag-based event queries
for better relevance. Variant-aware tags (geopolitical vs tech).
Deduplicates across tag queries, falls back to top-volume markets
only when needed. Add clickable links to Polymarket event pages.
- Add DFMGI.AE for UAE (DFM General Index)
- Use range=1mo instead of 5d to handle Sun-Thu trading weeks
- Take last ~5 trading days from monthly data for weekly % change
- Remove broken Qatar symbol (^QSI returns no data)
- Stock index API endpoint (Yahoo Finance, Redis 1h cache, ~50 countries)
- Stock chip in modal: shows weekly % change with green/red styling
- Pause deck.gl renders while country modal is open (fixes flickering)
- Suppress signal modal clicks while country modal is visible
- Single stock fetch reused for both UI chip and AI brief context
- Military signals now counted by geographic bounding box (lat/lon in country)
instead of operator flag — matches what posture panel shows
- Add country aliases for headline matching (Israel → israeli, gaza, hamas, idf...)
- Feed convergence scores + regional alerts to AI context
- Expanded prompt: 5 sections, 300-400 words, military posture analysis,
regional context, cross-signal correlation
- Bump max_tokens 500→900, cache version ci-v1→ci-v2
- Add 25+ country bounding boxes for geo-matching
Click empty map area to detect country via Nominatim reverse geocoding,
highlight it with GeoJSON boundaries, and show an AI-generated intel
brief (Groq, Redis-cached 2h) with CII score, active signals, and
contextual news headlines.
- Remove CCA from MILITARY_PREFIXES (Air China airline, not military)
- Remove duplicate IAF entry from Middle East section
- Move AIRLINE_CODES to module level Set for O(1) lookup
- Remove duplicate entries (ELY, THY, SWR)
- 20,396 verified military hex IDs from adsbexchange.com
- Updated daily by ADS-B Exchange community
- O(1) lookup using Set
- Combines with callsign detection for comprehensive coverage
Focus on ranges where military/civilian separation is clear:
- US (AE/AF), UK, France, Germany, NATO, Russia, China, Australia, Japan
For other countries, rely on callsign detection since hex ranges
include commercial aircraft mixed with military.
Military aircraft have specific transponder ID ranges by country.
This matches how OpenSky UI identifies military aircraft.
Now checks both callsign patterns AND hex ranges.
Ranges added for: USA, UK, France, Germany, Italy, NATO, Israel,
Saudi, UAE, Qatar, Turkey, Russia, China, Iran, India, Pakistan,
Australia, Japan, South Korea
- Remove UAE from military prefixes (conflicts with Emirates airline)
- Use more specific UAEAF, BAHAF, OMAAF for Gulf military
- Expand airline exclusion list with major carriers worldwide
- This improves accuracy by removing commercial flight false positives
- Add more US military prefixes (ARMY, NAVY, USAF, OPS, etc.)
- Add Middle East military prefixes (RSAF, UAE, EMIRI, etc.)
- Add NATO tactical callsigns (SWORD, LANCE, etc.)
- Add short tactical pattern (3 letters + 1-2 digits) with airline exclusion
- Exclude known airline codes (SVA, QTR, THY, etc.) from short patterns
The patterns [A-Z]{2,3}\d{3,4} and [A-Z]{3,4}\d{2,4} were matching
commercial airlines like PGT5873, IAW9011, IZG3020. New pattern only
matches 4+ letter callsigns like DUKE01, VIPER12 which are typical
military tactical callsigns.
BUG FIX: Wingbits API returns short field names like 'h' for icao24,
'f' for flight, 'la' for latitude, etc. The previous code was looking
for long names like 'icao24', 'callsign', 'latitude' which don't exist
in the response, causing all flights to be filtered out.
- Add /flights and /flights/batch endpoints to wingbits proxy
- Add fetchMilitaryFlightsFromWingbits() to theater-posture API
- Try OpenSky first, fallback to Wingbits on 429/failure
- Transform Wingbits data to match existing flight format
- Add 'source' field to response ('opensky' or 'wingbits')
- Switch Times of Israel from Railway to Vercel RSS proxy (already allowlisted)
- Increase stale cache TTL from 1 hour to 24 hours
- Add 7-day backup cache as last resort during prolonged outages
- Better error handling: try stale → backup → error
- Change default from 'fighter' to 'unknown' for unrecognized callsigns
- Add 'unknown' count to aircraft breakdown
- Show bombers, transport, drones, and 'other' in summary
- Fixes misleading '179 fighters' when most were unclassified
- ACLED changed API endpoint from api.acleddata.com to acleddata.com/api/
- Add Bearer token authentication via ACLED_ACCESS_TOKEN env var
- Add baseline scores fallback when ACLED is unavailable (no more 500s)
- Better error logging for auth failures
- Add stale cache fallback when OpenSky is rate-limited (1h TTL)
- Return cached data instead of 500 error when API fails
- Improve empty/error state messages with context
- Add retry buttons to no-data states
- Show stale data warning when using cached fallback
- Korean Peninsula (covers Korean DMZ area)
- South China Sea (disputed waters)
- Eastern Mediterranean (Israel/Syria/Lebanon)
Bump cache key to v2 to invalidate old 4-theater cache.
- Add missing byOperator to API response (was causing crash)
- Deep clone postures to prevent cached data mutation
- Add drone detection patterns (RQ/MQ/REAPER/PREDATOR)
- Separate drone from reconnaissance in callsign detection
- Add vessel fields to TheaterPostureSummary type (destroyers, frigates,
carriers, submarines, patrol, auxiliaryVessels, totalVessels)
- Update API to return theater bounds for client-side vessel matching
- Panel fetches vessels client-side (AIS WebSocket) and merges with
server-side aircraft data (OpenSky API)
- Display shows AIR and NAVAL sections when vessels detected
- Add posture-section-label CSS for section headers
- Create /api/theater-posture.js endpoint that fetches military flights
from OpenSky, calculates theater postures, and caches in Upstash Redis
- Add cached-theater-posture.ts client service with deduplication
- Update StrategicPosturePanel to fetch from cached endpoint independently
- Update App.ts to use cached postures for critical alert banner
Benefits: Theater posture calculation shared across all users via Redis
cache (5-min TTL), panel works without military layer enabled
- Remove Trump mention from date context for tech variant
- Add variant-aware system prompts:
- Tech: "Focus ONLY on technology, startups, AI, funding..."
- Tech: "IGNORE political news, trade policy, tariffs..."
- Use tech-appropriate examples: "OpenAI announced...", "Series B..."
- Bump cache version to v3 to force new summaries
The Redis cache key for summaries was built from headlines + mode +
geoContext but did NOT include the site variant (full vs tech). This
caused cross-site cache collisions where a summary generated for the
full variant could be returned to tech variant users (and vice versa).
Changes:
- Pass SITE_VARIANT from frontend to summarization API
- Include variant in cache key: `summary:{variant}:{hash}{geoHash}`
- Updated both groq-summarize and openrouter-summarize endpoints
Now cache keys are scoped per variant, preventing incorrect summaries.
The dev.events RSS feed is limited to 100 items sorted by "date added"
(not event date), causing major events like STEP Dubai to be pushed out
when newer events are added. Added a curated events list as fallback
for important conferences that may fall off the RSS feed:
- STEP Dubai 2026 (Feb 11-12) - 8,000+ attendees, AI economy focus
- GITEX Global 2026 (Dec 7-11) - World's largest tech show
- TOKEN2049 Dubai 2026 (Apr 29-30)
- Collision 2026 (Jun 22-25) - Toronto
- Web Summit 2026 (Nov 2-5) - Lisbon
Curated events are deduplicated with feed data to avoid duplicates.
LLMs have outdated knowledge (pre-Jan 2025), causing errors like
"former President Trump" when Trump is current president.
Added dateContext to all summarization prompts with:
- Current date
- Trump is current president (second term, Jan 2025)
Problem: Every panel summary started with 'Breaking news tonight:' because
the prompt said 'write like a news anchor opening the evening news'.
Fix: Rewrote prompts to:
- Explicitly forbid 'Breaking news', 'Good evening', 'Tonight' openings
- Remove TV news anchor framing entirely
- Instruct to start directly with subject: 'Iran's regime...', 'The US Treasury...'
- Focus on substance over style
Updated both groq-summarize.js and openrouter-summarize.js
Correlates news entities with map signals to identify "main characters"
appearing across multiple intelligence streams:
- Extract entities from all 80+ news sources via NER
- Cross-reference with map signals (flights, vessels, outages, protests)
- Score focal points where news + signals converge
- Generate rich AI context for correlation-aware summarization
New files:
- focal-point-detector.ts: Core detection and scoring service
- signal-aggregator.ts: Geographic signal aggregation
UI shows top focal points with urgency badges and signal icons.
AI prompts updated to leverage intelligence synthesis context.
- Rewrite prompts to vary output (no more "The dominant narrative...")
- Instruct model to lead with substance: location, action, impact
- Add explicit rule: NEVER start with meta-commentary
- New /api/risk-scores endpoint computes and caches scores in Redis
- Uses ACLED protest data + baseline geopolitical risk factors
- 10-minute cache TTL shared across all users
- CIIPanel and StrategicRiskPanel fetch cached scores on load
- Learning Mode banner hidden when cached scores available
- Eliminates 15-minute warmup for users
- Double MAX_RESULTS from 12 to 24
- Prioritize news over static infrastructure in search
- Update News24 URL to post-redirect destination (feeds.capi24.com)
- Add trailing slash to SCMP URL
- Add feeds.capi24.com to both proxy allowlists