stats step 2: World Bank, IMF WEO, OWID, Energy-Charts, Rijksfinanciën, US Treasury, USAspending collectors + tables, timers
All checks were successful
Deploy production / deploy (push) Successful in 1m10s
All checks were successful
Deploy production / deploy (push) Successful in 1m10s
This commit is contained in:
128
scripts/stats/imf.ts
Normal file
128
scripts/stats/imf.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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'
|
||||
},
|
||||
{ id: 'GGR_NGDP', title: 'General government revenue', unit: '% of GDP', theme: 'budget' },
|
||||
{
|
||||
id: 'GGX_NGDP',
|
||||
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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user