stats §8: Curaçao from ILOSTAT (SDMX), OWID, UN SDG 531, UN Comtrade 531 (cwintl + weekly timer), EU trade with CW (Eurostat ext_lt_maineu); CBS Curaçao LFS file pick fix; Curaçao detail panels on /stats/countries/CUW
All checks were successful
Deploy production / deploy (push) Successful in 1m10s

This commit is contained in:
2026-10-25 21:47:05 +00:00
parent a6b4f9931d
commit 031ee4baf2
10 changed files with 795 additions and 52 deletions

64
src/lib/stats/curacao.ts Normal file
View File

@@ -0,0 +1,64 @@
// §8 Curaçao detail series (CBS Curaçao, Eurostat, ILOSTAT, OWID, UN SDG, UN Comtrade), all with
// region CUW in stats.series. Loaded by /stats/countries/CUW next to the World Bank profile.
/** CBS Curaçao GDP by industry (ISIC 4 sections as published, mln ANG) with short labels. */
export const CW_INDUSTRIES: Record<string, string> = {
'a+b': 'Agriculture & mining',
c: 'Manufacturing',
'd+e': 'Utilities',
f: 'Construction',
g: 'Trade',
h: 'Transport',
i: 'Hotels & restaurants',
j: 'ICT',
k: 'Finance',
l: 'Real estate',
m: 'Professional services',
n: 'Admin services',
o: 'Public administration',
p: 'Education',
q: 'Health',
r: 'Arts & recreation',
's+t': 'Other services'
};
export const CW_NIGHT_MARKETS: Record<string, string> = {
europe: 'Europe',
north_america: 'North America',
south_america: 'South America',
caribbean: 'Caribbean',
others: 'Other'
};
export const EU_TRADE = (indic: string) => `eurostat:ext_lt_maineu:${indic}.TOTAL.CW.EU27_2020`;
export const CW_SERIES = [
'cw:cpi:index',
'cw:cpi:yoy',
'cw:gdp:gdp',
'cw:gdp:real_growth_pct',
...Object.keys(CW_INDUSTRIES).map((k) => `cw:gdp:${k}`),
'cw:gov:r.total',
'cw:gov:e.total',
'cw:gov:balance',
'cw:pop:jan1',
'cw:pop:census',
...['total', 'm', 'f', '15-24'].map((g) => `cw:lfs:unemployment_rate_pct:${g}`),
'cw:lfs:net_participation_pct:total',
'cw:tour:cruise_pax',
'cw:tour:daytrippers',
'cw:tour:nights',
...Object.keys(CW_NIGHT_MARKETS).map((m) => `cw:tour:nights:${m}`),
EU_TRADE('MIO_EXP_VAL'),
EU_TRADE('MIO_IMP_VAL'),
EU_TRADE('MIO_BAL_VAL'),
'ilostat:une_deap_sex_age_rt:total:15plus',
'owid:cw:population',
'owid:cw:co-emissions-per-capita',
'owid:cw:median-age',
'unsdg:eg_fec_rnew',
'comtrade:cw:imports:world',
'comtrade:cw:exports:world',
'comtrade:cw:imports:usa',
'comtrade:cw:imports:nld'
];

View File

@@ -1,6 +1,7 @@
import { error, redirect } from '@sveltejs/kit';
import { cached, loadSeries } from '#lib/server/stats.js';
import { isCountryPage, PROFILE_SERIES } from '#lib/stats/countries.js';
import { CW_SERIES } from '#lib/stats/curacao.js';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, url }) => {
@@ -16,7 +17,9 @@ export const load: PageServerLoad = async ({ params, url }) => {
`wb:NY.GDP.PCAP.PP.KD:${c}`,
`wb:SP.DYN.LE00.IN:${c}`,
`wb:FP.CPI.TOTL.ZG:${c}`
])
]),
// §8: Curaçao detail from CBS Curaçao and the international APIs (ids carry no code suffix).
...(code === 'CUW' ? CW_SERIES : [])
])
);
return { code, series };

View File

@@ -2,7 +2,15 @@
import { resolve } from '$app/paths';
import StatChart from '#lib/stats/StatChart.svelte';
import Kpi from '#lib/stats/Kpi.svelte';
import { change, latest, lineOption, type Line, type StatPoint } from '#lib/stats/chart.js';
import {
barRankOption,
change,
latest,
lineOption,
type Line,
type StatPoint
} from '#lib/stats/chart.js';
import { CW_INDUSTRIES, CW_NIGHT_MARKETS, EU_TRADE } from '#lib/stats/curacao.js';
import { compact, num, period } from '#lib/stats/format.js';
import { COUNTRY } from '#lib/stats/regions.js';
import { COUNTRY_COLOR, FLAG } from '#lib/stats/countries.js';
@@ -54,6 +62,23 @@
const kDebt = $derived(lastOf(pick('imf:GGXWDG_NGDP', 'wb:GC.DOD.TOTL.GD.ZS')));
const popDelta = $derived(change(pts('wb:SP.POP.TOTL'), 10)?.pct ?? null);
const imfCovered = $derived(has('imf:NGDP_RPCH'));
// §8 Curaçao detail series: stored without the `:CUW` suffix.
const cw = (id: string): StatPoint[] => s[id]?.points ?? [];
const isCw = $derived(c === 'CUW' && cw('cw:cpi:index').length > 0);
const cwGdpYear = $derived(latest(cw('cw:gdp:gdp'))?.[0]);
const cwIndustries = $derived(
Object.entries(CW_INDUSTRIES)
.map(([k, label]) => ({
label,
value: cw(`cw:gdp:${k}`).find(([d]) => d === cwGdpYear)?.[1] ?? NaN
}))
.filter((r) => Number.isFinite(r.value))
);
const cwCpi = $derived(latest(cw('cw:cpi:yoy')));
const cwPop = $derived(latest(cw('cw:pop:jan1')));
const cwUnemp = $derived(latest(cw('cw:lfs:unemployment_rate_pct:total')));
const cwGrowth = $derived(latest(cw('cw:gdp:real_growth_pct')));
</script>
<svelte:head>
@@ -73,9 +98,11 @@
<p>
{#if c === 'CUW'}
Curaçao has been a country within the Kingdom of the Netherlands since 2010, with about
150,000 inhabitants. The IMF's World Economic Outlook doesn't cover it, so the headline
numbers here come from the World Bank; detailed CBS Curaçao and central bank series are on
their way.
158,000 inhabitants. The IMF's World Economic Outlook doesn't cover it, so the headline
numbers come from the World Bank; the detail further down comes from CBS Curaçao (the island's
statistics office), Eurostat, ILOSTAT, Our World in Data and the UN. Amounts in guilders (ANG,
pegged at 1.79 to the US dollar). The central bank's balance-of-payments tables can't be
fetched automatically, so they aren't here yet.
{:else}
{name} at a glance, from the World Bank's development indicators and the IMF World Economic Outlook{imfCovered
? ' (dashed lines are IMF forecasts)'
@@ -365,33 +392,35 @@
<h2 class="stats-section-title">Government and energy</h2>
<div class="stats-grid">
<StatChart
title="Government finances"
subtitle={has('imf:GGXWDG_NGDP')
? '% of GDP · IMF (general government)'
: '% of GDP · World Bank (central government)'}
sources={has('imf:GGXWDG_NGDP') ? ['imf'] : ['worldbank']}
attributions={a}
option={(t) =>
lineOption(
t,
has('imf:GGXWDG_NGDP')
? [
...imfLines('imf:GGXWDG_NGDP', 'Debt', '#e11d48'),
...imfLines('imf:GGR_G01_GDP_PT', 'Revenue', '#10b981'),
...imfLines('imf:G_X_G01_GDP_PT', 'Spending', '#3b82f6')
]
: [
{ name: 'Debt', points: pts('wb:GC.DOD.TOTL.GD.ZS'), color: '#e11d48' },
{ name: 'Revenue', points: pts('wb:GC.REV.XGRT.GD.ZS'), color: '#10b981' },
{ name: 'Expense', points: pts('wb:GC.XPN.TOTL.GD.ZS'), color: '#3b82f6' }
],
{ unit: '% GDP', decimals: 1 }
)}
note={!has('imf:GGXWDG_NGDP') && !has('wb:GC.DOD.TOTL.GD.ZS')
? 'No government finance data for this country yet.'
: ''}
/>
{#if !isCw || has('imf:GGXWDG_NGDP') || has('wb:GC.DOD.TOTL.GD.ZS')}
<StatChart
title="Government finances"
subtitle={has('imf:GGXWDG_NGDP')
? '% of GDP · IMF (general government)'
: '% of GDP · World Bank (central government)'}
sources={has('imf:GGXWDG_NGDP') ? ['imf'] : ['worldbank']}
attributions={a}
option={(t) =>
lineOption(
t,
has('imf:GGXWDG_NGDP')
? [
...imfLines('imf:GGXWDG_NGDP', 'Debt', '#e11d48'),
...imfLines('imf:GGR_G01_GDP_PT', 'Revenue', '#10b981'),
...imfLines('imf:G_X_G01_GDP_PT', 'Spending', '#3b82f6')
]
: [
{ name: 'Debt', points: pts('wb:GC.DOD.TOTL.GD.ZS'), color: '#e11d48' },
{ name: 'Revenue', points: pts('wb:GC.REV.XGRT.GD.ZS'), color: '#10b981' },
{ name: 'Expense', points: pts('wb:GC.XPN.TOTL.GD.ZS'), color: '#3b82f6' }
],
{ unit: '% GDP', decimals: 1 }
)}
note={!has('imf:GGXWDG_NGDP') && !has('wb:GC.DOD.TOTL.GD.ZS')
? 'No government finance data for this country yet.'
: ''}
/>
{/if}
<StatChart
title="Energy"
subtitle="net energy imports (% of use) · renewable share of electricity"
@@ -409,6 +438,281 @@
/>
</div>
{#if isCw}
<h2 class="stats-section-title">Curaçao in detail</h2>
<div class="kpis">
<Kpi
label="Population, 1 Jan"
value={compact(cwPop?.[1])}
sub={`CBS Curaçao · ${period(cwPop?.[0], 'A')}`}
{color}
/>
<Kpi
label="Inflation"
value={num(cwCpi?.[1], 1)}
unit="%"
sub={`year on year · ${period(cwCpi?.[0], 'M')}`}
color="#e11d48"
/>
<Kpi
label="Real growth"
value={num(cwGrowth?.[1], 1)}
unit="%"
sub={period(cwGrowth?.[0], 'A')}
color="#3b82f6"
/>
<Kpi
label="Unemployment"
value={num(cwUnemp?.[1], 1)}
unit="%"
sub={`labour force survey · ${period(cwUnemp?.[0], 'A')}`}
color="#a855f7"
/>
</div>
<div class="stats-grid">
<StatChart
title="Consumer prices since 1970"
subtitle="CPI (2023 = 100) · inflation year on year (%) on the right"
sources={['cbscw']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{ name: 'CPI', points: cw('cw:cpi:index'), color: '#64748b', kind: 'area' },
{ name: 'Inflation', points: cw('cw:cpi:yoy'), color: '#e11d48', right: true }
],
{ unit: 'index', rightUnit: '%', decimals: 1, zoom: true }
)}
/>
<StatChart
title="GDP"
subtitle="nominal, million guilders · real growth (%) on the right"
sources={['cbscw']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{ name: 'GDP', points: cw('cw:gdp:gdp'), color, kind: 'bar' },
{
name: 'Real growth',
points: cw('cw:gdp:real_growth_pct'),
color: '#3b82f6',
right: true
}
],
{ unit: 'mln ANG', rightUnit: '%', decimals: 1 }
)}
/>
<StatChart
title="GDP by industry"
subtitle={`gross value added, million guilders · ${period(cwGdpYear, 'A')}`}
sources={['cbscw']}
attributions={a}
height="420px"
option={(t) => barRankOption(t, cwIndustries, { decimals: 0, unit: 'mln ANG', color })}
/>
<StatChart
title="Population since 1900"
subtitle="1 January (31 December before 1992) and census counts · UN estimate dashed"
sources={['cbscw', 'owid']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{ name: 'Population', points: cw('cw:pop:jan1'), color, kind: 'area' },
{ name: 'Census', points: cw('cw:pop:census'), color: '#0f172a', kind: 'bar' },
{
name: 'UN WPP (via OWID)',
points: cw('owid:cw:population'),
color: '#94a3b8',
dashed: true
}
],
{ decimals: 0, unit: 'people' }
)}
/>
<StatChart
title="Unemployment"
subtitle="% of the labour force · CBS labour force survey (no survey in 2021 and 2023), ILO dashed"
sources={['cbscw', 'ilostat']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{ name: 'Total', points: cw('cw:lfs:unemployment_rate_pct:total'), color: '#a855f7' },
{ name: 'Men', points: cw('cw:lfs:unemployment_rate_pct:m'), color: '#3b82f6' },
{ name: 'Women', points: cw('cw:lfs:unemployment_rate_pct:f'), color: '#ec4899' },
{
name: 'Aged 15–24',
points: cw('cw:lfs:unemployment_rate_pct:15-24'),
color: '#f97316'
},
{
name: 'ILO estimate (15+)',
points: cw('ilostat:une_deap_sex_age_rt:total:15plus'),
color: '#94a3b8',
dashed: true
}
],
{ unit: '%', decimals: 1 }
)}
/>
<StatChart
title="Tourism"
subtitle="cruise passengers and daytrippers · stay-over nights on the right"
sources={['cbscw']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{
name: 'Cruise passengers',
points: cw('cw:tour:cruise_pax'),
color: '#06b6d4',
kind: 'bar'
},
{
name: 'Daytrippers',
points: cw('cw:tour:daytrippers'),
color: '#eab308',
kind: 'bar'
},
{
name: 'Stay-over nights',
points: cw('cw:tour:nights'),
color: '#e11d48',
right: true
}
],
{ decimals: 0, rightUnit: 'nights' }
)}
/>
<StatChart
title="Stay-over nights by market"
subtitle="tourist nights by region of residence"
sources={['cbscw']}
attributions={a}
option={(t) =>
lineOption(
t,
Object.entries(CW_NIGHT_MARKETS).map(([k, label], i) => ({
name: label,
points: cw(`cw:tour:nights:${k}`),
color: ['#3b82f6', '#e11d48', '#10b981', '#eab308', '#94a3b8'][i],
kind: 'bar' as const,
stack: 'nights'
})),
{ decimals: 0, unit: 'nights' }
)}
/>
<StatChart
title="Government revenue and expenditure"
subtitle="million guilders · balance as bars (recent years provisional)"
sources={['cbscw']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{ name: 'Revenue', points: cw('cw:gov:r.total'), color: '#10b981' },
{ name: 'Expenditure', points: cw('cw:gov:e.total'), color: '#3b82f6' },
{ name: 'Balance', points: cw('cw:gov:balance'), color: '#e11d48', kind: 'bar' }
],
{ unit: 'mln ANG', decimals: 0 }
)}
/>
<StatChart
title="Trade with the EU"
subtitle="EU27 goods exports to and imports from Curaçao, million €"
sources={['eurostat']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{
name: 'EU exports to Curaçao',
points: cw(EU_TRADE('MIO_EXP_VAL')),
color: '#3b82f6'
},
{
name: 'EU imports from Curaçao',
points: cw(EU_TRADE('MIO_IMP_VAL')),
color: '#f97316'
},
{
name: 'EU balance',
points: cw(EU_TRADE('MIO_BAL_VAL')),
color: '#94a3b8',
kind: 'bar'
}
],
{ unit: 'mln €', decimals: 0 }
)}
/>
<StatChart
title="Goods trade (as reported by Curaçao)"
subtitle="US$ · only 2017–2020 are reported to the UN"
sources={['comtrade']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{
name: 'Imports',
points: cw('comtrade:cw:imports:world'),
color: '#e11d48',
kind: 'bar'
},
{
name: 'Exports',
points: cw('comtrade:cw:exports:world'),
color: '#10b981',
kind: 'bar'
},
{
name: 'Imports from the US',
points: cw('comtrade:cw:imports:usa'),
color: '#3b82f6'
},
{ name: 'Imports from NL', points: cw('comtrade:cw:imports:nld'), color: '#f97316' }
],
{ decimals: 0, unit: 'US$' }
)}
/>
<StatChart
title="Climate and energy"
subtitle="CO₂ per person (t) · renewable share of final energy (%) on the right"
sources={['owid', 'unsdg']}
attributions={a}
option={(t) =>
lineOption(
t,
[
{
name: 'CO₂ per person',
points: cw('owid:cw:co-emissions-per-capita'),
color: '#64748b'
},
{
name: 'Renewable share',
points: cw('unsdg:eg_fec_rnew'),
color: '#10b981',
right: true
}
],
{ unit: 't', rightUnit: '%', decimals: 1 }
)}
/>
</div>
{/if}
<style>
.back {
display: inline-flex;