koala73 / koala73/worldmonitor

feat(scorecard): five-factor country scorecard — 1-5 pillar scores (food/energy/demographics/technology/defense) projected over the resilience engine

Open
#6,441 2 comments 0 reactions 0 assignees View on GitHub
feature P1
Dominant language
TypeScript
Stars
86.6k
Forks
13.1k
Avg merge
8h 4m
Merged PRs (30d)
825

Description

## Why

Every country gets five integer scores (1–5) answering five questions: **Can we feed ourselves? Do we have enough energy? Do we have the people? What is our technology? Can we defend ourselves?** This is the judgment layer over signals WorldMonitor already computes plus the data issues #6436–#6440. Its value over the existing CII/resilience composites is legibility and comparability — a common operating picture people can argue about ("Russia: energy 5, demographics 1") — and upward composition: pillar scores aggregate to blocs, which is how claims like "Canada–US–Mexico is the #1 economic bloc" become testable, publishable comparisons.

This measures **structural self-sufficiency** (mostly static, annual-cadence inputs). It is NOT a replacement for CII (dynamic instability) or the resilience headline score (shock absorption) — and it must not change either.

## What exists (the scoping fact that shapes the architecture)

The resilience engine at `server/worldmonitor/resilience/v1/` already has everything a scoring system needs:

- `_pillar-membership.ts` — an existing pillar system (`economic`, `energy`, `health-food`, `infrastructure`, `social-governance`, `structural-readiness`, `recovery-capacity`, `live-shock-exposure`)
- `_dimension-scorers.ts` (~2,900 lines) + `_indicator-registry.ts` — per-dimension scoring with freshness (`_dimension-freshness.ts`), rankable universe, source-failure handling
- `scripts/_resilience-intervals.mjs` — confidence intervals with a versioned methodology
- Lockstep versioning discipline: the cache version (currently v14) bumps in `scripts/seed-resilience-scores.mjs` and `server/worldmonitor/resilience/v1/_shared.ts` together whenever dimensions change

**Do not build a second scoring engine.** The five-factor scorecard is a *projection*: map existing dimensions into the five pillars, add the missing dimensions, band the result.

Pillar input status today:
- **Energy** — strong: `energy` pillar dims, energy import dependency, gas storage, fuel stocks, SPR, electricity mix
- **Food** — partial: `health-food` dims (`scoreFoodWater()`), FAO static data, import HHI; stocks-to-use arrives with #6440
- **Technology** — moderate: tech-readiness ranking (`economic:worldbank-techreadiness:v1`), R&D, patents; infrastructure/cable dims
- **Demographics** — thin: population/migration/labor only; capability stack arrives with #6437
- **Defense** — absent from resilience: military posture lives in `seed-military-cii` and siblings; industrial base arrives with #6438

## Step 0 — architecture audit (post as issue comment before building)

1. Produce the full dimension→pillar mapping table: every existing resilience dimension assigned to F1–F5 or `none`, plus the new dimensions needed (defense posture, defense industrial base, demographics capability) with their input keys.
2. Confirm the projection approach: new dimensions register in the indicator registry but are **excluded from the resilience headline score** (membership stays out of the existing pillar sets). If the registry cannot express "registered but headline-excluded," say so and propose the smallest change that can.
3. Confirm the cache-version impact: if touching the registry forces a version bump, plan the lockstep bump per the v13→v14 precedent noted in `seed-resilience-scores.mjs`.

## Design decisions (specified, not open)

**Scoring: absolute criteria bands, not percentiles.** Each pillar maps its continuous sub-score to integer 1–5 via absolute, documented thresholds. Percentile/relative banding is forbidden: a "common language" breaks if a 3 means something different every quarter, and bloc comparisons become circular. Bands live in a methodology doc (see Expose) with the physical meaning of each band stated per pillar (e.g. Food 5 ≈ net exporter with above-median stocks-to-use; Food 1 ≈ majority import-dependent with concentrated suppliers). Exact thresholds are the implementer's proposal in the PR, reviewed against ~10 hand-checked countries.

**Provenance is mandatory.** Every pillar score ships with its contributing dimensions, their values, observation years, and the sub-score before banding. A score that can't show its work is not shippable — this is the partner-facing surface and "it's not opinion" is the pitch.

**Missing data: null, never a default.** A pillar with input coverage below its methodology-defined minimum returns `null` + machine-readable reasons (same discipline as the Strategic Risk insufficient-data gate). No defaulted 3s, no silent omission — a country with two scoreable pillars shows three explicit "insufficient data" pillars. The insufficient-data path must be observable through the public API and covered by a test that proves it fires (a guard that cannot fire is vacuous).

**Methodology versioning.** Every record carries `methodologyVersion`. Threshold or mapping changes bump it with a changelog entry in the methodology doc. Scores are partner-visible and will be cited; silent re-scoring destroys the credibility this exists to build.

**Bloc aggregation: aggregate raw inputs where physically meaningful, weighted scores otherwise.** Food and energy bloc scores come from combined production vs. combined consumption across members (a bloc that nets out self-sufficient scores as such even if each member alone doesn't) — never from averaging member scores. Demographics/technology/defense may use population-weighted score averages. Each bloc pillar states its aggregation method on the record. Predefined blocs: USMCA, EU27, BRICS, GCC, ASEAN, NATO; plus arbitrary ISO-2 lists via the RPC request (validated, max ~30 members).

**Phasing: ship v1 on existing data; do not block on #6437/#6438/#6440.** v1 ships with per-pillar `inputCoverage` marked (energy/technology strong; food partial; defense posture-only; demographics thin — expect many nulls). Each data issue landing upgrades coverage without a methodology redesign; wire-in of each is a small follow-up PR bumping the methodology version.

## Build

- Compute follows the resilience pattern: server-side scorer + seeder warm/refresh (`scripts/seed-five-factor-scores.mjs`, modeled on `seed-resilience-scores.mjs` including the seed-refresh-key gating). Cheap daily cadence; inputs are mostly annual. Bundle membership in `scripts/railway-services.json` per `docs/railway-seed-consolidation-runbook.md`.
- Storage: `scorecard:five-factor:v1` — per ISO-2 country: `{ methodologyVersion, computedAt, pillars: { food: { score: 1-5|null, subScore, band, inputs: [{dim, value, year, source}], insufficientReasons?, inputCoverage }, energy: {...}, demographics: {...}, technology: {...}, defense: {...} } }`. Bloc results computed on request (predefined blocs may be cached under `scorecard:five-factor-bloc:v1`).
- Scorer functions must be pure and exported for testing (no live clock, no I/O inside scoring — fixtures in, scores out).

## Expose

- sebuf only (`docs/adding-endpoints.mdx`): a new `worldmonitor.scorecard.v1` service — `GetFiveFactorScorecard(country)`, `ListFiveFactorScorecards()`, `GetBlocScorecard(preset | members[])`. Proto → `make generate`; generated code checked in; biome CI runs the API contract gate.
- Country deep-dive: five-factor section rendering the five scores with provenance drill-down and explicit insufficient-data states. i18n key gates apply to all new UI strings.
- MCP: `get_five_factor_scorecard` (country + bloc) — this is the partner-POC surface ("five factors on Zimbabwe" must work from an agent); update `docs/mcp-tools-reference.mdx`.
- New methodology docs page (bands per pillar, aggregation methods, versioning changelog) — a new docs page triggers the docs registry gates; follow them.

## Acceptance criteria

- [ ] Step 0 mapping table + architecture confirmation posted as an issue comment before implementation.
- [ ] **Resilience headline scores unchanged**: before/after comparison on seeded production data shows byte-identical resilience scores (regression check in the PR).
- [ ] `scorecard:five-factor:v1` populated; every non-null pillar carries provenance (inputs, values, years, sub-score); every null pillar carries reasons.
- [ ] Pure-scorer unit tests with synthetic fixtures, including band-boundary values (score flips exactly at the documented threshold) and the insufficient-data gate proven to fire through the public API path.
- [ ] Bloc test proving food/energy bloc scores derive from combined raw inputs, not averaged member scores (fixture where the two methods disagree).
- [ ] ~10-country hand-check table in the PR (score vs. reviewer expectation, discrepancies explained or thresholds adjusted).
- [ ] RPCs + regenerated OpenAPI; deep-dive renders including a mostly-null country; MCP answers country and bloc questions end-to-end.
- [ ] Methodology docs page published with v1 bands and changelog; `docs/mcp-tools-reference.mdx` updated.

## Out of scope

Company and commodity five-factor variants (different products: physical-DD and supply profiles), changes to CII or the resilience headline score, prediction-ledger integration, marketing/SEO pages for scorecards, historical score time-series (record from v1 onward; no backfill).

## Dependencies

Consumes (but is not blocked by): #6437 (demographics), #6438 (defense industrial base), #6440 (food stocks). #6436/#6439 do not feed this scorecard (they feed the divergence-index and chokepoint judgment layers, filed separately).

Contributor guide

Open the contributing guide

Research direction

Begin with the Step 0 audit in server/worldmonitor/resilience/v1/, reading _pillar-membership.ts, _dimension-scorers.ts, _indicator-registry.ts, and scripts/seed-resilience-scores.mjs. Trace the existing seeding, API, deep-dive, MCP, and documentation entry points named in the issue. Done means the architecture is confirmed first, then the scorecard ships with pure tests, provenance and null-data handling, bloc tests, generated RPCs, UI/MCP exposure, and methodology docs.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend-api-design, data, documentation, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.