Jaspers Hub

jaspers / imf

pluginOfficialReviewed★ 0↓ 2

IMF DataMapper Country Macro: Gets IMF DataMapper macro indicators, including GDP growth, inflation, debt and unemployment, by country and year, with IMF forecasts. No key needed.

3.4 KB
1import { defineSource } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3
4
5// The IMF's DataMapper, which is the World Economic Outlook and friends behind a plain JSON API.
6// No key. One indicator, many countries, a year at a time.
7
8const HOST = 'www.imf.org'
9const BASE = `https://${HOST}/external/datamapper/api/v1`
10
11async function get(ctx: { fetch: typeof fetch }, path: string): Promise<unknown> {
12 // The IMF refuses a request that does not say who it is.
13 const response = await ctx.fetch(`${BASE}/${path}`, {
14 headers: { 'User-Agent': 'Jaspers Terminal (https://jsprai.com)', Accept: 'application/json' },
15 })
16 if (!response.ok) throw new Error(`The IMF answered ${response.status}.`)
17 return response.json()
18}
19
20export const indicators = defineSource({
21 description: 'Every indicator the IMF DataMapper publishes, with its code and what it measures. Read this to find the code an indicator source needs.',
22 hosts: [HOST],
23 input: z.object({ contains: z.string().optional().describe('Only indicators whose name holds this, like inflation or debt.') }),
24 async run({ contains }, ctx) {
25 const body = (await get(ctx, 'indicators')) as { indicators: Record<string, { label?: string; unit?: string; source?: string }> }
26 const wanted = contains?.trim().toLowerCase()
27 const rows = Object.entries(body.indicators)
28 .map(([code, one]) => ({ code, label: one.label ?? code, unit: one.unit ?? null, source: one.source ?? null }))
29 .filter((one) => !wanted || one.label.toLowerCase().includes(wanted) || one.code.toLowerCase().includes(wanted))
30 .sort((a, b) => a.code.localeCompare(b.code))
31 return { rows }
32 },
33})
34
35export const indicator = defineSource({
36 description:
37 'One IMF indicator for one or more countries, by year. indicator is a DataMapper code like NGDP_RPCH (real GDP growth), PCPIPCH (inflation), GGXWDG_NGDP (government debt to GDP), or LUR (unemployment). Countries are ISO three letter codes. The IMF publishes forecasts as well as history, so later years are projections.',
38 hosts: [HOST],
39 input: z.object({
40 indicator: z.string().default('NGDP_RPCH'),
41 countries: z.array(z.string()).default(['USA']).describe('ISO three letter codes, like USA, DEU, CHN.'),
42 from: z.number().int().optional().describe('Earliest year.'),
43 }),
44 async run({ indicator: code, countries, from }, ctx) {
45 const wanted = (countries as string[]).map((one) => one.trim().toUpperCase()).filter(Boolean)
46 const body = (await get(ctx, `${encodeURIComponent(code.trim().toUpperCase())}/${wanted.join('/')}`)) as {
47 values?: Record<string, Record<string, Record<string, number>>>
48 }
49 const byCountry = body.values?.[code.trim().toUpperCase()]
50 if (!byCountry) throw new Error(`The IMF has no indicator called ${code}. Run the indicators source to find its code.`)
51 const rows: { country: string; year: number; value: number }[] = []
52 for (const [country, years] of Object.entries(byCountry)) {
53 // Asking for two countries answers with those two; asking for one answers with every country,
54 // so what was asked for is what is kept.
55 if (wanted.length > 0 && !wanted.includes(country)) continue
56 for (const [year, value] of Object.entries(years)) {
57 if (typeof value !== 'number') continue
58 if (from && Number(year) < from) continue
59 rows.push({ country, year: Number(year), value })
60 }
61 }
62 rows.sort((a, b) => a.year - b.year || a.country.localeCompare(b.country))
63 return { indicator: code, rows }
64 },
65})