1 Commits

Author SHA1 Message Date
Elie Habib
994aa05e63 feat(forecasts): EMA-based threat velocity spike detection (#2463)
* feat(forecasts): add EMA-based threat velocity spike detection

- New pure-math module _ema-threat-engine.mjs: updateWindow, computeWindowStats,
  computeEmaWindows, computeRisk24h with ALPHA=0.3, 24-entry sliding window
- Add ACLED (conflict:acled:v1:all:0:0) and EMA windows (conflict:ema-windows:v1)
  to readInputKeys() Redis pipeline batch
- updateEmaWindows() merges ACLED + UCDP event counts, computes z-scores, persists
  updated windows with 26h TTL before domain detectors run
- detectConflictScenarios and detectUcdpConflictZones inject velocity_spike signals
  (weight 0.35, +0.08 prob boost) when risk24h >= 75

* fix(ema): delta counts + EMA-based z-score in threat engine

- computeEmaWindows: track lastRawCount per country, append delta
  (currentRaw - priorRaw) instead of absolute snapshot totals
- Union prior + current keys so countries absent from current snapshot
  get delta=0 (not silently dropped)
- First run: priorRaw = currentRaw so initial delta is 0, not a spike
- Clamp delta >= 0 to handle snapshot window shrinkage
- computeRisk24h: z-score now uses window.ema instead of raw last entry,
  so ALPHA=0.3 smoothing actually dampens single-run spikes before alerting

* fix(ema): timestamp-based 24h event counting replaces unreliable snapshot delta

- Remove lastRawCount delta logic that suppressed real acceleration in rolling source windows
- computeEmaWindows now counts events with event_date/date_start >= now-24h directly from snapshot objects
- Add private normalizeCountry() helper; add nowMs param for testability
- Remove lastRawCount from WindowState typedef (no longer stored or persisted)

* fix(ema): seed-meta write + dead code cleanup

- P1: write seed-meta:conflict:ema-windows:v1 after EMA persist so health monitoring detects silent failures
- P2: remove dead `last` variable from computeWindowStats
- P2: remove redundant full O(n) EMA sweep in computeWindowStats (incremental EMA in updateWindow is authoritative)
- P2: remove dead `?? 0` guard on `prob` in detectConflictScenarios (already a number at that point)
2026-03-29 11:09:26 +04:00