diff --git a/src/world_intel_mcp/dashboard/app.py b/src/world_intel_mcp/dashboard/app.py index 06f703c..89fc1df 100644 --- a/src/world_intel_mcp/dashboard/app.py +++ b/src/world_intel_mcp/dashboard/app.py @@ -42,6 +42,7 @@ from world_intel_mcp.sources import ( nuclear, ) from world_intel_mcp.analysis.alerts import fetch_alert_digest, fetch_weekly_trends +from world_intel_mcp.config.countries import INTEL_HOTSPOTS logger = logging.getLogger(__name__) @@ -119,6 +120,35 @@ async def _fetch_overview() -> dict: else: result[name] = data + # Conflict zone fallback: when both ACLED and UCDP fail, provide + # static hotspot data so the conflict layer is never empty. + acled = result.get("acled_events", {}) + ucdp = result.get("ucdp_events", {}) + acled_ok = not acled.get("error") and (acled.get("count") or 0) > 0 + ucdp_ok = not ucdp.get("error") and (ucdp.get("count") or 0) > 0 + if not acled_ok and not ucdp_ok: + escalation_labels = {1: "low", 2: "low", 3: "moderate", 4: "high", 5: "critical"} + hotspot_events = [ + { + "latitude": h["lat"], + "longitude": h["lon"], + "country": name.replace("_", " ").title(), + "event_type": "conflict zone", + "type_of_violence_label": "active hotspot", + "fatalities": 0, + "best": 0, + "escalation": h["baseline_escalation"], + "severity": escalation_labels.get(h["baseline_escalation"], "unknown"), + "associated_countries": h.get("associated_countries", []), + } + for name, h in INTEL_HOTSPOTS.items() + ] + result["conflict_zones"] = { + "events": hotspot_events, + "count": len(hotspot_events), + "source": "intel-hotspots", + } + # Attach source health + timestamp result["source_health"] = _breaker.status() if _breaker else {} result["cache_stats"] = _cache.stats() if _cache else {} diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html index cec2454..3cf83cc 100644 --- a/src/world_intel_mcp/dashboard/index.html +++ b/src/world_intel_mcp/dashboard/index.html @@ -700,12 +700,16 @@ function updateMapConflict(data) { var lat = e.latitude || e.lat, lon = e.longitude || e.lon; if (lat == null || lon == null) return; var fat = e.fatalities || e.best || 0; - var size = Math.max(8, Math.min(18, fat / 2 + 8)); + var esc_level = e.escalation || 0; + var size = fat > 0 ? Math.max(8, Math.min(18, fat / 2 + 8)) : Math.max(8, esc_level * 3 + 6); var label = e.event_type || e.type_of_violence_label || '?'; var mk = L.marker([Number(lat), Number(lon)], { icon: L.divIcon({ className: 'mk-conflict', iconSize: [size, size], iconAnchor: [size/2, size/2] }) }); - mk.bindTooltip(label + ' \u2014 ' + (e.country || '') + (fat > 0 ? ' (' + fat + ' fatal)' : ''), { className: 'mk-tip', direction: 'top', offset: [0, -size/2] }); + var tip = label + ' \u2014 ' + (e.country || ''); + if (fat > 0) tip += ' (' + fat + ' fatal)'; + else if (e.severity) tip += ' [' + e.severity + ']'; + mk.bindTooltip(tip, { className: 'mk-tip', direction: 'top', offset: [0, -size/2] }); mk.on('click', function() { showDetail('conflict', e); }); mk.addTo(mapLayers.conflict); }); @@ -811,7 +815,9 @@ function updateHudStats(data) { if (data.military_flights && !data.military_flights.error) { pills.push('
| Zone | Severity |
|---|---|
| ' + esc(trunc(e.country || '?', 25)) + ' | ' + esc(sev) + ' |