Skip to content

feat(resilience): external index correlation validation (ND-GAIN, INFORM)#2824

Merged
koala73 merged 3 commits into
mainfrom
feat/resilience-external-correlation
Apr 8, 2026
Merged

feat(resilience): external index correlation validation (ND-GAIN, INFORM)#2824
koala73 merged 3 commits into
mainfrom
feat/resilience-external-correlation

Conversation

@koala73

@koala73 koala73 commented Apr 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds scripts/validate-resilience-correlation.mjs computing Spearman rank correlation (rho) between WorldMonitor resilience scores and two established benchmark indices: ND-GAIN Readiness and INFORM Risk Index
  • Embeds hardcoded reference data for 52 representative countries (fragile states through Nordics) so the script is reproducible without network dependencies
  • Reports top rank divergences to identify which countries WorldMonitor scores differently from established indices

Phase 4 gate: rho > 0.6 with at least 2 of 3 benchmark indices.

Implementation details

  • Reads cached resilience:score:v4:<ISO2> keys from Redis (same as seed-resilience-scores.mjs)
  • Spearman rho via rank conversion + Pearson on ranks, with proper tied-rank handling
  • INFORM scores are inverted (higher = more risk, opposite of resilience)
  • isMain guard prevents accidental execution when imported as a module
  • Exports core functions (spearmanRho, toRanks, pearson, computeCorrelation) for future test use

Test plan

  • Script parses without syntax errors (node --check)
  • Biome lint passes
  • Spearman implementation verified: perfect positive = 1.0, perfect negative = -1.0, tied ranks handled correctly
  • toRanks([10, 20, 20, 30]) produces [1, 2.5, 2.5, 4]
  • All 153 project tests pass
  • No production scoring code modified

…ORM)

Batch validation script computing Spearman rho between WorldMonitor
resilience scores and ND-GAIN readiness / INFORM risk indices for 50
representative countries. Identifies top divergences.

Phase 4 gate: rho > 0.6 with at least 2 benchmark indices.
@vercel

vercel Bot commented Apr 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Apr 8, 2026 0:09am

Request Review

@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds scripts/validate-resilience-correlation.mjs, a read-only diagnostic script that computes Spearman rank correlation between cached WorldMonitor resilience scores and two hardcoded reference indices (ND-GAIN Readiness and INFORM Risk) across 52 representative countries. No production scoring logic is touched. The Spearman implementation (toRanks with tied-rank averaging, pearson on ranks) is mathematically correct and the INFORM inversion via negation before ranking is sound.

Confidence Score: 5/5

Safe to merge — script is a read-only diagnostic tool with no changes to production scoring code.

All findings are P2: one piece of dead code (redisGetJson) and a gate-description mismatch (2-of-3 stated vs 2-of-2 implemented with a higher ND-GAIN threshold). Neither blocks correctness or data integrity. The statistical core is verified correct and no production paths are modified.

No files require special attention.

Vulnerabilities

No security concerns identified. The script is a read-only diagnostic tool that reads from Redis using existing credential helpers (getRedisCredentials) and does not write any data. All reference data is hardcoded; no user-supplied input is processed.

Important Files Changed

Filename Overview
scripts/validate-resilience-correlation.mjs New read-only diagnostic script: fetches Redis resilience scores and computes Spearman rho against hardcoded ND-GAIN and INFORM reference data. Core statistics (toRanks, pearson, spearmanRho) are implemented correctly with proper tied-rank handling. Minor issues: redisGetJson helper is defined but never called (dead code), and the gate check implements 2-of-2 (both must pass) rather than the stated 2-of-3 with the ND-GAIN threshold set at > 0.65 instead of the described uniform > 0.60.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[run] --> B[loadEnvFile / getRedisCredentials]
    B --> C[fetchWorldMonitorScores\nredisPipeline GET resilience:score:v4:ISO2]
    C --> D{wmScores.size < 20?}
    D -- yes --> E[process.exit 1]
    D -- no --> F[computeCorrelation\nvs ND-GAIN  invert=false]
    D -- no --> G[computeCorrelation\nvs INFORM  invert=true]
    F --> H[spearmanRho\ntoRanks + pearson on ranks]
    G --> H
    H --> I[Rank divergences\nsorted by delta]
    F --> J{ndgainPass\nrho > 0.65?}
    G --> K{informPass\nrho > 0.60?}
    J --> L[passingCount]
    K --> L
    L --> M{passingCount >= 2?}
    M -- YES --> N[GATE PASS — print report]
    M -- NO --> O[GATE FAIL — print report]
Loading

Reviews (1): Last reviewed commit: "feat(resilience): external index correla..." | Re-trigger Greptile

Comment on lines +30 to +39
async function redisGetJson(url, token, key) {
const resp = await fetch(`${url}/get/${encodeURIComponent(key)}`, {
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(5_000),
});
if (!resp.ok) return null;
const data = await resp.json();
if (!data?.result) return null;
try { return JSON.parse(data.result); } catch { return null; }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead code: redisGetJson is never called

redisGetJson is defined but not used anywhere in the file. All score fetching is done through redisPipeline inside fetchWorldMonitorScores (line 91). Since the exported API surface only exposes the statistical functions and computeCorrelation, this helper won't be reached from import callers either. Consider removing it to avoid confusion.

Comment on lines +162 to +170
const ndgainPass = ndgainResult.rho > 0.65;
const informPass = informResult.rho > 0.60;

console.log(`WorldMonitor vs ND-GAIN Readiness: rho = ${ndgainResult.rho.toFixed(3)} (n=${ndgainResult.n}, target > 0.65) ${ndgainPass ? 'PASS' : 'FAIL'}`);
console.log(`WorldMonitor vs INFORM Risk: rho = ${informResult.rho.toFixed(3)} (n=${informResult.n}, target > 0.60, inverted) ${informPass ? 'PASS' : 'FAIL'}`);

const passingCount = [ndgainPass, informPass].filter(Boolean).length;
const gatePass = passingCount >= 2;
console.log(`\nGATE CHECK: rho > 0.6 for at least 2 indices? ${gatePass ? 'YES' : 'NO'} (${passingCount}/2 passing)\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Gate check description vs. implementation mismatch

The PR description states the gate is "rho > 0.6 with at least 2 of 3 benchmark indices," but only two indices are implemented here. The gate condition passingCount >= 2 with exactly two indices is equivalent to requiring both to pass — which is stricter than "2 of 3." Additionally, the ND-GAIN threshold is > 0.65 (line 162) while the described gate threshold is > 0.60 uniformly; the INFORM threshold matches at > 0.60. If a third index is planned, the gate logic will need updating when it is added. As written, the GATE CHECK console message saying (${passingCount}/2 passing) correctly reports what is implemented, but the mismatch with the PR description may cause confusion during a phase review.

Was hardcoded to v4, but production is v5 after PR #2821 (baseline/stress
engine). Script would miss all cached scores and fail with "Too few
scores available".
@koala73
koala73 merged commit 5b0e112 into main Apr 8, 2026
10 checks passed
@koala73
koala73 deleted the feat/resilience-external-correlation branch April 8, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant