Strategic Risk Overview
The Strategic Risk Overview displays the server-side Strategic Risk headline and nearby operational context from the browser panel.Server Score and Browser Fallback (0-100)
The displayed/server headline score comes fromGetRiskScores.strategicRisks[0].
The server derives it from the weighted top 5 CII countries by combinedScore,
not from the additive panel context formula:
sanctionsScore is capped at 10 from new sanctions entries,
largest-country entry volume, and sanctioned vessel/aircraft counts;
radiationScore is capped at 12 from radiation-watch spikes, elevated readings,
and corroborated observations, reduced by low-confidence or conflicting
observations.
Risk Levels
The browser panel and the server API intentionally expose two related but different level schemes for the same 0-100 headline score. Panel-visible display labels use the same five CII-aligned bands as country risk rows:
Server/on-wire enum labels remain the three-tier
StrategicRisk.level
contract returned by GetRiskScores:
API clients should treat
StrategicRisk.level as the server severity enum and
the Strategic Risk panel label as a display mapping of StrategicRisk.score.
Unified Alert System
Alerts from all modules are merged using temporal and spatial deduplication:- Time window: Alerts within 2 hours may be merged
- Distance threshold: Alerts within 200km may be merged
- Same country: Alerts affecting the same country may be merged
Alert Priority
Alert priority is separate from the server Strategic Risk High/Medium/Low bands. References to Critical and High CII below use the Country Instability Index country-score bands documented in Country Instability Index.Trend Detection
Country-level CII rows track approximate 24-hour movement throughdynamicScore:
dynamicScoreis a signed delta from -100 to 100 against the nearest valid prior CII snapshot.- Positive values mean the score rose, negative values mean it fell, and
0means stable or no valid prior snapshot. - Trend labels use the server deadband: greater than
+1is rising, less than-1is falling, and values from-1through+1remain stable.
StrategicRisk headline is different: it currently
sets trend to stable while publishing the top-5 CII roll-up score. If cached
server scores are unavailable, the browser fallback computes its own
escalating/stable/de-escalating panel trend from local additive context; that
fallback trend is a UI continuity signal, not the authoritative server
Strategic Risk trend contract.
Pentagon Pizza Index (PizzINT)
The dashboard integrates real-time foot traffic data from strategic locations near government and military facilities. This “Pizza Index” concept, tracking late-night activity spikes at restaurants near the Pentagon, Langley, and other facilities, provides an unconventional indicator of crisis activity.How It Works
The system aggregates percentage-of-usual metrics from monitored locations:- Locations: Fast food, pizza shops, and convenience stores near Pentagon, CIA, NSA, State Dept, and other facilities
- Aggregation: Activity percentages are averaged, capped at 100%
- Spike Detection: Locations exceeding their baseline are flagged
DEFCON-Style Alerting
Aggregate activity maps to a 5-level readiness scale:World Monitor Tension Pairs
The indicator also displays World Monitor tension scores computed independently from GDELT event exports:
Each pair shows:
- Current tension score (World Monitor’s normalized score)
- Trend (rising, falling, stable), against the pair’s own usual weekly swing
- Percentage change: the last seven days against the seven days before them
- Evidence, when there is any: up to three non-news readings above their usual level, such as airport closures or military aircraft in the region
Backed by endpoints
World Monitor country-pair tensions
Tensions use GDELT event exports, not PizzINT’s GPR scale. The materializer counts both actor directions for the pairs inshared/gdelt-tension-pairs.json:
US–Russia, Russia–Ukraine, US–China, China–Taiwan, US–Iran, Israel–Iran,
Iran–GCC, and Saudi Arabia–Yemen. A side can be a group: Iran–GCC sums Iran’s
events with each Gulf Cooperation Council member (Saudi Arabia, the UAE, Qatar,
Kuwait, Bahrain, Oman) into one bucket; events between members are not counted.
GDELT codes Houthi actors as plain Yemen (YEM), so Saudi Arabia–Yemen counts
every Yemeni actor, Houthi or government. US–Venezuela did not meet the initial
20 conflict events/day measurement threshold and is excluded. No GDELT query
API is used.
A pair added to the list scores Insufficient data until its 90-day history
is rebuilt (below), 11 to 22 hours after the worker first runs with it. The
day in progress gets no bucket for the new pair, since it would miss the
earlier cohorts; that day is rebuilt once it is complete. China–Taiwan,
Israel–Iran and Saudi Arabia–Yemen were added together with the fifteen-day
hourly window, so the first runs after that deploy rebuild every stored day,
newest first. Until the last fourteen completed days have their hours, two to
four hours, the existing pairs read from completed days.
For each UTC day, conflict intensity is the sum of
NumMentions × max(0, −GoldsteinScale) for events in QuadClass 3 or 4.
The daily bucket also retains total events, conflict events, and tone sum/count
(mean tone is their quotient). Events are assigned to their export cohort’s UTC
day. For the last fifteen days, each day also keeps per-hour conflict counts and
intensity, so scoring can follow the live feed instead of waiting for midnight.
The score is the percentile of the live seven-day mean against the pair’s 90
completed daily intensities. The live mean is the last 168 complete hours
divided by seven; an hour is complete once all four of its 15-minute export
cohorts are in, so the hour in progress is not counted. If any of the last 336
hours is missing or partial, the pair falls back to completed days: the last
seven for the score, and the last fourteen for the change. The percentile is
100 × (days below the mean + 0.5 × days equal to it) / 90.
A non-flat history’s maximum scores 100; a flat history scores 50.
This measures relative event intensity, not the probability of conflict.
The scored seven days (live or completed) must contain at least 140 conflict events (20 × 7).
All 90 days must have all 96 export cohorts. Missing or partial days do not
count as quiet days. During initial history collection, or when these conditions
fail, the stored insufficientPairs lists the pair; the API omits its numeric
score and the panel shows Insufficient data.
Change is 100 × (last seven days − the seven before) / the seven before, on
summed intensity. Live, the two weeks are the last 336 complete hours split at
168, so a surge shows within the hour; in the completed-day fallback they are
the last fourteen completed days. Each week holds every weekday once, so the
weekend dip in news volume (about 0.7× on Saturdays and Sundays) cancels out
instead of reading as a fall.
Each pair’s trend threshold is its own usual weekly swing: the 75th percentile
(linearly interpolated) of the absolute seven-day against previous seven-day
changes over every fourteen-day span in its 90 completed days, never below 10%.
The trend rises above the threshold, falls below its negative, and is stable at
the boundaries, so a volatile pair needs a larger move than a calm one.
A zero preceding week with positive recent intensity has no finite percentage
change, so that pair is also withheld. Two zero weeks give a zero change.
The independent gdelt:bulk:dyad-tension:v1 snapshot retains 90 completed days
plus today’s bucket, with a 92-day TTL and its own replay cursor. Health checks
seed-meta:gdelt:bulk:dyad-tension at a 45-minute budget for the 15-minute worker.
The API withholds snapshots older than 45 minutes even though history remains
stored. Responses that include tensions bypass HTTP caches, and the browser does
not retain tension responses for cache fallback. Healthy ingestion does not mean
that the 90-day scoring history is ready.
The probe stays pending until the first successful data and metadata publication
sets its durable activation marker. It then enforces freshness permanently.
Pizza outages do not hide available tensions.
Each worker run also repairs history. It walks the past days in the window
that are missing, lack any of their 96 cohorts, lack a bucket for a pair
added after the day began, or, within the last fifteen days, lack complete
hours, newest first, and rebuilds
up to two of them from that day’s GDELT export files. A rebuilt day replaces
the stored day. It is never added to it, so the partial first day of
collection counts once. Only days before the replay cursor’s day are repaired,
so the live merge never adds to a rebuilt day. The worker downloads a day only
when the GDELT master file list names all 96 of its exports, and checks each
file against the listed size and MD5. A day with an upstream gap is skipped
without downloading it and does not count toward the two. A day whose download
or verification fails is recorded in the snapshot’s repairFailures and
skipped for six hours, so it cannot hold the run’s slots while older days wait.
A later successful rebuild clears its record. The repair has a 60-second budget
per run. When the budget runs out, the worker aborts in-flight downloads and
starts no new ones. A failure or an overrun keeps the days that finished and
never delays or fails the live publication. A run usually rebuilds two days and
sometimes one when downloads are slow, so an empty window fills in 45 to 90
runs, 11 to 22 hours.
Evidence beside the scores
The GDELT score measures news coverage. Under a pair’s score, the popup can also show readings from feeds that are not news coverage, when they are above their usual level. They never change the score.
Military aircraft read one operator in one region, so an aircraft is counted
once where theaters overlap: US–Iran counts US aircraft and Israel–Iran Israeli
aircraft in the Iran theater; US–China counts US aircraft and China–Taiwan
Chinese aircraft in the Taiwan theater; US–Russia counts US aircraft in the
Baltic and Black Sea theaters; Russia–Ukraine counts Russian aircraft in the
Black Sea theater; Saudi Arabia–Yemen counts Saudi aircraft in the Yemen and
Red Sea theater.
A reading shows when it reaches its floor and is at least 1.5 times its usual
level. The floors are 3 strikes, 1 airport, 6 military aircraft, 20 Chinese
aircraft, 10 Chinese ships and 3 sanctions additions. A disrupted airport shows
before any usual level exists; every other reading waits for one. The popup
shows at most three readings per pair and adds the usual level in parentheses.
Limitations:
- Strike reports place a strike where it landed, not who fired it, and include interceptions and claims. Saudi Arabia belongs to both Iran–GCC and Saudi Arabia–Yemen, so a strike on Riyadh counts for both. The feed covers the Middle East only.
- The strike feed keeps only its newest 400 events. When all of them fall inside the 72 hours, older strikes in the window are missing, so the strike reading is withheld until the feed reaches back past the window.
- A sanctions list can be skipped for a while (unavailable, or a suspicious snapshot held back). Its changes from that time are never recorded, so that time is left out of that list’s usual rate. A retained gap keeps the last snapshot, and the list’s changes are recorded when it returns, so its time stays in.
- The aircraft counts see only aircraft that broadcast ADS-B. No Chinese, Russian or Saudi military aircraft appeared in the six hours of snapshots checked on 2026-10-25, so those readings are usually zero.
- Ukraine has no monitored airport. Airport names are English city names.
- The strike tally keeps 30 days and began on 2026-10-19; the sanctions log began on 2026-10-18. Until each has its minimum history, strikes and sanctions have no usual level and do not show.
scripts/seed-tension-evidence.mjs every 15
minutes. It writes intelligence:tension-evidence:v1 as
{ updatedAt, live, days }: live holds every pair’s current readings, and
days keeps one value per pair, reading and UTC day for 90 days (the daily
peak for airports and military aircraft, the latest value for the rest).
Strike days are copied from the tally for every day it still holds, so a
missed run is repaired. The key has a 100-day TTL. Each source must be within its own health budget
(strikes 45 minutes, airport delays 90, NOTAM closures 240, military aircraft
30, Taiwan Strait 720, sanctions 720); a stale or malformed source drops only
its readings, and a run with no readings at all keeps the last payload. Health
checks seed-meta:intelligence:tension-evidence at a 45-minute budget once the
first publish sets seed-activated:intelligence:tension-evidence. The API
attaches only notable readings, ignores evidence older than 45 minutes, and
serves the scores unchanged when the evidence is missing.
Related Assets
News clusters are automatically enriched with nearby critical infrastructure. When a story mentions a geographic region, the system identifies relevant assets within 600km, providing immediate operational context.Asset Types
Location Inference
The system infers the geographic focus of news stories through:- Keyword matching: Headlines are scanned against hotspot keyword lists (e.g., “Taiwan” maps to Taiwan Strait hotspot)
- Confidence scoring: Multiple keyword matches increase location confidence
- Fallback to conflicts: If no hotspot matches, active conflict zones are checked
Distance Calculation
Assets are ranked by Haversine distance from the inferred location:Example Context
A news cluster about “pipeline explosion in Germany” would show:- Pipelines: Nord Stream (23km), Yamal-Europe (156km)
- Cables: TAT-14 landing (89km)
- Bases: Ramstein (234km)
Server-Side Risk Score API
Strategic risk and Country Instability Index (CII) scores are pre-computed server-side rather than calculated in the browser. This eliminates the “cold start” problem where new users would see no data while the system accumulated enough information to generate scores.How It Works
TheGetRiskScores RPC handler (get-risk-scores.ts):
- Fetches recent protest/riot/battle/explosion/civilian-violence data from ACLED. While ACLED returns no events (WorldMonitor has no ACLED access), Conflict and Unrest instead read GDELT terms (escalation or sustained level) from
gdelt:bulk:country-events:v1. - Fetches auxiliary sources from Redis, including UCDP conflicts, outages, climate, fires, GPS jamming, Iran events, OREF alerts, advisories, displacement, classified news summaries, aviation alerts, earthquakes, sanctions, and military/AIS CII aggregates.
- Computes CII v14 scores for 31 Tier-1 countries using the shared coefficient table documented in CII Risk Scoring Methodology.
- Derives Strategic Risk from the weighted top 5 CII scores.
- Caches results in Redis with versioned live and stale keys tied to the current CII formula version.
CII Score Calculation
Each country’s score combines a static baseline (40%) with a dynamic event score (60%), plus supplemental boosts and floors. Baseline Risk (0-50 points): Static geopolitical risk reflecting structural fragility. The canonical per-country values live in CII Risk Scoring Methodology. Event Score blends four sub-components:
Floors (minimum score guarantees):
Supplemental Boosts: Advisory boost (+15/+10/+5), OREF blend boost for IL (+15 active + history tiers), climate (+15 max), fires (+8 max), displacement (+20 max), news urgency (+5 max), earthquakes (+25 max), sanctions (+14 max), AIS disruptions (+10 max), and strike pressure (+10 max).
Advisory outputs expose both
advisoryLevel and advisoryProvenance on each
CiiScore. advisoryProvenance is live when the score used the seeded
advisory feed, fallback when it used the embedded State Department fallback
table, and absent when no advisory level affected boosts or floors.
Event Significance Multipliers
Events in some countries carry more global significance than others:Strategic Risk Derivation
The composite strategic risk score is computed as a weighted average of the top 5 CII scores:Data Sources
Fallback Behavior
When upstream data is unavailable (API errors, rate limits):- Stale cache: Return the latest versioned stale risk-score payload when available.
- Baseline fallback: Return scores using static baselines and available floors when no usable live or stale payload exists.
GetRiskScoresResponse: stale-cache
responses set degraded=true and stale=true; cold baseline-only responses set
degraded=true and stale=false. Fresh/cache-hit responses set both flags to
false.
The relay CII warm-ping loop is active in scripts/ais-relay.cjs: it calls
/api/intelligence/v1/get-risk-scores every 8 minutes, lets the RPC handler
refresh live/stale cache state, and writes seed-meta:intelligence:risk-scores
for health monitoring when scores are returned. That seed-meta recordCount
is real-time signal-density coverage, not raw feed availability: it counts the
score-relevant Tier-1 conflict (ACLED, UCDP, or a live GDELT baseline while ACLED returns no events) and news signal families
present in the CII refresh. Quiet-but-fresh real-time feeds can therefore produce
COVERAGE_PARTIAL; source-specific freshness remains the feed-heartbeat view.