Files
worldmonitor/docs/methodology/indicator-sources.yaml
Elie Habib 7cf37c604c feat(resilience): PR 3 — dead-signal cleanup (plan §3.5, §3.6) (#3297)
* feat(resilience): PR 3 §3.5 — retire fuelStockDays from core score permanently

First commit in PR 3 of the resilience repair plan. Retires
`fuelStockDays` from the core score with no replacement.

Why permanent, not replaced:
IEA emergency-stockholding rules are defined in days of NET IMPORTS
and do not bind net exporters by design. Norway/Canada/US measured
in days-of-imports are incomparable to Germany/Japan measured the
same way — the construct is fundamentally different across the two
country classes. No globally-comparable recovery-fuel signal can
be built from this source; the pre-repair probe showed 100% imputed
at 50 for every country in the April 2026 freeze.

  scoreFuelStockDays:
    - Rewritten to return coverage=0 + observedWeight=0 +
      imputationClass='source-failure' for every country regardless
      of seed content.
    - Drops the dimension from the `recovery` domain's coverage-
      weighted mean automatically; remaining recovery dimensions
      pick up the share via re-normalisation in
      `_shared.ts#coverageWeightedMean`.
    - No explicit weight transfer needed — the coverage-weighted
      blend handles redistribution.

  Registry:
    - recoveryFuelStockDays re-tagged from tier='enrichment' to
      tier='experimental' so the Core coverage gate treats it as
      out-of-score.
    - Description updated to make the retirement explicit; entry
      stays in the registry for structural continuity (the
      dimension `fuelStockDays` remains in RESILIENCE_DIMENSION_ORDER
      for the 19-dimension tests; removing the dimension entirely is
      a PR 4 structural-audit concern).

  Housekeeping:
    - Removed `RESILIENCE_RECOVERY_FUEL_STOCKS_KEY` constant (no
      longer read; noUnusedLocals would reject it).
    - Removed `RecoveryFuelStocksCountry` interface for the same
      reason. Comment at the removed declaration instructs future
      maintainers not to re-add the type as a reservation; when a
      new recovery-fuel concept lands, introduce a fresh interface.

Plan reference: §3.5 point 1 of
`docs/plans/2026-04-22-001-fix-resilience-scorer-structural-bias-plan.md`.

51 resilience tests pass, typecheck + biome clean. The
`recovery` domain's published score will shift slightly for every
country because the 0.10 slot that fuelStockDays was imputing to
now redistributes; the compare-harness acceptance-gate rerun at
merge time will quantify the shift per plan §6 gates.

* feat(resilience): PR 3 §3.5 — retire BIS-backed currencyExternal; rebuild on IMF inflation + WB reserves

BIS REER/DSR feeds were load-bearing in currencyExternal (weights 0.35
fxVolatility + 0.35 fxDeviation, ~70% of dimension). They cover ~60
countries max — so every non-BIS country fell through to
curated_list_absent (coverage 0.3) or a thin IMF proxy (coverage 0.45).
Combined with reserveMarginPct already removed in PR 1, currencyExternal
was the clearest "construct absent for most of the world" carrier left
in the scorer.

Changes:

_dimension-scorers.ts
- scoreCurrencyExternal now reads IMF macro (inflationPct) + WB FX
  reserves only. Coverage ladder:
    inflation + reserves → 0.85 (observed primary + secondary)
    inflation only       → 0.55
    reserves only        → 0.40
    neither              → 0.30 (IMPUTE.bisEer retained for snapshot
                                 continuity; semantics read as
                                 "no IMF + no WB reserves" now)
- Removed dead symbols: RESILIENCE_BIS_EXCHANGE_KEY constant (reserved
  via comment only, flagged by noUnusedLocals), stddev() helper,
  getCountryBisExchangeRates() loader, BisExchangeRate interface,
  dateToSortableNumber() — all were exclusive callers of the retired
  BIS path.

_indicator-registry.ts
- New core entry inflationStability (weight 0.60, tier=core,
  sourceKey=economic:imf:macro:v2).
- fxReservesAdequacy weight 0.15 → 0.40 (secondary reliability
  anchor).
- fxVolatility + fxDeviation demoted tier=enrichment → tier=experimental
  (BIS ~60-country coverage; off the core weight sum).
- Non-experimental weights now sum to 1.0 (0.60 + 0.40).

scripts/compare-resilience-current-vs-proposed.mjs
- EXTRACTION_RULES: added inflationStability →
  imf-macro-country-field field=inflationPct so the registry-parity
  test passes and the correlation harness sees the new construct.

tests/resilience-dimension-scorers.test.mts
- Dropped BIS-era wording ("non-BIS country") and test 266
  (BIS-outage coverage 0.35 branch) which collapsed to the inflation-
  only path post-retirement.
- Updated coverage assertions: inflation-only 0.45 → 0.55; inflation+
  reserves 0.55 → 0.85.

tests/resilience-scorers.test.mts
- domainAverages.economic 68.33 → 66.33 (US currencyExternal score
  shifts slightly under IMF+reserves vs old BIS composite).
- stressScore 67.85 → 67.21; stressFactor 0.3215 → 0.3279.
- overallScore 65.82 → 65.52.
- baselineScore unchanged (currencyExternal is stress-only).

All 6324 data-tier tests pass. typecheck:api clean. No change to
seeders or Redis keys; this is a pure scorer + registry rebuild.

* feat(resilience): PR 3 §3.5 point 3 — re-goalpost externalDebtCoverage (0..5 → 0..2)

Plan §2.1 diagnosis table showed externalDebtCoverage saturating at
score=100 across all 9 probe countries — including stressed states.
Signal was collapsed. Root cause: (worst=5, best=0) gave every country
with ratio < 0.5 a score above 90, and mapped Greenspan-Guidotti's
reserve-adequacy threshold (ratio=1.0) to score 80 — well into "no
worry" territory instead of the "mild warning" it should be.

Re-anchored on Greenspan-Guidotti directly: ratio=1.0 now maps to score
50 (mild warning), ratio=2.0 to score 0 (acute rollover-shock exposure).
Ratios above 2.0 clamp to 0, consistent with "beyond this point the
country is already in crisis; exact value stops mattering."

Files changed:

- _indicator-registry.ts: recoveryDebtToReserves goalposts
  {worst: 5, best: 0} → {worst: 2, best: 0}. Description updated to
  cite Greenspan-Guidotti; inline comment documents anchor + rationale.

- _dimension-scorers.ts: scoreExternalDebtCoverage normalizer bound
  changed from (0..5) to (0..2), with inline comment.

- docs/methodology/country-resilience-index.mdx: goalpost table row
  5-0 → 2-0, description cites Greenspan-Guidotti.

- docs/methodology/indicator-sources.yaml:
  * constructStatus: dead-signal → observed-mechanism (signal is now
    discriminating).
  * reviewNotes updated to describe the new anchor.
  * mechanismTestRationale names the Greenspan-Guidotti rule.

- tests/resilience-dimension-monotonicity.test.mts: updated the
  comment + picked values inside the (0..2) discriminating band (0.3
  and 1.5). Old values (1 vs 4) had 4 clamping to 0.

- tests/resilience-dimension-scorers.test.mts: NO score threshold
  relaxed >90 → >=85 (NO ratio=0.2 now scores 90, was 96).

- tests/resilience-scorers.test.mts: fixture drift:
  * domainAverages.recovery 54.83 → 47.33 (US extDebt 70 → 25).
  * baselineScore 63.63 → 60.12 (extDebt is baseline type).
  * overallScore 65.52 → 63.27.
  * stressScore / stressFactor unchanged (extDebt is baseline-only).

All 6324 data-tier tests pass. typecheck:api clean.

* feat(resilience): PR 3 §3.6 — CI gate on indicator coverage and nominal weight

Plan §3.6 adds a new acceptance criterion (also §5 item 5):

> No indicator with observed coverage below 70% may exceed 5% nominal
> weight OR 5% effective influence in the post-change sensitivity run.

This commit enforces the NOMINAL-WEIGHT half as a unit test that runs
on every CI build. The EFFECTIVE-INFLUENCE half is produced by
scripts/validate-resilience-sensitivity.mjs as a committed artifact;
the gate file only asserts that script still exists so a refactor that
removes it breaks the build loudly.

Why the gate exists (plan §3.6):

  "A dimension at 30% observed coverage carries the same effective
   weight as one at 95%. This contradicts the OECD/JRC handbook on
   uncertainty analysis."

Implementation:

tests/resilience-coverage-influence-gate.test.mts — three tests:
  1. Nominal-weight gate: for every core indicator with coverage < 137
     countries (70% of the ~195-country universe), computes its nominal
     overall weight as
       indicator.weight × (1/dimensions-in-domain) × domain-weight
     and asserts it does not exceed 5%. Equal-share-per-dimension is
     the *upper bound* on runtime weight (coverage-weighted mean gives
     a lower share when a dimension drops out), so this is a strict
     bound: if the nominal number passes, the runtime number also
     passes for every country.
  2. Effective-influence contract: asserts the sensitivity script
     exists at its expected path. Removing it (intentionally or by
     refactor) breaks the build.
  3. Audit visibility: prints the top 10 core indicators by nominal
     overall weight. No assertion beyond "ran" — the list lets
     reviewers spot outliers that pass the gate but are near the cap.

Current state (observed from audit output):

  recoveryReserveMonths:   nominal=4.17%  coverage=188
  recoveryDebtToReserves:  nominal=4.17%  coverage=185
  recoveryImportHhi:       nominal=4.17%  coverage=190
  inflationStability:      nominal=3.40%  coverage=185
  electricityConsumption:  nominal=3.30%  coverage=217
  ucdpConflict:            nominal=3.09%  coverage=193

Every core indicator has coverage ≥ 180 (already enforced by the
pre-existing indicator-tiering test), so the nominal-weight gate has
no current violators — its purpose is catching future drift, not
flagging today's state.

All 6327 data-tier tests pass. typecheck:api clean.

* docs(resilience): PR 3 methodology doc — document §3.5 dead-signal retirements + §3.6 coverage gate

Methodology-doc update capturing the three §3.5 landings and the §3.6 CI
gate. Five edits:

1. **Known construct limitations section (#5 and #6):** strikethrough the
   original "dead signals" and "no coverage-based weight cap" items,
   annotate them with "Landed in PR 3 §3.5"/"Landed in PR 3 §3.6" +
   specifics of what shipped.

2. **Currency & External H4 section:** completely rewritten. Old table
   (fxVolatility / fxDeviation / fxReservesAdequacy on BIS primary) is
   replaced by the two-indicator post-PR-3 table (inflationStability at
   0.60 + fxReservesAdequacy at 0.40). Coverage ladder spelled out
   (0.85 / 0.55 / 0.40 / 0.30). Legacy BIS indicators named as
   experimental-tier drill-downs only.

3. **Fuel Stock Days H4 section:** H4 heading text kept verbatim so the
   methodology-lint H4-to-dimension mapping does not break; body
   rewritten to explain that the dimension is retired from core but the
   seeder still runs for IEA-member drill-downs.

4. **External Debt Coverage table row:** goalpost 5-0 → 2-0, description
   cites Greenspan-Guidotti reserve-adequacy rule.

5. **New v2.2 changelog entry** — PR 3 dead-signal cleanup, covering
   §3.5 points 1/2/3 + §3.6 + acceptance gates + construct-audit
   updates.

No scoring or code changes in this commit. Methodology-lint test passes
(H4 mapping intact). All 6327 data-tier tests pass.

* fix(resilience): PR 3 §3.6 gate — correct share-denominator for coverage-weighted aggregation

Reviewer catch (thanks). The previous gate computed each indicator's
nominal overall weight as

  indicator.weight × (1 / N_total_dimensions_in_domain) × domain_weight

and claimed this was an upper bound ("actual runtime weight is ≤ this
when some dimensions drop out on coverage"). That is BACKWARDS for
this scorer.

The domain aggregation is coverage-weighted
(server/worldmonitor/resilience/v1/_shared.ts coverageWeightedMean),
so when a dimension pins at coverage=0 it is EXCLUDED from the
denominator and the surviving dimensions' shares go UP, not down.

PR 3 commit 1 retires fuelStockDays by hard-coding its scorer to
coverage=0 for every country — so in the current live state the
recovery domain has 5 contributing dimensions (not 6), and each core
recovery indicator's nominal share is

  1.0 × 1/5 × 0.25 = 5.00% (was mis-reported as 4.17%)

The old gate therefore under-estimated nominal influence and could
silently pass exactly the kind of low-coverage overweight regression
it is meant to block.

Fix:

- Added `coreBearingDimensions(domainId)` helper that counts only
  dimensions that have ≥1 core indicator in the registry. A dimension
  with only experimental/enrichment entries (post-retirement
  fuelStockDays) has no core contribution → does not dilute shares.
- Updated `nominalOverallWeight` to divide by the core-bearing count,
  not the raw dimension count.
- Rewrote the helper's doc comment to stop claiming this is a strict
  upper bound — explicitly calls out the dynamic case (source failure
  raising surviving dim shares further) as the sensitivity script's
  responsibility.
- Added a new regression test: asserts (a) at least one recovery
  dimension is all-non-core (fuelStockDays post-retirement),
  (b) fuelStockDays has zero core indicators, and (c) recoveryDebt
  ToReserves nominal = 0.05 exactly (not 0.0417) — any reversion
  of the retirement or regression to N_total-denominator will fail
  loudly.

Top-10 audit output now correctly shows:

  recoveryReserveMonths:   nominal=5%     coverage=188
  recoveryDebtToReserves:  nominal=5%     coverage=185
  recoveryImportHhi:       nominal=5%     coverage=190
  (was 4.17% each under the old math)

All 486 resilience tests pass. typecheck:api clean.

Note: the 5% figure is exactly AT the cap, not over it. "exceed" means
strictly > 5%, so it still passes. But now the reviewer / audit log
reflects reality.

* fix(resilience): PR 3 review — retired-dim confidence drag + false source-failure label

Addresses the Codex review P1 + P2 on PR #3297.

P1 — retired-dim drag on confidence averages
--------------------------------------------
scoreFuelStockDays returns coverage=0 by design (retired construct),
but computeLowConfidence, computeOverallCoverage, and the widget's
formatResilienceConfidence averaged across all 19 dimensions. That
dragged every country's reported averageCoverage down — US went from
0.8556 (active dims only) to 0.8105 (all dims) — enough drift to
misclassify edge countries as lowConfidence and to shift the ranking
widget's overallCoverage pill for every country.

Fix: introduce an authoritative RESILIENCE_RETIRED_DIMENSIONS set in
_dimension-scorers.ts and filter it out of all three averages. The
filter is keyed on the retired-dim REGISTRY, not on coverage === 0,
because a non-retired dim can legitimately emit coverage=0 on a
genuinely sparse-data country via weightedBlend fall-through — those
entries MUST keep dragging confidence down (that is the sparse-data
signal lowConfidence exists to surface). Verified: sparse-country
release-gate test (marks sparse WHO/FAO countries as low confidence)
still passes with the registry-keyed filter; would have failed with
a naive coverage=0 filter.

Server-client parity: widget-utils cannot import server code, so
RESILIENCE_RETIRED_DIMENSION_IDS is a hand-mirrored constant, kept
in lockstep by tests/resilience-retired-dimensions-parity.test.mts
(parses the widget file as text, same pattern as existing widget-util
tests that can't import the widget module directly).

P2 — false "Source down" label on retired dim
---------------------------------------------
scoreFuelStockDays hard-coded imputationClass: 'source-failure',
which the widget maps to "Source down: upstream seeder failed" with
a `!` icon for every country. That is semantically wrong for an
intentional retirement. Flipped to null so the widget's absent-path
renders a neutral cell without a false outage label. null is already
a legal value of ResilienceDimensionScore.imputationClass; no type
change needed.

Tests
-----
- tests/resilience-confidence-averaging.test.mts (new): pins the
  registry-keyed filter semantic for computeOverallCoverage +
  computeLowConfidence. Includes a negative-control test proving
  non-retired coverage=0 dims still flip lowConfidence.
- tests/resilience-retired-dimensions-parity.test.mts (new):
  lockstep gate between server and client retired-dim lists.
- Widget test adds a registry-keyed exclusion test with a non-retired
  coverage=0 dim in the fixture to lock in the correct semantic.
- Existing tests asserting imputationClass: 'source-failure' for
  fuelStockDays flipped to null.

All 494 resilience tests + full 6336/6336 data-tier suite pass.
Typecheck clean for both tsconfig.json and tsconfig.api.json.

* docs(resilience): align methodology + registry metadata with shipped imputationClass=null

Follow-up to the previous PR 3 review commit that flipped
scoreFuelStockDays's imputationClass from 'source-failure' to null to
avoid a false "Source down" widget label on every country. The code
changed; the doc and registry metadata did not, leaving three sites
in the methodology mdx and two comment/description sites in the
registry still claiming imputationClass='source-failure'. Any future
reviewer (or tooling that treats the registry description as
authoritative) would be misled.

This commit rewrites those sites to describe the shipped behavior:
 - imputationClass=null (not 'source-failure'), with the rationale
 - exclusion from confidence/coverage averages via the
   RESILIENCE_RETIRED_DIMENSIONS registry filter
 - the distinction between structural retirement (filtered) and
   runtime coverage=0 (kept so sparse-data countries still flag
   lowConfidence)

Touched:
 - docs/methodology/country-resilience-index.mdx (lines ~33, ~268, ~590)
 - server/worldmonitor/resilience/v1/_indicator-registry.ts
   (recoveryFuelStockDays comment block + description field)

No code-behavior change. Docs-only.

Tests: 157 targeted resilience tests pass (incl. methodology-lint +
widget + release-gate + confidence-averaging). Typecheck clean on
both tsconfig.json and tsconfig.api.json.
2026-04-22 23:57:28 +04:00

687 lines
27 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Resilience scorer indicator-source manifest (PR 0 scaffold, 2026-04-22).
#
# One entry per sub-indicator used inside a dimension scorer. Each entry
# answers the mechanism test from docs/plans/2026-04-22-001-fix-resilience-
# scorer-structural-bias-plan.md §1.1: what direct shock channel does this
# measure?
#
# Fields:
# indicator — scorer variable name (matches the weighted-blend entry)
# dimension — parent dimension id (matches RESILIENCE_DIMENSION_ORDER)
# domain — parent domain id
# weight — current nominal weight inside the dimension blend
# direction — higher-better | lower-better | composite
# source — authority that publishes the series
# seriesId — canonical series id where applicable (e.g. EG.IMP.CONS.ZS)
# seriesUrl — direct link to source documentation
# coveragePct — observed-data coverage across the 222-country static index (first-pass estimate; authoritative value lives in the matching seeder's seed-meta.coverage field)
# lastObservedYear — most-recent year with global data in the source
# license — reuse license (CC-BY, CC0, OGL, Proprietary-with-fair-use, etc.)
# mechanismTestRationale — one-sentence answer to "what direct shock channel does this measure?"
# constructStatus — observed-mechanism | wealth-proxy | imputed-floor | regional-only | dead-signal
#
# constructStatus is the v3-plan classification:
# observed-mechanism — passes the mechanism test; kept as-is pending goalpost review
# wealth-proxy — fails the mechanism test; slated for removal or threshold-transform
# imputed-floor — data source not wired; producing only the imputed midpoint
# regional-only — data source covers <50% of scorable countries
# dead-signal — saturated or compressed; signal collapsed across the ranking
# ECONOMIC DOMAIN (weight 0.17) -------------------------------------------
- indicator: govRevenuePct
dimension: macroFiscal
domain: economic
weight: 0.50
direction: higher-better
source: IMF
seriesId: GGR_G01_GDP_PT
seriesUrl: https://www.imf.org/external/datamapper/GGR_G01_GDP_PT@FM/
coveragePct: 0.90
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: Government revenue % GDP is the policy-response headroom a state can deploy during a fiscal shock; higher = more ability to absorb.
constructStatus: observed-mechanism
reviewNotes: Goalpost 5-45 is probably too wide at the top; Nordic revenue/GDP ≥ 50% saturates. Review in PR 4 goalpost pass.
- indicator: debtGrowthRate
dimension: macroFiscal
domain: economic
weight: 0.20
direction: lower-better
source: National debt databases (IMF + WB cross-source)
seriesId: TBD
seriesUrl: TBD
coveragePct: 0.80
lastObservedYear: 2024
license: Mixed (per-source)
mechanismTestRationale: Rising debt growth indicates deteriorating fiscal trajectory and reduced capacity to finance a shock response.
constructStatus: observed-mechanism
- indicator: currentAccountPct
dimension: macroFiscal
domain: economic
weight: 0.30
direction: higher-better
source: IMF
seriesId: BCA_NGDPD
seriesUrl: https://www.imf.org/external/datamapper/BCA_NGDPD@WEO/
coveragePct: 0.85
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: Current account surplus indicates external-payments resilience; deficit indicates vulnerability to external-finance shocks.
constructStatus: observed-mechanism
- indicator: fxVolatility
dimension: currencyExternal
domain: economic
weight: 0.60
direction: lower-better
source: BIS Data Portal
seriesId: Broad Effective Exchange Rate
seriesUrl: https://data.bis.org/topics/EER
coveragePct: 0.30 # BIS covers 64 economies
lastObservedYear: 2024
license: CC-BY
mechanismTestRationale: FX volatility measures monetary-shock transmission risk.
constructStatus: regional-only
reviewNotes: BIS EER covers only 64 economies. Replace with FR.INR.RINR (real interest rate) or IMF inflation volatility in PR 3.
- indicator: fxDeviation
dimension: currencyExternal
domain: economic
weight: 0.25
direction: lower-better
source: BIS Data Portal
seriesId: EER deviation from equilibrium
seriesUrl: https://data.bis.org/topics/EER
coveragePct: 0.30
lastObservedYear: 2024
license: CC-BY
mechanismTestRationale: EER deviation from equilibrium proxies mis-aligned exchange rates that create abrupt-correction risk.
constructStatus: regional-only
reviewNotes: Same 64-economy limitation. Retire in PR 3.
- indicator: fxReservesAdequacy
dimension: currencyExternal
domain: economic
weight: 0.15
direction: higher-better
source: World Bank
seriesId: FI.RES.TOTL.MO
seriesUrl: https://data.worldbank.org/indicator/FI.RES.TOTL.MO
coveragePct: 0.85
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Reserves in months of imports directly measures immediate external-finance cushion.
constructStatus: observed-mechanism
reviewNotes: Also enters reserveAdequacy at 1.0 weight. Double-counting risk across dimensions; review in PR 2.
- indicator: sanctionCount
dimension: tradeSanctions
domain: economic
weight: 0.45
direction: lower-better
source: OFAC
seriesId: Consolidated Sanctions List (count per country)
seriesUrl: https://sanctionslist.ofac.treas.gov/
coveragePct: 1.00
lastObservedYear: 2026
license: US Government public domain
mechanismTestRationale: Active sanctions restrict trade/finance channels; higher count = more channels restricted.
constructStatus: observed-mechanism
reviewNotes: OFAC-only. PR 4 adds EU/UK/CN sanctions for directional completeness.
- indicator: tradeRestrictions
dimension: tradeSanctions
domain: economic
weight: 0.15
direction: lower-better
source: WTO
seriesId: Trade Monitoring Database
seriesUrl: https://www.wto.org/english/tratop_e/tpr_e/trade_monitoring_e.htm
coveragePct: 0.75
lastObservedYear: 2025
license: Open
mechanismTestRationale: Active trade restrictions (in-force, weighted 3×) directly measure market-access loss.
constructStatus: observed-mechanism
- indicator: tradeBarriers
dimension: tradeSanctions
domain: economic
weight: 0.15
direction: lower-better
source: WTO
seriesId: Trade Barriers Notifications
seriesUrl: https://tradebarriers.wto.org/
coveragePct: 0.70
lastObservedYear: 2025
license: Open
mechanismTestRationale: Notified trade barriers (not yet in force) indicate near-term market-access risk.
constructStatus: observed-mechanism
- indicator: appliedTariffRate
dimension: tradeSanctions
domain: economic
weight: 0.25
direction: lower-better
source: World Bank / WITS
seriesId: TM.TAX.MRCH.WM.AR.ZS
seriesUrl: https://data.worldbank.org/indicator/TM.TAX.MRCH.WM.AR.ZS
coveragePct: 0.90
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Applied tariff rates measure cost of trade restriction on imports.
constructStatus: observed-mechanism
# INFRASTRUCTURE DOMAIN (weight 0.15) -------------------------------------
- indicator: cyberThreats
dimension: cyberDigital
domain: infrastructure
weight: 0.45
direction: lower-better
source: Cyber threat feeds (mixed Western-origin)
seriesId: severity-weighted count (critical 3×, high 2×, medium 1×, low 0.5×)
seriesUrl: Internal seed
coveragePct: 0.70
lastObservedYear: 2026
license: Proprietary feeds (aggregated)
mechanismTestRationale: Severity-weighted cyber threat count directly measures ongoing cyber-attack pressure on national digital infrastructure.
constructStatus: observed-mechanism
reviewNotes: Western-feed bias; non-English cyber activity under-represented. PR 4 §4.8 tracks this.
- indicator: internetOutages
dimension: cyberDigital
domain: infrastructure
weight: 0.35
direction: lower-better
source: Cloudflare Radar + internal monitoring
seriesId: Outage penalty (total 4×, major 2×, partial 1×)
seriesUrl: https://radar.cloudflare.com/
coveragePct: 0.95
lastObservedYear: 2026
license: CC-BY-4.0
mechanismTestRationale: Internet outages directly measure digital-infrastructure availability under current stress.
constructStatus: observed-mechanism
- indicator: gpsJamming
dimension: cyberDigital
domain: infrastructure
weight: 0.20
direction: lower-better
source: GPSJam
seriesId: Hex penalty (high 3×, medium 1×)
seriesUrl: https://gpsjam.org/
coveragePct: 0.95
lastObservedYear: 2026
license: Open data
mechanismTestRationale: GPS jamming intensity measures electronic-warfare / navigation-disruption exposure.
constructStatus: observed-mechanism
- indicator: logisticsPerformanceIndex
dimension: logisticsSupply
domain: infrastructure
weight: TBD
direction: higher-better
source: World Bank LPI
seriesId: LP.LPI.OVRL.XQ
seriesUrl: https://lpi.worldbank.org/
coveragePct: 0.85
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Logistics Performance Index measures functional capacity to move goods; directly shocks during supply-chain disruptions.
constructStatus: observed-mechanism
reviewNotes: Goalpost anchors OECD-centric; PR 4 review.
- indicator: infrastructureSubcomponents
dimension: infrastructure
domain: infrastructure
weight: TBD
direction: higher-better
source: World Bank + WEF Global Competitiveness
seriesId: Composite
seriesUrl: TBD
coveragePct: 0.80
lastObservedYear: 2024
license: Mixed
mechanismTestRationale: Physical infrastructure quality is the baseline capacity for delivering services during normal and crisis periods.
constructStatus: observed-mechanism
# ENERGY DOMAIN (weight 0.11) --------------------------------------------
- indicator: dependency
dimension: energy
domain: energy
weight: 0.25
direction: lower-better
source: IEA (via static seed)
seriesId: Energy import dependency (%)
seriesUrl: https://www.iea.org/data-and-statistics/data-browser
coveragePct: 0.50 # IEA detail covers OECD + major non-OECD
lastObservedYear: 2023
license: Proprietary-with-fair-use
mechanismTestRationale: Share of energy consumption that is imported; direct supply-shock exposure.
constructStatus: observed-mechanism
reviewNotes: PR 1 §3.2 replaces with World Bank EG.IMP.CONS.ZS (better coverage) as part of importedFossilDependence composite.
- indicator: gasShare
dimension: energy
domain: energy
weight: 0.12
direction: lower-better
source: IEA World Energy Balances via static seed
seriesId: Natural gas share of primary energy
seriesUrl: https://www.iea.org/data-and-statistics/data-browser
coveragePct: 0.85
lastObservedYear: 2023
license: Proprietary-with-fair-use
mechanismTestRationale: CURRENT SCORER applies this as a vulnerability (lower-better) but it CONFLATES fossil-dominance with fossil-import-dependence. Domestic gas is a resilience asset, not a vulnerability.
constructStatus: wealth-proxy
reviewNotes: PR 1 §3.2 removes as standalone input; folds into importedFossilDependence under power-system framing.
- indicator: coalShare
dimension: energy
domain: energy
weight: 0.08
direction: lower-better
source: IEA World Energy Balances via static seed
seriesId: Coal share of primary energy
seriesUrl: https://www.iea.org/data-and-statistics/data-browser
coveragePct: 0.85
lastObservedYear: 2023
license: Proprietary-with-fair-use
mechanismTestRationale: Same concern as gasShare — penalty is climate-frame, not resilience-frame. Fails mechanism test under absolute-resilience contract.
constructStatus: wealth-proxy
reviewNotes: PR 1 §3.2 removes.
- indicator: renewShare
dimension: energy
domain: energy
weight: 0.05
direction: higher-better
source: IEA / World Bank
seriesId: EG.ELC.RNEW.ZS
seriesUrl: https://data.worldbank.org/indicator/EG.ELC.RNEW.ZS
coveragePct: 0.85
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Share of electricity from renewables, proxies low-carbon-firm-generation capacity (for hydro/geothermal) and diversity-of-supply (for wind/solar).
constructStatus: observed-mechanism
reviewNotes: PR 1 §3.3 collapses with nuclearShare (currently missing) into one lowCarbonGenerationShare indicator.
- indicator: storageStress
dimension: energy
domain: energy
weight: 0.10
direction: lower-better
source: GIE AGSI+
seriesId: EU gas storage fill % (per country)
seriesUrl: https://agsi.gie.eu/
coveragePct: 0.15 # EU + UK + a handful
lastObservedYear: 2026
license: Open
mechanismTestRationale: EU gas storage fill directly measures winter-heating-shock buffer. European-only platform.
constructStatus: regional-only
reviewNotes: PR 1 §3.5 renames to euGasStorageStress and scopes to EU-only (weight 0 for non-EU).
- indicator: exposedEnergyStress
dimension: energy
domain: energy
weight: 0.10
direction: composite
source: Internal composite (energy-price-stress × import-exposure)
seriesId: Derived
seriesUrl: Internal seed
coveragePct: 0.70
lastObservedYear: 2026
license: Internal
mechanismTestRationale: Combines energy-price shocks with import-exposure to measure price-shock transmission.
constructStatus: observed-mechanism
reviewNotes: PR 1 may simplify given importedFossilDependence covers import-exposure directly.
- indicator: electricityConsumption
dimension: energy
domain: energy
weight: 0.30
direction: higher-better
source: World Bank
seriesId: EG.USE.ELEC.KH.PC
seriesUrl: https://data.worldbank.org/indicator/EG.USE.ELEC.KH.PC
coveragePct: 0.90
lastObservedYear: 2022
license: CC-BY-4.0
mechanismTestRationale: FAILS the mechanism test. Per-capita electricity consumption tracks GDP per capita; it is a level-of-load measure not a resilience mechanism. IEA energy-security framing treats EFFICIENCY (lower load for same output) as resilience, which this indicator inversely rewards.
constructStatus: wealth-proxy
reviewNotes: PR 1 §3.1 removes. Replaced with powerLossesPct (EG.ELC.LOSS.ZS), reserveMarginPct (IEA), and accessToElectricityPct (EG.ELC.ACCS.ZS) moved to infrastructure domain.
# SOCIAL-GOVERNANCE DOMAIN (weight 0.19) ---------------------------------
- indicator: wgiComposite
dimension: governanceInstitutional
domain: social-governance
weight: 1.0
direction: higher-better
source: World Bank WGI
seriesId: Voice/Accountability, Political Stability, Government Effectiveness, Regulatory Quality, Rule of Law, Control of Corruption
seriesUrl: https://info.worldbank.org/governance/wgi/
coveragePct: 0.98
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: WGI subscores measure state capacity to design and enforce policy response to shocks. Passes the mechanism test conditionally — the composite is a direct policy-response-capacity signal.
constructStatus: observed-mechanism
reviewNotes: Weights review in PR 4. Individual WGI subscores may need separate weighting vs equal-blend.
- indicator: gpiScore
dimension: socialCohesion
domain: social-governance
weight: 0.40 # approximate
direction: lower-better
source: Institute for Economics and Peace
seriesId: Global Peace Index
seriesUrl: https://www.visionofhumanity.org/
coveragePct: 0.75
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: GPI measures internal conflict, militarization, and external conflict intensity — direct social-cohesion-shock exposure.
constructStatus: observed-mechanism
reviewNotes: Known Western-democracy bias in GPI methodology; PR 4 review.
- indicator: displacementMetric
dimension: socialCohesion
domain: social-governance
weight: 0.30 # approximate
direction: lower-better
source: UNHCR
seriesId: totalDisplaced
seriesUrl: https://data.unhcr.org/
coveragePct: 0.95
lastObservedYear: 2025
license: Open
mechanismTestRationale: Total displaced persons directly measures ongoing forced-migration pressure. BIAS: currently blends origin + host; penalizes Jordan/Turkey/Germany for HOSTING.
constructStatus: wealth-proxy # classified as biased — bias label
reviewNotes: PR 4 §4.2 splits origin (negative signal) from host (mixed signal).
- indicator: unrestMetric
dimension: socialCohesion
domain: social-governance
weight: 0.30 # approximate
direction: lower-better
source: Internal unrest seed (cross-source signals + UCDP)
seriesId: unrestCount + sqrt(fatalities)
seriesUrl: Internal seed
coveragePct: 0.85
lastObservedYear: 2026
license: Mixed
mechanismTestRationale: Active unrest events measure current social-cohesion stress.
constructStatus: observed-mechanism
- indicator: borderSecuritySubs
dimension: borderSecurity
domain: social-governance
weight: TBD
direction: composite
source: Composite (UNHCR displacement + UCDP conflict + governance)
seriesId: Derived
seriesUrl: Internal seed
coveragePct: 0.80
lastObservedYear: 2026
license: Mixed
mechanismTestRationale: Border-security composite captures cross-border shock transmission exposure.
constructStatus: observed-mechanism
reviewNotes: Inherits displacement host-vs-sending bias from socialCohesion. PR 4 fix.
- indicator: rsfPressFreedom
dimension: informationCognitive
domain: social-governance
weight: TBD
direction: higher-better
source: Reporters Sans Frontieres
seriesId: Press Freedom Index
seriesUrl: https://rsf.org/en/index
coveragePct: 0.85
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: Press freedom proxies quality of information-shock response and independent verification capacity.
constructStatus: observed-mechanism
- indicator: languageNormalizedSocialVelocity
dimension: informationCognitive
domain: social-governance
weight: TBD
direction: composite
source: Reddit + cross-source + internal language-coverage-weighting
seriesId: Internal
seriesUrl: Internal seed
coveragePct: 0.95
lastObservedYear: 2026
license: Mixed
mechanismTestRationale: Language-normalized social-information velocity measures information-shock propagation speed adjusted for source-density bias.
constructStatus: observed-mechanism
# HEALTH-FOOD DOMAIN (weight 0.13) ---------------------------------------
- indicator: whoHealthExpenditure
dimension: healthPublicService
domain: health-food
weight: TBD
direction: higher-better
source: WHO Global Health Observatory
seriesId: Current health expenditure per capita, PPP
seriesUrl: https://www.who.int/data/gho
coveragePct: 0.95
lastObservedYear: 2022
license: CC-BY-4.0
mechanismTestRationale: Health expenditure per capita proxies health-system capacity. FAILS the strict mechanism test — it measures SPEND, not CAPACITY. Should be replaced with surge-capacity / bed-density / ICU-density threshold signal.
constructStatus: wealth-proxy
reviewNotes: PR 4 §4.9 replacement.
- indicator: ipcPhase
dimension: foodWater
domain: health-food
weight: 0.15
direction: lower-better
source: FAO IPC (Integrated Food Security Phase Classification)
seriesId: IPC Phase (1-5)
seriesUrl: https://www.ipcinfo.org/
coveragePct: 0.40 # IPC covers acutely-affected countries
lastObservedYear: 2025
license: Open
mechanismTestRationale: IPC phase directly measures current food-security-crisis severity.
constructStatus: observed-mechanism
reviewNotes: Coverage is inherently partial — IPC only tracks countries with current/imminent food crises. Imputed to a resilient-default for non-tracked countries.
- indicator: aquastatWaterStress
dimension: foodWater
domain: health-food
weight: 0.25
direction: lower-better
source: FAO AQUASTAT
seriesId: Water stress (withdrawal / renewable resources)
seriesUrl: https://www.fao.org/aquastat/
coveragePct: 0.85
lastObservedYear: 2020
license: Open
mechanismTestRationale: Water stress directly measures water-supply-shock exposure.
constructStatus: observed-mechanism
- indicator: aquastatWaterAvailability
dimension: foodWater
domain: health-food
weight: 0.15
direction: higher-better
source: FAO AQUASTAT
seriesId: Water availability (m³/capita)
seriesUrl: https://www.fao.org/aquastat/
coveragePct: 0.85
lastObservedYear: 2020
license: Open
mechanismTestRationale: Water availability per capita proxies baseline water-security-shock buffer.
constructStatus: observed-mechanism
# RECOVERY DOMAIN (weight 0.25) ------------------------------------------
- indicator: recoveryGovRevenue
dimension: fiscalSpace
domain: recovery
weight: 0.40
direction: higher-better
source: IMF
seriesId: GGR_G01_GDP_PT
seriesUrl: https://www.imf.org/external/datamapper/GGR_G01_GDP_PT@FM/
coveragePct: 0.90
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: Government revenue % GDP for recovery scenarios — policy-response fiscal headroom.
constructStatus: observed-mechanism
reviewNotes: Duplicate with macroFiscal.govRevenuePct. PR 4 may de-duplicate.
- indicator: recoveryFiscalBalance
dimension: fiscalSpace
domain: recovery
weight: 0.30
direction: higher-better
source: IMF
seriesId: GGXCNL_G01_GDP_PT
seriesUrl: https://www.imf.org/external/datamapper/GGXCNL_G01_GDP_PT@FM/
coveragePct: 0.85
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: General government net lending/borrowing as % of GDP — direct fiscal-response-capacity signal.
constructStatus: observed-mechanism
- indicator: recoveryDebtToGdp
dimension: fiscalSpace
domain: recovery
weight: 0.30
direction: lower-better
source: IMF
seriesId: GGXWDG_NGDP_PT
seriesUrl: https://www.imf.org/external/datamapper/GGXWDG_NGDP_PT@FM/
coveragePct: 0.90
lastObservedYear: 2024
license: Proprietary-with-fair-use
mechanismTestRationale: General government gross debt to GDP — fiscal-stress cushion.
constructStatus: observed-mechanism
reviewNotes: Goalpost 0-150 is too linear; Japan at 260% (mostly domestic, yen-denominated) scores 0 despite weak real fiscal-stress risk. PR 4 §4.4 adds holder-composition modifier.
- indicator: recoveryReserveMonths
dimension: reserveAdequacy
domain: recovery
weight: 1.00
direction: higher-better
source: World Bank
seriesId: FI.RES.TOTL.MO
seriesUrl: https://data.worldbank.org/indicator/FI.RES.TOTL.MO
coveragePct: 0.85
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Central-bank reserves in months of imports — immediate external-liquidity cushion.
constructStatus: observed-mechanism
reviewNotes: PR 2 §3.4 renames to liquidReserveAdequacy; new dimension sovereignFiscalBuffer added.
- indicator: recoveryDebtToReserves
dimension: externalDebtCoverage
domain: recovery
weight: 1.00
direction: lower-better
source: World Bank
seriesId: DT.DOD.DSTC.CD / FI.RES.TOTL.CD
seriesUrl: https://data.worldbank.org/indicator/DT.DOD.DSTC.CD
coveragePct: 0.75
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: Short-term external debt to reserves ratio — rollover-shock exposure. Goalpost anchored on Greenspan-Guidotti (ratio≥1 = reserve inadequacy).
constructStatus: observed-mechanism
reviewNotes: PR 3 §3.5 point 3 re-goalposted from (0..5) to (0..2). Old goalpost saturated at 100 across the 9-country probe including stressed states; new anchor maps ratio=1.0 to score 50 and ratio=2.0 to score 0.
- indicator: recoveryImportHhi
dimension: importConcentration
domain: recovery
weight: 1.00
direction: lower-better
source: UN Comtrade
seriesId: HS2 bilateral Herfindahl-Hirschman Index
seriesUrl: https://comtrade.un.org/
coveragePct: 0.70
lastObservedYear: 2023
license: Open
mechanismTestRationale: Import-partner concentration (HHI) — supplier-shock exposure.
constructStatus: observed-mechanism
reviewNotes: Coverage gap for UAE and small-island states; PR 1+ audit.
- indicator: recoveryWgiContinuity
dimension: stateContinuity
domain: recovery
weight: 0.50
direction: higher-better
source: World Bank WGI
seriesId: Mean of WGI subscores
seriesUrl: https://info.worldbank.org/governance/wgi/
coveragePct: 0.98
lastObservedYear: 2023
license: CC-BY-4.0
mechanismTestRationale: WGI composite as state-continuity proxy — institutional durability through shocks.
constructStatus: observed-mechanism
reviewNotes: Duplicate with governanceInstitutional.wgiComposite. PR 4 de-duplicate.
- indicator: recoveryConflictPressure
dimension: stateContinuity
domain: recovery
weight: 0.30
direction: lower-better
source: UCDP
seriesId: Armed conflict events / fatalities
seriesUrl: https://ucdp.uu.se/
coveragePct: 0.95
lastObservedYear: 2026
license: Open
mechanismTestRationale: UCDP conflict intensity — direct state-continuity-shock metric.
constructStatus: observed-mechanism
- indicator: recoveryDisplacementVelocity
dimension: stateContinuity
domain: recovery
weight: 0.20
direction: lower-better
source: UNHCR
seriesId: Displacement as share of population
seriesUrl: https://data.unhcr.org/
coveragePct: 0.95
lastObservedYear: 2025
license: Open
mechanismTestRationale: Displacement velocity — population-scale state-continuity stress.
constructStatus: observed-mechanism
reviewNotes: Inherits host-vs-sending bias. PR 4 §4.2 fix.
- indicator: recoveryFuelStockDays
dimension: fuelStockDays
domain: recovery
weight: 1.00
direction: higher-better
source: IEA / EIA
seriesId: Days of fuel stock cover
seriesUrl: https://www.iea.org/data-and-statistics/data-tools/oil-stocks-of-iea-countries
coveragePct: 0.30 # imputed for every country
lastObservedYear: null
license: Proprietary
mechanismTestRationale: Days of fuel stock for import-shock coverage. IEA rules bind only net importers; net exporters get no observed value.
constructStatus: imputed-floor
reviewNotes: PR 3 §3.5 retires from core score (permanent). Enrichment-only if IEA/EIA connector ever wires.
# PENDING ADDITIONS FOR PR 1+ --------------------------------------------
# PR 1 additions (not yet in the scorer):
# - powerLossesPct → EG.ELC.LOSS.ZS (transmission+distribution losses, lower-better)
# - reserveMarginPct → IEA electricity balance (generation reserve margin, higher-better)
# - accessToElectricityPct → EG.ELC.ACCS.ZS (threshold/saturating, moved to infrastructure domain)
# - importedFossilDependence → EG.IMP.CONS.ZS × fossil-generation-share (Option B power-system framing)
# - lowCarbonGenerationShare → EG.ELC.NUCL.ZS + EG.ELC.RNEW.ZS (higher-better)
# PR 2 additions (not yet in the scorer):
# - liquidReserveAdequacy → FI.RES.TOTL.MO (rename of current reserveMonths)
# - sovereignFiscalBuffer → IFSWF + official disclosures × access × liquidity × transparency
# PR 3 replacements (not yet in the scorer):
# - realInterestRate → FR.INR.RINR (replaces currencyExternal for non-BIS countries)