From 0fdd0814d71356f34e9dd91f0b6152932921b3dc Mon Sep 17 00:00:00 2001 From: Marc Shade Date: Mon, 23 Feb 2026 20:18:09 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20wire=20Phase=207=20feeds=20to=20dashboa?= =?UTF-8?q?rd=20=E2=80=94=207=20new=20visualizations=20+=20nuclear=20map?= =?UTF-8?q?=20layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: add 7 new data feeds to _fetch_overview() (33 total SSE streams). Frontend: alert banner (critical/high/medium), health outbreak tracker, election calendar with risk bars, shipping stress gauge, social signals from Reddit, nuclear test site map layer with concern scoring, weekly trends with anomaly tracking. New HUD pills for alerts, shipping stress, outbreaks, nuclear flags. Co-Authored-By: Claude Opus 4.6 --- src/world_intel_mcp/dashboard/app.py | 13 + src/world_intel_mcp/dashboard/index.html | 319 ++++++++++++++++++++++- 2 files changed, 329 insertions(+), 3 deletions(-) diff --git a/src/world_intel_mcp/dashboard/app.py b/src/world_intel_mcp/dashboard/app.py index 6b1412d..06f703c 100644 --- a/src/world_intel_mcp/dashboard/app.py +++ b/src/world_intel_mcp/dashboard/app.py @@ -35,7 +35,13 @@ from world_intel_mcp.sources import ( intelligence, space_weather, ai_watch, + health, + elections, + shipping, + social, + nuclear, ) +from world_intel_mcp.analysis.alerts import fetch_alert_digest, fetch_weekly_trends logger = logging.getLogger(__name__) @@ -91,6 +97,13 @@ async def _fetch_overview() -> dict: "signal_convergence": intelligence.fetch_signal_convergence(fetcher), "space_weather": space_weather.fetch_space_weather(fetcher), "ai_watch": ai_watch.fetch_ai_watch(fetcher), + "disease_outbreaks": health.fetch_disease_outbreaks(fetcher), + "election_calendar": elections.fetch_election_calendar(fetcher), + "shipping_index": shipping.fetch_shipping_index(fetcher), + "social_signals": social.fetch_social_signals(fetcher), + "nuclear_monitor": nuclear.fetch_nuclear_monitor(fetcher), + "alert_digest": fetch_alert_digest(fetcher), + "weekly_trends": fetch_weekly_trends(fetcher), } gathered = await asyncio.gather( diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html index 90d29cf..f13d7b8 100644 --- a/src/world_intel_mcp/dashboard/index.html +++ b/src/world_intel_mcp/dashboard/index.html @@ -274,6 +274,43 @@ a { color: var(--accent); text-decoration: none; } .ticker-item .src { color: var(--dim); margin-left: 6px; } .ticker-item .sep { color: rgba(0,229,255,0.2); margin: 0 4px; } +/* ═══════════════ ALERT BANNER ═══════════════ */ +#alertBanner { + position: fixed; top: 54px; left: 10px; right: 360px; z-index: 45; + max-height: 0; overflow: hidden; transition: max-height 0.4s var(--ease); +} +#alertBanner.show { max-height: 120px; } +#alertBanner.show + #layers { top: 108px; } +.alert-strip { + display: flex; align-items: center; gap: 10px; + padding: 6px 14px; border-radius: 8px; margin-bottom: 4px; + font-family: var(--mono); font-size: 0.68rem; + background: rgba(255,59,59,0.06); border: 1px solid rgba(255,59,59,0.12); +} +.alert-strip.critical { background: rgba(255,59,59,0.1); border-color: rgba(255,59,59,0.2); } +.alert-strip.high { background: rgba(255,159,28,0.08); border-color: rgba(255,159,28,0.15); } +.alert-strip.medium { background: rgba(77,168,255,0.06); border-color: rgba(77,168,255,0.1); } +.alert-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; animation: pulse-dot 2s ease-in-out infinite; } +.alert-strip.critical .alert-dot { background: var(--red); box-shadow: 0 0 8px var(--red); } +.alert-strip.high .alert-dot { background: var(--amber); box-shadow: 0 0 6px var(--amber); } +.alert-strip.medium .alert-dot { background: var(--blue); box-shadow: 0 0 4px var(--blue); } +.alert-msg { flex: 1; color: var(--bright); } +.alert-domain { color: var(--dim); font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.5px; flex-shrink: 0; } + +/* ═══════════════ RISK BAR ═══════════════ */ +.risk-bar { position: relative; height: 6px; background: rgba(255,255,255,0.04); border-radius: 3px; margin: 2px 0; overflow: hidden; } +.risk-fill { height: 100%; border-radius: 3px; transition: width 0.5s var(--ease); } +.risk-fill.crit { background: linear-gradient(90deg, var(--red), #ff6666); } +.risk-fill.high { background: linear-gradient(90deg, var(--amber), #ffcc66); } +.risk-fill.med { background: linear-gradient(90deg, var(--blue), #80c4ff); } +.risk-fill.low { background: linear-gradient(90deg, var(--green), #66e6b3); } + +/* ═══════════════ GAUGE ═══════════════ */ +.gauge-row { display: flex; align-items: center; gap: 8px; margin: 6px 0 10px; } +.gauge-track { flex: 1; height: 8px; background: rgba(255,255,255,0.04); border-radius: 4px; overflow: hidden; } +.gauge-fill { height: 100%; border-radius: 4px; transition: width 0.6s var(--ease), background 0.3s; } +.gauge-label { font-family: var(--mono); font-size: 0.75rem; font-weight: 600; min-width: 32px; text-align: right; } + /* ═══════════════ MAP MARKERS ═══════════════ */ .mk-quake { border-radius: 50%; @@ -315,6 +352,19 @@ a { color: var(--accent); text-decoration: none; } cursor: pointer; transition: border-color 0.2s, background 0.2s; } .mk-conv:hover { border-color: rgba(167,139,250,0.6); background: rgba(167,139,250,0.1); } +.mk-nuclear { + border-radius: 50%; + background: radial-gradient(circle, rgba(52,211,153,0.8) 15%, rgba(52,211,153,0) 70%); + border: 2px solid rgba(52,211,153,0.5); + cursor: pointer; transition: transform 0.15s; + box-shadow: 0 0 6px rgba(52,211,153,0.3); +} +.mk-nuclear:hover { transform: scale(1.3); } +.mk-nuclear.crit { animation: glow-green 1.8s ease-in-out infinite; } +@keyframes glow-green { + 0%,100% { box-shadow: 0 0 4px rgba(52,211,153,0.3); } + 50% { box-shadow: 0 0 22px rgba(52,211,153,0.8); } +} /* Leaflet tooltip override */ .leaflet-tooltip.mk-tip { @@ -370,6 +420,9 @@ a { color: var(--accent); text-decoration: none; } + +
+ @@ -449,7 +508,7 @@ function ago(ts) { if (!ts) return '\u2014'; var s = Math.floor((Date.now() - ne var latestData = null; var map = null; var mapLayers = {}; -var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true }; +var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true }; var drawerOpen = false; // ════════════ MAP INIT ════════════ @@ -462,6 +521,7 @@ function initMap() { mapLayers.conflict = L.layerGroup().addTo(map); mapLayers.fires = L.layerGroup().addTo(map); mapLayers.convergence = L.layerGroup().addTo(map); + mapLayers.nuclear = L.layerGroup().addTo(map); } // ════════════ LAYER TOGGLES ════════════ @@ -486,8 +546,8 @@ $('#drawerToggle').addEventListener('click', function() { }); // ════════════ DETAIL MODAL ════════════ -var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)' }; -var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE' }; +var COLORS = { earthquake: 'var(--red)', military: 'var(--blue)', conflict: 'var(--amber)', fire: 'var(--gold)', convergence: 'var(--purple)', nuclear: 'var(--green)' }; +var LABELS = { earthquake: 'EARTHQUAKE', military: 'MILITARY AIRCRAFT', conflict: 'CONFLICT EVENT', fire: 'WILDFIRE CLUSTER', convergence: 'SIGNAL CONVERGENCE', nuclear: 'NUCLEAR MONITOR' }; function showDetail(type, d) { var title = '', subtitle = '', fields = [], link = ''; @@ -560,6 +620,20 @@ function showDetail(type, d) { } } if (d.lat != null) fields.push(['Coordinates', d.lat.toFixed(3) + ', ' + d.lon.toFixed(3)]); + } else if (type === 'nuclear') { + var cs = d.concern_score || 0; + title = (d.site || 'Unknown Site') + ' \u2014 M' + (d.magnitude || 0).toFixed(1); + subtitle = d.site_country || ''; + fields = [ + ['Concern Score', cs.toFixed(1), cs >= 70 ? 'crit' : cs >= 50 ? 'high' : cs >= 30 ? 'high' : ''], + ['Concern Level', d.concern_level || '\u2014', d.concern_level === 'critical' ? 'crit' : d.concern_level === 'high' ? 'high' : ''], + ['Magnitude', (d.magnitude || 0).toFixed(1)], + ['Depth', d.depth_km != null ? d.depth_km + ' km' : '\u2014', d.depth_km <= 5 ? 'high' : ''], + ['Distance to Site', d.distance_km != null ? d.distance_km + ' km' : '\u2014'], + ['Location', d.place || '\u2014'], + ['Time', d.time || '\u2014'], + ['Coordinates', d.latitude != null ? d.latitude.toFixed(3) + ', ' + d.longitude.toFixed(3) : '\u2014'] + ]; } var hdr = '
' + esc(LABELS[type] || type) + '
' + @@ -679,6 +753,52 @@ function updateMapConvergence(data) { }); } +function updateMapNuclear(data) { + mapLayers.nuclear.clearLayers(); + if (!data || data.error) { $('#cntNuclear').textContent = '0'; return; } + var sites = data.sites || []; + var flagged = 0; + sites.forEach(function(site) { + if (site.lat == null || site.lon == null) return; + var evts = site.events_detected || 0; + flagged += evts; + var highest = site.highest_concern; + var isCrit = highest && highest.concern_level === 'critical'; + var size = evts > 0 ? Math.max(14, Math.min(24, evts * 3 + 10)) : 10; + var mk = L.marker([site.lat, site.lon], { + icon: L.divIcon({ className: 'mk-nuclear' + (isCrit ? ' crit' : ''), iconSize: [size, size], iconAnchor: [size/2, size/2] }) + }); + var tip = esc(site.name) + ' (' + esc(site.status) + ')'; + if (evts > 0) tip += ' \u2014 ' + evts + ' events'; + mk.bindTooltip(tip, { className: 'mk-tip', direction: 'top', offset: [0, -size/2] }); + mk.on('click', function() { + if (highest) { showDetail('nuclear', highest); } + }); + mk.addTo(mapLayers.nuclear); + }); + $('#cntNuclear').textContent = flagged; +} + +function updateAlertBanner(data) { + var el = $('#alertBanner'); + if (!data || data.error || !data.alerts || !data.alerts.length) { + el.classList.remove('show'); + el.textContent = ''; + return; + } + var alerts = data.alerts.slice(0, 3); + var html = alerts.map(function(a) { + var p = a.priority || 'medium'; + return '
' + + '' + + '' + esc(a.message || '') + '' + + '' + esc(a.domain || '') + '' + + '
'; + }).join(''); + el.innerHTML = safe(html); + el.classList.add('show'); +} + // ════════════ TOP BAR STATS ════════════ function updateHudStats(data) { @@ -711,6 +831,22 @@ function updateHudStats(data) { if (data.ai_watch && !data.ai_watch.error) { pills.push('
' + (data.ai_watch.count || 0) + 'AI Papers
'); } + if (data.alert_digest && !data.alert_digest.error && data.alert_digest.alert_count > 0) { + var ac = data.alert_digest.alert_count; + var hasCrit = (data.alert_digest.by_priority || {}).critical > 0; + pills.push('
' + ac + 'Alerts
'); + } + if (data.shipping_index && !data.shipping_index.error) { + var ss = data.shipping_index.stress_score || 0; + pills.push('
' + ss + 'Ship Stress
'); + } + if (data.disease_outbreaks && !data.disease_outbreaks.error) { + var hc = data.disease_outbreaks.high_concern_count || 0; + if (hc > 0) pills.push('
' + hc + 'Outbreak
'); + } + if (data.nuclear_monitor && !data.nuclear_monitor.error && data.nuclear_monitor.critical_flags > 0) { + pills.push('
' + data.nuclear_monitor.critical_flags + 'Nuke Flag
'); + } $('#hudStats').innerHTML = safe(pills.join('')); } @@ -719,6 +855,26 @@ function updateHudStats(data) { function updateDrawer(data) { var h = ''; + // ── ALERTS ── + if (data.alert_digest && !data.alert_digest.error && data.alert_digest.alert_count > 0) { + h += '
ALERTS
'; + var byPri = data.alert_digest.by_priority || {}; + h += '
'; + if (byPri.critical) h += '
' + byPri.critical + '
Critical
'; + if (byPri.high) h += '
' + byPri.high + '
High
'; + if (byPri.medium) h += '
' + byPri.medium + '
Medium
'; + h += '
'; + var alertItems = data.alert_digest.alerts || []; + alertItems.forEach(function(a) { + var pc = a.priority === 'critical' ? 'down' : a.priority === 'high' ? 'warn' : 'dim'; + h += '
'; + h += ''; + h += '' + esc(a.message || '') + ''; + h += '' + esc(a.domain || '') + ''; + h += '
'; + }); + } + // ── MARKETS ── h += '
MARKETS
'; if (data.market_quotes && !data.market_quotes.error) { @@ -1003,6 +1159,159 @@ function updateDrawer(data) { h += '
Loading AI feeds...
'; } + // ── HEALTH ── + if (data.disease_outbreaks && !data.disease_outbreaks.error) { + var dob = data.disease_outbreaks; + h += '
HEALTH
'; + h += '
'; + h += '
' + (dob.count || 0) + '
Alerts
'; + h += '
' + (dob.high_concern_count || 0) + '
High Concern
'; + h += '
'; + var byOrg = dob.by_organization || {}; + if (Object.keys(byOrg).length) { + h += '
'; + for (var orgK in byOrg) { + if (byOrg.hasOwnProperty(orgK)) { + h += '
' + byOrg[orgK] + '
' + esc(orgK) + '
'; + } + } + h += '
'; + } + var healthItems = dob.items || []; + if (healthItems.length) { + h += ''; + healthItems.slice(0, 12).forEach(function(item) { + var isHigh = item.high_concern; + h += '' + esc(trunc(item.title || '?', 40)) + ''; + }); + h += '
OutbreakOrgAge
' + esc(item.organization || '\u2014') + '' + ago(item.published) + '
'; + } + } + + // ── ELECTIONS ── + if (data.election_calendar && !data.election_calendar.error) { + var ecal = data.election_calendar; + var elections = ecal.elections || []; + if (elections.length) { + h += '
ELECTIONS
'; + var hiRisk = ecal.highest_risk; + if (hiRisk) { + h += '
' + fmtNum(hiRisk.risk_score || 0, 0) + '
Top Risk
'; + h += '
' + esc(hiRisk.country || '?') + '
' + esc(hiRisk.election_type || '') + '
'; + } + h += ''; + elections.slice(0, 12).forEach(function(e) { + var rs = e.risk_score || 0; + var rCls = rs >= 80 ? 'crit' : rs >= 50 ? 'high' : rs >= 30 ? 'high' : ''; + var riskPct = Math.min(100, rs); + h += ''; + }); + h += '
CountryTypeDateRisk
' + esc(e.country || '?') + '' + esc(e.election_type || '\u2014') + '' + esc(e.date || '\u2014') + '' + + '
' + + '
'; + } + } + + // ── SHIPPING ── + if (data.shipping_index && !data.shipping_index.error) { + var ship = data.shipping_index; + h += '
SHIPPING
'; + var stress = ship.stress_score || 0; + var stressColor = stress >= 60 ? 'var(--red)' : stress >= 30 ? 'var(--amber)' : 'var(--green)'; + h += '
'; + h += 'Stress'; + h += '
'; + h += '' + stress + ''; + h += '
'; + h += '
' + esc(ship.assessment || '\u2014') + '
Assessment
'; + var sigs = ship.signals || []; + if (sigs.length) { + h += '
'; + sigs.forEach(function(s) { h += '' + esc(s) + ''; }); + h += '
'; + } + var shipQuotes = ship.quotes || []; + if (shipQuotes.length) { + h += ''; + shipQuotes.forEach(function(q) { + h += ''; + }); + h += '
SymPrice%
' + esc(q.symbol || '?') + '' + fmtNum(q.price) + '' + fmtPct(q.change_pct) + '%
'; + } + } + + // ── SOCIAL ── + if (data.social_signals && !data.social_signals.error) { + var soc = data.social_signals; + h += '
SOCIAL
'; + var vel = soc.velocity_metrics || {}; + if (Object.keys(vel).length) { + h += '
'; + for (var vk in vel) { + if (vel.hasOwnProperty(vk)) { + h += '
' + (typeof vel[vk] === 'number' ? fmtNum(vel[vk], 0) : esc(String(vel[vk]))) + '
' + esc(vk.replace(/_/g, ' ')) + '
'; + } + } + h += '
'; + } + var posts = soc.posts || []; + if (posts.length) { + h += ''; + posts.slice(0, 10).forEach(function(p) { + h += ''; + }); + h += '
PostScoreSub
' + esc(trunc(p.title || '?', 35)) + '' + fmtBigPlain(p.score || 0) + '' + esc(p.subreddit || '\u2014') + '
'; + } + } + + // ── NUCLEAR ── + if (data.nuclear_monitor && !data.nuclear_monitor.error) { + var nuc = data.nuclear_monitor; + h += '
NUCLEAR MONITOR
'; + h += '
'; + h += '
' + (nuc.total_flagged_events || 0) + '
Flagged
'; + h += '
' + (nuc.critical_flags || 0) + '
Critical
'; + h += '
' + ((nuc.sites || []).length) + '
Sites
'; + h += '
'; + var nucSites = nuc.sites || []; + if (nucSites.length) { + h += ''; + nucSites.forEach(function(s) { + var concern = s.highest_concern ? s.highest_concern.concern_level : 'none'; + var cc = concern === 'critical' ? 'down' : concern === 'high' ? 'warn' : concern === 'elevated' ? 'warn' : 'dim'; + h += ''; + }); + h += '
SiteStatusEventsConcern
' + esc(s.name || '?') + '' + esc(s.status || '\u2014') + '' + (s.events_detected || 0) + '' + esc(concern) + '
'; + } + } + + // ── TRENDS ── + if (data.weekly_trends && !data.weekly_trends.error) { + var wt = data.weekly_trends; + h += '
TRENDS
'; + h += '
'; + h += '
' + (wt.trend_count || 0) + '
Metrics
'; + h += '
' + (wt.current_anomaly_count || 0) + '
Anomalies
'; + h += '
'; + var anomalies = wt.current_anomalies || []; + if (anomalies.length) { + h += '
Active Anomalies
'; + anomalies.slice(0, 10).forEach(function(a) { + var dev = a.z_score || a.deviation || 0; + h += ''; + }); + h += '
MetricRegionDev
' + esc(a.event_type || a.metric || '?') + '' + esc(a.region || '\u2014') + '' + (dev > 0 ? '+' : '') + fmtNum(dev, 1) + '\u03C3
'; + } + var trends = wt.trends || []; + if (trends.length) { + h += '
Most Volatile
'; + trends.slice(0, 8).forEach(function(t) { + h += ''; + }); + h += '
MetricCV%Mean
' + esc(t.metric || '?') + '' + fmtNum(t.volatility_cv, 1) + '' + fmtNum(t.mean, 1) + '
'; + } + } + $('#drawerBody').innerHTML = safe(h); } @@ -1035,6 +1344,10 @@ function updateAll(data) { updateMapConflict(data.acled_events); updateMapFires(data.wildfires); updateMapConvergence(data.signal_convergence); + updateMapNuclear(data.nuclear_monitor); + + // Alert banner + updateAlertBanner(data.alert_digest); // HUD updateHudStats(data);