diff --git a/src/world_intel_mcp/analysis/exposure.py b/src/world_intel_mcp/analysis/exposure.py
index 060afe3..0871426 100644
--- a/src/world_intel_mcp/analysis/exposure.py
+++ b/src/world_intel_mcp/analysis/exposure.py
@@ -54,6 +54,8 @@ def _find_exposed_cities(
exposed[cname] = {
"city": cname,
"country": city["country"],
+ "lat": city["lat"],
+ "lon": city["lon"],
"population": city["pop"],
"distance_km": round(dist, 1),
"nearest_event": event.get("type", "unknown"),
diff --git a/src/world_intel_mcp/dashboard/app.py b/src/world_intel_mcp/dashboard/app.py
index cca2db0..e656ffe 100644
--- a/src/world_intel_mcp/dashboard/app.py
+++ b/src/world_intel_mcp/dashboard/app.py
@@ -43,6 +43,9 @@ from world_intel_mcp.sources import (
service_status,
)
from world_intel_mcp.analysis.alerts import fetch_alert_digest, fetch_weekly_trends
+from world_intel_mcp.analysis.posture import fetch_strategic_posture
+from world_intel_mcp.analysis.exposure import fetch_population_exposure
+from world_intel_mcp.sources.fleet import fetch_fleet_report
from world_intel_mcp.config.countries import INTEL_HOTSPOTS
from world_intel_mcp.config.geospatial import MILITARY_BASES, STRATEGIC_PORTS, PIPELINES, NUCLEAR_FACILITIES
@@ -108,6 +111,9 @@ async def _fetch_overview() -> dict:
"alert_digest": fetch_alert_digest(fetcher),
"weekly_trends": fetch_weekly_trends(fetcher),
"service_status": service_status.fetch_service_status(fetcher),
+ "strategic_posture": fetch_strategic_posture(fetcher),
+ "fleet_report": fetch_fleet_report(fetcher),
+ "population_exposure": fetch_population_exposure(fetcher),
}
gathered = await asyncio.gather(
diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html
index 59da5eb..8c980b6 100644
--- a/src/world_intel_mcp/dashboard/index.html
+++ b/src/world_intel_mcp/dashboard/index.html
@@ -503,6 +503,12 @@ a { color: var(--accent); text-decoration: none; }
0
+
@@ -549,7 +555,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, nuclear: true, infra: true };
+var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true, infra: true, exposure: true };
var drawerOpen = false;
// ════════════ MAP INIT ════════════
@@ -564,6 +570,7 @@ function initMap() {
mapLayers.convergence = L.layerGroup().addTo(map);
mapLayers.nuclear = L.layerGroup().addTo(map);
mapLayers.infra = L.layerGroup().addTo(map);
+ mapLayers.exposure = L.layerGroup().addTo(map);
}
// ════════════ LAYER TOGGLES ════════════
@@ -1116,6 +1123,38 @@ function updateMapInfra(data) {
$('#cntInfra').textContent = total;
}
+function updateMapExposure(data) {
+ mapLayers.exposure.clearLayers();
+ if (!data || data.error) { $('#cntExposure').textContent = '0'; return; }
+ var cities = data.exposed_cities || [];
+ var evtColors = { conflict: '#ff1744', earthquake: '#ff9100', wildfire: '#ffea00' };
+ $('#cntExposure').textContent = cities.length;
+ cities.forEach(function(c) {
+ if (c.lat == null || c.lon == null) return;
+ var color = evtColors[c.nearest_event] || '#e040fb';
+ var pop = c.population || 0;
+ var radius = Math.max(5, Math.min(22, Math.sqrt(pop / 400000)));
+ var circle = L.circleMarker([c.lat, c.lon], {
+ radius: radius, color: color, fillColor: color, fillOpacity: 0.3,
+ weight: 2, opacity: 0.7
+ });
+ var popStr = pop >= 1000000 ? (pop / 1000000).toFixed(1) + 'M' : (pop / 1000).toFixed(0) + 'K';
+ circle.bindTooltip(
+ '' + esc(c.city) + ' (' + popStr + ')
' +
+ '' + esc(c.nearest_event) + ' ' + c.distance_km + 'km',
+ { className: 'mk-tip', direction: 'top' }
+ );
+ circle.on('click', function() {
+ showDetail('exposure', {
+ city: c.city, country: c.country, population: popStr,
+ nearest_event: c.nearest_event, event_detail: c.event_detail,
+ distance_km: c.distance_km
+ });
+ });
+ circle.addTo(mapLayers.exposure);
+ });
+}
+
function updateAlertBanner(data) {
var el = $('#alertBanner');
if (!data || data.error || !data.alerts || !data.alerts.length) {
@@ -1187,6 +1226,14 @@ function updateHudStats(data) {
if (data.nuclear_monitor && !data.nuclear_monitor.error && data.nuclear_monitor.critical_flags > 0) {
pills.push('' + data.nuclear_monitor.critical_flags + 'Nuke Flag
');
}
+ if (data.strategic_posture && !data.strategic_posture.error) {
+ var ps = data.strategic_posture.composite_score || 0;
+ var psCls = ps >= 55 ? ' crit' : ps >= 35 ? ' warn' : '';
+ pills.push('' + ps.toFixed(0) + 'Posture
');
+ }
+ if (data.population_exposure && !data.population_exposure.error && data.population_exposure.exposed_city_count > 0) {
+ pills.push('' + (data.population_exposure.total_exposed_population_formatted || '0') + 'Exposed
');
+ }
$('#hudStats').innerHTML = safe(pills.join(''));
}
@@ -1197,6 +1244,44 @@ function updateDrawer(data) {
_drawerIdx = 0;
var h = '';
+ // ── STRATEGIC POSTURE ──
+ if (data.strategic_posture && !data.strategic_posture.error) {
+ var sp = data.strategic_posture;
+ var compScore = sp.composite_score || 0;
+ var riskLvl = sp.risk_level || 'UNKNOWN';
+ var riskColor = riskLvl === 'CRITICAL' ? 'var(--red)' : riskLvl === 'HIGH' ? 'var(--amber)' : riskLvl === 'ELEVATED' ? '#ff9100' : 'var(--green)';
+ h += 'STRATEGIC POSTURE
';
+ h += '';
+ h += '
' + compScore.toFixed(0) + '
';
+ h += '
' + esc(riskLvl) + '
';
+ h += '
' + (sp.domains_assessed || 0) + ' domains assessed
';
+ h += '
';
+ var ds = sp.domain_scores || {};
+ h += '';
+ for (var dk in ds) {
+ if (ds.hasOwnProperty(dk)) {
+ var dInfo = ds[dk];
+ var dScore = dInfo.score || 0;
+ var dColor = dScore >= 55 ? 'var(--red)' : dScore >= 35 ? 'var(--amber)' : 'var(--green)';
+ h += '
';
+ h += '
' + dScore.toFixed(0) + '
';
+ h += '
' + esc(dk) + '
';
+ h += '
';
+ }
+ }
+ h += '
';
+ var threats = sp.top_threats || [];
+ if (threats.length) {
+ h += 'Top Threats
';
+ threats.slice(0, 5).forEach(function(t) {
+ h += '';
+ h += '' + esc(t.domain) + '';
+ h += '' + esc(t.signal) + '';
+ h += '
';
+ });
+ }
+ }
+
// ── ALERTS ──
if (data.alert_digest && !data.alert_digest.error && data.alert_digest.alert_count > 0) {
h += 'ALERTS
';
@@ -1615,6 +1700,29 @@ function updateDrawer(data) {
}
}
+ // ── FLEET ──
+ if (data.fleet_report && !data.fleet_report.error) {
+ var fr = data.fleet_report;
+ h += 'FLEET
';
+ var fScore = fr.readiness_score || 0;
+ var fColor = fScore >= 70 ? 'var(--red)' : fScore >= 40 ? 'var(--amber)' : 'var(--green)';
+ h += '';
+ h += '
';
+ h += '
' + esc(fr.readiness_level || '\u2014') + '
Level
';
+ h += '
' + (fr.total_tracked_aircraft || 0) + '
Aircraft
';
+ h += '
' + (fr.surge_count || 0) + '
Surges
';
+ h += '
';
+ var wws = fr.waterway_summary || [];
+ if (wws.length) {
+ h += 'Waterways
| Waterway | Status | Warnings |
';
+ wws.forEach(function(w) {
+ var sCls = w.status === 'critical' ? 'down' : w.status === 'elevated' ? 'warn' : w.status === 'advisory' ? 'warn' : 'dim';
+ h += '| ' + esc(trunc(w.name || '?', 22)) + ' | ' + esc(w.status || '\u2014') + ' | ' + (w.warning_count || 0) + ' |
';
+ });
+ h += '
';
+ }
+ }
+
// ── SOCIAL ──
if (data.social_signals && !data.social_signals.error) {
var soc = data.social_signals;
@@ -1661,6 +1769,39 @@ function updateDrawer(data) {
}
}
+ // ── POPULATION EXPOSURE ──
+ if (data.population_exposure && !data.population_exposure.error && data.population_exposure.exposed_city_count > 0) {
+ var pe = data.population_exposure;
+ h += 'POPULATION EXPOSURE
';
+ h += '';
+ h += '
' + esc(pe.total_exposed_population_formatted || '0') + '
Total Exposed
';
+ h += '
' + (pe.exposed_city_count || 0) + '
Cities
';
+ h += '
' + (pe.events_analyzed || 0) + '
Events
';
+ h += '
' + (pe.radius_km || 200) + 'km
Radius
';
+ h += '
';
+ var byType = pe.by_event_type || {};
+ if (Object.keys(byType).length) {
+ h += '';
+ for (var etk in byType) {
+ if (byType.hasOwnProperty(etk)) {
+ var etColor = etk === 'conflict' ? 'var(--red)' : etk === 'earthquake' ? 'var(--amber)' : '#ffea00';
+ h += '
' + esc(byType[etk]) + '
' + esc(etk) + '
';
+ }
+ }
+ h += '
';
+ }
+ var exCities = pe.exposed_cities || [];
+ if (exCities.length) {
+ h += '| City | Pop | Threat | Dist |
';
+ exCities.slice(0, 15).forEach(function(c) {
+ var popStr = (c.population || 0) >= 1000000 ? ((c.population / 1000000).toFixed(1) + 'M') : ((c.population / 1000).toFixed(0) + 'K');
+ var tColor = c.nearest_event === 'conflict' ? 'down' : c.nearest_event === 'earthquake' ? 'warn' : 'warn';
+ h += '| ' + esc(c.city || '?') + ' | ' + popStr + ' | ' + esc(c.nearest_event || '\u2014') + ' | ' + (c.distance_km || 0) + 'km |
';
+ });
+ h += '
';
+ }
+ }
+
// ── GEOSPATIAL ──
if (data.military_bases || data.strategic_ports || data.pipelines || data.nuclear_facilities) {
h += 'GEOSPATIAL
';
@@ -1757,6 +1898,7 @@ function updateAll(data) {
updateMapConvergence(data.signal_convergence);
updateMapNuclear(data.nuclear_monitor);
updateMapInfra(data);
+ updateMapExposure(data.population_exposure);
// Alert banner
updateAlertBanner(data.alert_digest);