All checks were successful
Deploy production / deploy (push) Successful in 1m10s
130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
// A4 IMF World Economic Outlook via DataMapper. The country filter in the path is ignored, so
|
|
// each call returns every country; keep the compared ones. Each WEO round is kept as a vintage
|
|
// (April / October) so forecasts can later be compared with outcomes.
|
|
import {
|
|
COMPARE_ISO3,
|
|
getJson,
|
|
insertChunks,
|
|
log,
|
|
run,
|
|
sql,
|
|
writeSeries,
|
|
type ObsRow,
|
|
type SeriesRow
|
|
} from './common';
|
|
|
|
export const IMF_INDICATORS: { id: string; title: string; unit: string; theme: string }[] = [
|
|
{ id: 'NGDP_RPCH', title: 'Real GDP growth', unit: '%', theme: 'countries' },
|
|
{ id: 'NGDPDPC', title: 'GDP per capita', unit: 'current USD', theme: 'countries' },
|
|
{
|
|
id: 'PPPPC',
|
|
title: 'GDP per capita, PPP',
|
|
unit: 'current international $',
|
|
theme: 'countries'
|
|
},
|
|
{ id: 'PCPIPCH', title: 'Inflation (average consumer prices)', unit: '%', theme: 'countries' },
|
|
{ id: 'LUR', title: 'Unemployment rate', unit: '%', theme: 'countries' },
|
|
{ id: 'GGXWDG_NGDP', title: 'General government gross debt', unit: '% of GDP', theme: 'budget' },
|
|
{
|
|
id: 'GGXCNL_NGDP',
|
|
title: 'General government net lending/borrowing',
|
|
unit: '% of GDP',
|
|
theme: 'budget'
|
|
},
|
|
// WEO revenue/spending (GGR_NGDP, GGX_NGDP) are not on DataMapper; the Fiscal Monitor ones are.
|
|
{ id: 'GGR_G01_GDP_PT', title: 'General government revenue', unit: '% of GDP', theme: 'budget' },
|
|
{
|
|
id: 'G_X_G01_GDP_PT',
|
|
title: 'General government total expenditure',
|
|
unit: '% of GDP',
|
|
theme: 'budget'
|
|
}
|
|
];
|
|
// IMF aggregate codes kept besides the countries.
|
|
const AGGREGATES: Record<string, string> = { EU: 'EU27', WEOWORLD: 'WLD' };
|
|
|
|
/** WEO round in force on a date: October and April releases. */
|
|
export function currentVintage(now = new Date()) {
|
|
const y = now.getUTCFullYear();
|
|
const m = now.getUTCMonth() + 1;
|
|
return m >= 10 ? `${y}-10` : m >= 4 ? `${y}-04` : `${y - 1}-10`;
|
|
}
|
|
|
|
export async function imf(opts: { only?: string; job: string }) {
|
|
const vintage = currentVintage();
|
|
const vintageYear = Number(vintage.slice(0, 4));
|
|
await run('imf', opts.job, async (note) => {
|
|
let total = 0;
|
|
for (const ind of IMF_INDICATORS) {
|
|
if (opts.only && !opts.only.split(',').includes(ind.id)) continue;
|
|
try {
|
|
const body = await getJson<{
|
|
values?: Record<string, Record<string, Record<string, number>>>;
|
|
}>(`https://www.imf.org/external/datamapper/api/v1/${ind.id}`);
|
|
const byCountry = body.values?.[ind.id] ?? {};
|
|
const rows: {
|
|
vintage: string;
|
|
indicator: string;
|
|
country_iso3: string;
|
|
year: number;
|
|
value: number;
|
|
is_forecast: boolean;
|
|
}[] = [];
|
|
for (const [code, years] of Object.entries(byCountry)) {
|
|
const region = COMPARE_ISO3.includes(code) ? code : AGGREGATES[code];
|
|
if (!region) continue;
|
|
for (const [y, v] of Object.entries(years))
|
|
if (v != null)
|
|
rows.push({
|
|
vintage,
|
|
indicator: ind.id,
|
|
country_iso3: region,
|
|
year: Number(y),
|
|
value: v,
|
|
is_forecast: Number(y) >= vintageYear
|
|
});
|
|
}
|
|
if (!rows.length) throw new Error('no rows');
|
|
await sql.begin(async (tx) => {
|
|
await tx`delete from stats.imf_weo where vintage = ${vintage} and indicator = ${ind.id}`;
|
|
await insertChunks(tx, 'imf_weo', rows);
|
|
});
|
|
const series: SeriesRow[] = [];
|
|
const obs: ObsRow[] = [];
|
|
for (const region of new Set(rows.map((r) => r.country_iso3))) {
|
|
const id = `imf:${ind.id}:${region}`;
|
|
series.push({
|
|
series_id: id,
|
|
source_id: 'imf',
|
|
source_code: `${ind.id}/${region}`,
|
|
title: ind.title,
|
|
unit: ind.unit,
|
|
freq: 'A',
|
|
region_code: region,
|
|
theme: ind.theme,
|
|
meta: {
|
|
vintage,
|
|
forecastFrom: vintageYear,
|
|
url: `https://www.imf.org/external/datamapper/${ind.id}`
|
|
}
|
|
});
|
|
for (const r of rows)
|
|
if (r.country_iso3 === region)
|
|
obs.push({
|
|
series_id: id,
|
|
period: `${r.year}-01-01`,
|
|
value: r.value,
|
|
status: r.is_forecast ? 'forecast' : null
|
|
});
|
|
}
|
|
await writeSeries(series, obs);
|
|
log(`imf ${ind.id}: ${rows.length} rows (vintage ${vintage})`);
|
|
total += rows.length;
|
|
} catch (error) {
|
|
note(`${ind.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|
|
return total;
|
|
});
|
|
}
|