feat: surface IMF fiscal indicators on country Economic Indicators card#3668
Conversation
…dicators card Adds three orphan-data rows to buildImfEconomicIndicators sourced from already-seeded IMF GGX_NGDP / GGR_NGDP / GGXONLB_NGDP series: - Public Spending (govExpenditurePct, flat trend — level is descriptive, Nordics at 50%+ are stable) - Gov Revenue (govRevenuePct, flat trend) - Primary Balance (primaryBalancePct, directional trend with IMF DSA noise-floor thresholds: > +1 → up, < -3 → down) Pattern mirrors the existing GDP Growth / Inflation / Unemployment / GDP-per-Capita rows in the same function. Renders on CountryDeepDivePanel only. Zero impact on resilience scoring or data pipeline. Resolves the "ingest-but-not-render" gap on fiscal series we already pay to fetch every month via seed-imf-macro.mjs.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Greptile SummarySurfaces three previously-orphaned IMF WEO fiscal fields — Public Spending, Gov Revenue, and Primary Balance — as new rows on the
Confidence Score: 4/5Safe to merge — purely additive UI rows reading already-seeded data with correct null/NaN guards and well-reasoned trend thresholds. The implementation is straightforward and the null/finite guards are applied consistently across all three new fields. The only gaps are missing boundary tests at primaryBalance === -3.0 and primaryBalance === 1.0, and the existing NaN-guard test not exercising the new fields — both are minor test coverage holes with no impact on production behavior. tests/imf-country-data.test.mts — boundary and NaN-guard coverage for the new Primary Balance thresholds. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[buildImfEconomicIndicators\nbundle: ImfCountryBundle] --> B{bundle.growth?\n.realGdpGrowthPct}
B -- not null & finite --> C[Real GDP Growth row\ntrend: >0.5 up / <-0.5 down / flat]
B -- null/NaN --> D{bundle.macro?\n.inflationPct}
C --> D
D -- not null & finite --> E[CPI Inflation row\ntrend: >5 down / <1 flat / else up]
D -- null/NaN --> F{bundle.labor?\n.unemploymentPct}
E --> F
F -- not null & finite --> G[Unemployment row\ntrend: >10 down / <5 up / flat]
F -- null/NaN --> H{bundle.growth?\n.gdpPerCapitaUsd}
G --> H
H -- not null & finite --> I[GDP / Capita row\ntrend: flat]
H -- null/NaN --> J{bundle.macro?\n.govExpenditurePct}
I --> J
J -- not null & finite --> K[Public Spending row\ntrend: flat - NEW]
J -- null/NaN --> L{bundle.macro?\n.govRevenuePct}
K --> L
L -- not null & finite --> M[Gov Revenue row\ntrend: flat - NEW]
L -- null/NaN --> N{bundle.macro?\n.primaryBalancePct}
M --> N
N -- not null & finite --> O{primaryBalance\nthreshold check}
N -- null/NaN --> P[return out]
O -- greater than +1 --> Q[trend: up - NEW]
O -- less than -3 --> R[trend: down - NEW]
O -- else --> S[trend: flat - NEW]
Q --> P
R --> P
S --> P
|
| it('marks severe primary deficit (<-3) with a downward trend', () => { | ||
| const rows = buildImfEconomicIndicators(bundle({ | ||
| macro: { | ||
| inflationPct: null, currentAccountPct: null, govRevenuePct: null, | ||
| cpiIndex: null, cpiEopPct: null, govExpenditurePct: null, primaryBalancePct: -4.0, | ||
| year: 2024, | ||
| }, | ||
| })); | ||
| const pb = rows.find(r => r.label === 'Primary Balance')!; | ||
| assert.equal(pb.value, '-4.0% GDP'); | ||
| assert.equal(pb.trend, 'down'); | ||
| }); |
There was a problem hiding this comment.
Boundary value at exactly
-3.0 is untested
The primaryBalance < -3 ? 'down' : 'flat' threshold means -3.0 exactly renders as flat, yet there is no test for it. The PR covers -1.0 (flat) and -4.0 (down) but skips the exact boundary, so a future refactor changing < to <= would go undetected. Adding a case for primaryBalancePct: -3.0 expecting flat would pin this boundary. Similarly, primaryBalancePct: 1.0 (upper boundary, should be flat) has no coverage.
There was a problem hiding this comment.
Addressed in commit b44e78601 — added boundary tests for primaryBalancePct: 1.0 and primaryBalancePct: -3.0 (both expected flat), plus extended the non-finite guard test to put NaN/Infinity into all 7 IMF fields (govExpenditurePct, govRevenuePct, primaryBalancePct, realGdpGrowthPct, gdpPerCapitaUsd, unemploymentPct, currentAccountPct) and assert 0 rows. Tests: 13 → 15.
…es 6-row slice Caller's Economic Indicators card caps at 6 rows (src/app/country-intel.ts:1288). Previous push order emitted Primary Balance as the 7th IMF row (after the 3 flat context rows), guaranteeing that the most informative directional fiscal signal was the first to be sliced off — exactly the opposite of intent. New order, mirroring "directional first, flat context last": Real GDP Growth → CPI Inflation → Unemployment → Primary Balance → GDP / Capita → Public Spending → Gov Revenue For a country with no stock/score rows, Primary Balance now lands at slot 4 and survives the slice cleanly. For full-coverage countries (stock + momentum + score + macro), the 6-row cap still drops some IMF rows, but the directional macro signals win priority over flat context. Adds an explicit ordering test that asserts the full 7-row sequence. Existing tests unaffected — they use `.find()` to locate rows and the one label-list assertion happens to not exercise primaryBalance.
…icIndicators
External reviewer caught that two cap sites gate visibility of the
Economic Indicators card, not just the country-intel.ts one I cited in
the prior commit:
- src/app/country-intel.ts:1288 — caps the combined producer output
- src/components/CountryDeepDivePanel.ts:2240 — re-caps after the
asynchronous Stock Index prepend in updateStock
For full-coverage countries (stock + score + 7 IMF rows), the two caps
combine to drop 3-4 IMF rows. The reorder (prior commit) ensures the
4 directional rows — growth / inflation / unemployment / primary
balance — win priority over the 3 flat context rows. Behavior is
correct; bumping either cap is a follow-up if more rows are desired.
Comment-only change — no behavioral or test impact.
…ge gaps Two pure test additions addressing PR #3668 Greptile review findings. No production code changed. 1. Boundary tests at the exact threshold values for Primary Balance: - primaryBalancePct: 1.0 → trend: flat (pins `> 1` inclusivity) - primaryBalancePct: -3.0 → trend: flat (pins `< -3` inclusivity) A future refactor flipping `>` to `>=` (or `<` to `<=`) now fails tests immediately. 2. Extended the non-finite guard test to cover all 7 IMF fields, not just inflationPct. Puts NaN/Infinity into govExpenditurePct, govRevenuePct, primaryBalancePct, realGdpGrowthPct, gdpPerCapitaUsd, unemploymentPct, and currentAccountPct, asserting all rows skip. Catches a future regression where the Number.isFinite guard is accidentally dropped on one of the new fiscal fields. Tests: 13 → 15 (all pass). tsc clean, biome lint clean.
Summary
CountryDeepDivePaneleconomic:imf:macro:v2— no new ingestionWhy
These series are pulled monthly by
seed-imf-macro.mjsbut no UI surface consumed them — they were orphan data in Redis. Plan #1 of a two-part fiscal-resilience initiative; Plan #2 (debtSustainabilityGapresilience indicator) is drafted inplans/add-debt-sustainability-gap-indicator.mdand will ship as a separate PR.Trend semantics
> +1%→ up,< -3%→ down, else flat. A 0.3% surplus showing a green arrow would be noise.Test plan
npx tsc --noEmitcleannode --import tsx --test tests/imf-country-data.test.mts→ 12/12 pass (was 7; added 5 new tests + updated 1 label-list assertion to expect Gov Revenue row)npx biome lint src/services/imf-country-data.ts tests/imf-country-data.test.mtscleanCountryDeepDivePanelfor France in the dev app, confirm three new rows appear with correctly formatted values (57.2% GDP,XX.X% GDP,±X.X% GDP) alongside the existing GDP Growth / CPI Inflation / Unemployment / GDP-per-Capita rowsPost-Deploy Monitoring & Validation
No additional operational monitoring required. Purely additive rows on the existing Economic Indicators card; reads existing seeded
economic:imf:macro:v2fields with null-skip guards. No new network calls, no new error paths, no new failure modes, no resilience-score impact.Rollback: revert the PR — Economic Indicators card returns to its previous 4 rows; no data cleanup, no cache invalidation needed.
Observable behavior: yes — three new rows visible on
CountryDeepDivePanel's Economic Indicators card for countries with IMF WEO coverage (≈190 countries). Demo evidence skipped for this PR; rows are testable locally with any IMF-WEO-covered country.