Skip to content

Fix frontend CII source of truth for on-demand surfaces#4150

Merged
koala73 merged 4 commits into
mainfrom
codex/cii-frontend-source-of-truth
Jun 6, 2026
Merged

Fix frontend CII source of truth for on-demand surfaces#4150
koala73 merged 4 commits into
mainfrom
codex/cii-frontend-source-of-truth

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Jun 6, 2026

Summary

  • Route remaining on-demand CII consumers through cached/server scores before local fallback.
  • Add shared cached CII score accessors for country brief, story data, cross-module alerts, military posture, and hotspot escalation.
  • Align CountryIntelModal and StrategicRiskPanel color thresholds to the canonical CII bands.

Root Cause

Several frontend surfaces still recomputed or preferred local country-instability.ts scores after server-authoritative CII scores were available, so visible CII-derived values could diverge from the CII panel and choropleth.

Validation

  • npm_config_cache=/tmp/worldmonitor-npm-cache npx tsx --test tests/frontend-cii-source-of-truth.test.mts tests/frontend-cii-closeout.test.mts tests/cached-risk-scores.test.mts
  • npm run typecheck
  • Pre-push hook passed: typecheck, API typecheck, boundary/safe-html/rate-limit/premium-fetch checks, changed test files, edge function tests, version check

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jun 6, 2026 11:56am

Request Review

@koala73 koala73 marked this pull request as ready for review June 6, 2026 10:43
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR routes all on-demand CII consumers through server/cached scores before falling back to locally-computed values, and aligns the colour-band thresholds in CountryIntelModal and StrategicRiskPanel to the canonical 81/66/51 cutpoints used in cached-risk-scores.ts.

  • New shared accessors (getCachedCountryScore, getCachedCountryScoreValue, getCachedCountryScores) centralise the cache-to-CountryScore conversion and are wired into country-intel.ts, story-data.ts, cross-module-integration.ts, military-surge.ts, Map.ts, and DeckGLMap.ts.
  • StrategicRiskPanel now unconditionally awaits fetchCachedRiskScores on every refresh (circuit-breaker makes subsequent calls cheap), removing the previous shouldUseCachedScores condition that allowed local computation to win when signals were loaded.
  • checkCIIChanges in cross-module-integration.ts now feeds from getAuthoritativeCIIScores(), which can switch source mid-session (local → cached), leaving previousCIIScores populated with locally-computed values that may diverge significantly from backend values and trigger spurious CII-spike alerts on cold start.

Confidence Score: 3/5

Safe to merge if the source-switch alert race in checkCIIChanges is accepted or guarded before the next production deployment.

The threshold alignment and new accessor helpers are clean and well-tested. The concrete defect is in checkCIIChanges: the module-level previousCIIScores map is not reset when getAuthoritativeCIIScores() switches from locally-computed to backend-cached values, so a cold-start user who exits learning mode before the backend RPC completes will see a round of false CII-spike alerts as soon as the circuit-breaker warms up.

src/services/cross-module-integration.ts — specifically the interaction between getAuthoritativeCIIScores() and the previousCIIScores map in checkCIIChanges.

Important Files Changed

Filename Overview
src/services/cached-risk-scores.ts Adds three new accessor helpers that wrap the existing getCachedScores/toCountryScore pattern. Correct; getCachedCountryScore normalises to uppercase while callers' fallback paths do not.
src/services/cross-module-integration.ts Introduces getAuthoritativeCIIScores() and routes checkCIIChanges and calculateStrategicRiskOverview through it. The source-switch from local to cached scores can generate spurious CII-spike alerts because previousCIIScores is not reset when the source changes.
src/components/StrategicRiskPanel.ts Removes the shouldUseCachedScores guard so fetchCachedRiskScores is always awaited on every refresh(). Badge selection and applyCachedRiskOverview logic are correct; the isConnected guard after the await is preserved.
src/app/country-intel.ts Replaces the conditional hasIntelligenceSignalsLoaded guard with a one-liner getCachedCountryScore(code) ?? calculateCII().find(...). Logic is correct; falls through cleanly when no cached score is present.
src/services/story-data.ts Simplifies CII resolution to getCachedCountryScore first, then local fallback. Clean change with no logic regressions.
src/components/CountryIntelModal.ts Aligns scoreBar colour thresholds to the canonical CII bands (81/66/51), matching the server-side getScoreLevel in cached-risk-scores.ts.
src/components/DeckGLMap.ts Wires setCIIGetter to prefer cached score value with local fallback. Straightforward one-liner change.
src/components/Map.ts Same setCIIGetter update as DeckGLMap; correct and consistent with the sibling component.
src/services/military-surge.ts Routes theater posture CII boost through getCachedCountryScoreValue with local fallback. Existing CII thresholds (85/70) in posture logic are unchanged.
tests/frontend-cii-source-of-truth.test.mts Expands tests with esbuild-powered module isolation and two new behavioural cases for story-data. The makeScore helper uses an incomplete level expression covering only 'critical'/'high'.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CII Consumer\ncountry-intel / story-data\nMap / DeckGLMap / military-surge] --> B{getCachedCountryScore\nor getCachedCountryScoreValue}
    B -->|cache hit| C[Return backend/cached score]
    B -->|cache miss| D[calculateCII local fallback]

    E[checkCIIChanges\ncross-module-integration] --> F{getAuthoritativeCIIScores}
    F -->|getCachedCountryScores length > 0| G[Backend cached scores]
    F -->|empty cache| H[calculateCII local scores]
    G --> I[Compare vs previousCIIScores map]
    H --> I
    I -->|delta ge 10 and not learning| J[Emit CII spike alert]
    I --> K[Update previousCIIScores]

    L[StrategicRiskPanel.refresh] --> M[fetchCachedRiskScores\ncircuit-breaker]
    M -->|data available| N[applyCachedRiskOverview\nbadge = cached]
    M -->|null| O[Local overview\nbadge = live or unavailable]
Loading

Reviews (1): Last reviewed commit: "fix(frontend): use cached CII scores for..." | Re-trigger Greptile

Comment on lines +106 to +109
function getAuthoritativeCIIScores(): CountryScore[] {
const cachedScores = getCachedCountryScores();
return cachedScores.length > 0 ? cachedScores : calculateCII();
}
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.

P1 Spurious CII-spike alerts on cold-start source switch

checkCIIChanges() stores locally-computed CII scores in the module-level previousCIIScores map on the first few calls, before the backend circuit-breaker is warm. Once the backend RPC completes and getAuthoritativeCIIScores() begins returning cached scores, the delta between a locally-computed score (e.g. 5 during early ingestion) and a backend score (e.g. 80) easily exceeds the 10-point threshold, generating a false "CII spike" alert for every affected country in the same checkCIIChanges() call. This only happens on a cold start (no localStorage seed) or after cache expiry, and only when the app is outside learning mode at the moment the circuit-breaker first records a success. A simple guard would be to reset previousCIIScores (or initialise each entry with the new score rather than alerting) whenever the source flips from local to cached.

Comment thread tests/frontend-cii-source-of-truth.test.mts
Comment thread src/services/cached-risk-scores.ts
@koala73 koala73 merged commit 3e28787 into main Jun 6, 2026
17 checks passed
@koala73 koala73 deleted the codex/cii-frontend-source-of-truth branch June 6, 2026 12:15
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