docs/panels/forecast.mdx
The AI Forecasts panel (internal id forecast) surfaces forecasts produced by WorldMonitor's forecasting pipeline. Each forecast is a structured prediction with a probability, a domain classification, a time horizon, and supporting signals; the panel exposes two axes of filtering (domain and macro-region) so you can home in on the slice that matters to you.
A list of active forecasts, filtered by two pill rows:
All / Conflict / Market / Supply Chain / Political / Military / Cyber / Infra
All Regions / MENA / East Asia / Europe / South Asia / Africa / LatAm / N. America
Forecasts whose region does not classify (unknown or global) only appear under All Regions. The panel's minimum-probability floor is 0.1 (10%) — predictions below that threshold are suppressed from the panel view.
Each forecast row shows the prediction text, the event probability, the domain (color-coded), the time horizon, and a compact provenance trail. Deep-simulation path confidence is shown separately as confidence, not as event probability.
Forecast probabilities come from deterministic, rule-based signal detectors in scripts/seed-forecasts.mjs, with an optional prediction-market blend when a matching market anchor exists. LLM calls do not set the numeric probability. They generate the narrative scenario, case-file, branches, and perspective text around the already-scored forecast. The default source-backed LLM routes in the seeder are OpenRouter deepseek/deepseek-v4-flash (primary, reasoning disabled) and Groq llama-3.3-70b-versatile (fallback). One tracked exception: the critical-signals extraction stage contributes signal strength/confidence into the deterministic scoring of state-derived (market/supply-chain) forecasts, so that stage stays pinned to its legacy models until the calibration question is settled (see issue #4963).
Forecast probabilities are market-calibrated only when the seeder can match the forecast to a prediction-market anchor. In those cases the calibration object includes the market title, market price, drift, and source, and the displayed probability is blended with that market anchor. Forecasts with calibration: null are internal/legacy estimates derived from WorldMonitor signal rules rather than externally market-calibrated probabilities.
The seeder also publishes horizon projections (24h, 7d, 30d) from domain-specific projection curves in scripts/seed-forecasts.mjs. Market forecasts are normalized against the strongest multiplier in their domain curve before the three horizons are expanded, so a 30-day market emission does not inflate the 24-hour projection. Other domains preserve the emitted horizon as the semantic anchor. Projected probabilities are then clamped to a 1% floor and 95% cap (0.01 / 0.95) as a presentation heuristic, so projection endpoints should be interpreted as bounded scenario paths rather than unconstrained calibrated probabilities.
The GET /api/forecast/v1/get-forecasts response includes degraded, stale, and error fields so clients can distinguish a backend/cache outage from a healthy empty forecast set.
Panel id is forecast; canonical component is src/components/ForecastPanel.ts.
forecast.FULL_PANELS in src/config/panels.ts.Primary RPC: GET /api/forecast/v1/get-forecasts. The panel also consumes adjacent RPCs for the richer deep-simulation surface:
GET /api/forecast/v1/get-simulation-package — the most recent simulation inputs bundle.GET /api/forecast/v1/get-simulation-outcome — the most recent simulation result.POST /api/forecast/v1/trigger-simulation — starts a fresh simulation run outside the cached cron output.The forecasting pipeline runs as a Railway cron that pulls recent conflict, intelligence, markets, and supply-chain signals, runs them through the forecasting model, and writes the aggregated result at forecast:predictions:v2 in Redis. Macro-region classification comes from shared/forecast-macro-regions.js.
Published forecasts are capped and deduplicated before they reach the panel:
| Rule | Cap |
|---|---|
| Forecasts per situation | 3 |
| Forecasts per situation/domain pair | 2 |
| Forecasts per family | 4 |
| Forecasts per family/domain pair | 2 |
| Target published count | 10-14 |
| Conflict base detector probability ceiling (before velocity spike) | 0.90 |
| UCDP conflict-zone gate floor at 10 events (before velocity spike) | 0.35 |
| UCDP conflict-zone base probability ceiling (before velocity spike) | 0.85 |
| Conflict velocity-spike override ceiling | 0.99 |
| Market probability ceiling | 0.85 |
| Supply-chain / maritime probability ceiling | 0.85 |
| GPS supply-chain detector probability ceiling | 0.60 |
| Political probability ceiling | 0.80 |
| Military probability ceiling | 0.90 |
| Infrastructure probability ceiling | 0.85 |
| Cyber probability ceiling | 0.72 |
When a forecast can be matched to a geopolitical prediction market, the seeder records the market title, market price, drift from the internal probability, and market source. The final probability is then blended as 0.4 * market_probability + 0.6 * internal_probability.
Market-bucket scenario calibration is an editorial calibration layer in scripts/seed-forecasts.mjs, not a learned market model. It biases how market-context simulation states contribute to forecast pressure and confidence so that direct energy/freight transmission receives more lift, broad macro buckets stay moderate, and defense repricing is damped unless the evidence is strong.
| Bucket | Simulation bias: confirmation / pressure / edge / contradiction | State calibration: edgeLift / macroLift / confidenceLift / dampener | Rationale |
|---|---|---|---|
| Energy | 0.20 / 0.12 / 0.10 / 0.14 | 0.08 / 0.14 / 0.05 / - | Strong direct route, commodity, and macro pass-through. |
| Freight | 0.18 / 0.12 / 0.10 / 0.14 | 0.09 / 0.12 / 0.04 / - | Shipping-cost shocks are treated as high-signal supply-chain pressure. |
| Sovereign risk | 0.17 / 0.11 / 0.09 / 0.15 | 0.07 / 0.10 / 0.04 / - | Macro stress matters, but confirmation still needs country/market evidence. |
| Rates / inflation | 0.16 / 0.10 / 0.08 / 0.16 | 0.06 / 0.12 / 0.05 / - | Policy and inflation transmission is broad but less event-specific. |
| FX stress | 0.15 / 0.09 / 0.08 / 0.14 | 0.05 / 0.10 / 0.04 / - | Currency pressure is allowed to lift risk, with moderate confidence. |
| Semis | 0.13 / 0.08 / 0.09 / 0.12 | 0.04 / 0.04 / 0.02 / - | Semiconductor shocks need targeted infrastructure or shipping evidence. |
| Crypto / stablecoins | 0.11 / 0.07 / 0.08 / 0.12 | 0.03 / 0.05 / 0.02 / - | Digital-asset stress is useful context but gets a lighter macro lift. |
| Defense | 0.08 / 0.04 / 0.05 / 0.10 | -0.03 / 0 / -0.03 / 0.12 | Defense signals are intentionally damped to avoid over-reacting to noisy security headlines. |
The conflict and UCDP conflict-zone rows are base detector caps. UCDP conflict-zone counts begin at the 10-event publish gate with a 0.35 base probability and ramp to the 0.85 base cap at 100 events. When the matching EMA risk score has velocitySpike, the seeder adds a +0.08 probability override after the base cap and clamps the result to 0.99.
Defense state calibration has additional direct-confirmation terms outside the table: each unit of direct defense_repricing confirmation adds +0.12 pressure and +0.08 confidence. When that direct defense confirmation is absent, pressure subtracts the table-driven dampener (0.12) and confidence subtracts a separate 0.04 absence penalty.
Probability projections are expanded to 24h, 7d, and 30d with domain curves. Market projections use the curve's peak multiplier as the anchor; other domains use the forecast's emitted horizon:
| Domain | 24h | 7d | 30d |
|---|---|---|---|
| Conflict | 0.91 | 1.00 | 0.78 |
| Market | 1.00 | 0.58 | 0.42 |
| Supply chain | 0.91 | 1.00 | 0.64 |
| Political | 0.83 | 0.87 | 1.00 |
| Military | 1.00 | 0.91 | 0.65 |
| Cyber | 1.00 | 0.78 | 0.40 |
| Infrastructure | 1.00 | 0.50 | 0.25 |
Trend is serialized as a string, not an enum. The current values are rising, falling, and stable, based on a +/- 0.05 probability delta versus the prior forecast snapshot.
World Monitor also runs a shadow-only forecasting lane at forecast:bets:history:v1. It never writes forecast:predictions:v2, so its bets do not appear in the AI Forecasts panel. The purpose is to measure candidate generation methods against resolved outcomes before allowing them into the user-facing forecast stream.
The lane now includes a dedicated geopolitical prediction-market family:
generationOrigin: "bet_engine" so the scorecard can compare that lane with the canonical forecast generator instead of mixing their records.Each shadow bet retains its empirical or thin-history baselineProbability. When FORECAST_BETS_ENSEMBLE=1 is explicitly enabled, a bounded three-pass LLM ensemble may replace the working probability for top-ranked new attempts while leaving the baseline attached for Brier-score comparison. The ensemble is off by default; partial passes are labeled ensemble_partial and are eligible for a later upgrade rather than being treated as a completed ensemble.
This separation is deliberate: prediction-market liquidity is useful calibration evidence, but shipping a new family directly into the panel before it accumulates settlement history would confuse implementation completeness with demonstrated forecast skill.
Every published forecast carries a resolution spec — a machine-checkable contract that defines what "came true" means at resolution time. The spec is either hard (auto-resolvable by comparing a metric from the same WorldMonitor feed the detector scored from) or judged (a resolution question for a later LLM judge, still with a hard deadline). The deadline is always present, encoded in epoch milliseconds: emission time plus the forecast's stated horizon — except prediction-market forecasts, which resolve at the market's own end date when it has one.
Hard specs apply to forecasts with clean metric backing:
Judged specs apply to forecasts in domains where signal-to-outcome mapping is editorial or composite:
The resolution spec does not change which forecasts are published or their order — it only adds machine-checkable ground truth to each forecast, the substrate for a future public track record that scores forecasts against what actually happened.
The seeder runs approximately hourly. The key is allowed up to 90 minutes in api/health.js (maxStaleMin: 90) before the health surface escalates.
Published forecasts carry machine-checkable resolution specs. A daily resolver pre-registers those specs into forecast:resolutions:v1, samples hard-source metrics until their deadlines, then resolves each published forecast to YES, NO, or VOID with evidence. The derived forecast:scorecard:v1 reports Brier score, log score, calibration buckets, VOID rate, publication coverage, and market-anchor skill. Judged specs are tracked as pending for a fast-follow judge and are excluded from the first hard-source scorecard until resolved.
On web, AI Forecasts is currently in trial — free to anyone, including unauthenticated readers. On desktop, the same panel ships with premium: 'locked' in src/config/panels.ts so it is gated there. This split reflects the product's current trial positioning; expect the web gating to tighten over time.
get-forecasts, get-forecast-scorecard, get-simulation-outcome, get-simulation-package, and trigger-simulation.generate_forecasts tool.