diff --git a/src/lib/insee.ts b/src/lib/insee.ts
index ff39c4b..c81ce21 100644
--- a/src/lib/insee.ts
+++ b/src/lib/insee.ts
@@ -1,28 +1,49 @@
/**
* Client INSEE BDM (Banque de Données Macroéconomiques)
- * API SDMX publique — pas de clé requise pour les séries publiques.
- * Doc: https://api.insee.fr/catalogue/site/themes/wso2/subthemes/insee/pages/item-info.jag?name=BDM&version=V1&provider=INSEE
+ * Portail : https://portail-api.insee.fr — API publique, aucune clé requise.
+ * Format : SDMX-ML XML (l'API ne supporte pas JSON).
+ * Doc: https://api.insee.fr/series/BDM
*/
-const BASE = 'https://api.insee.fr/series/BDM/V1';
+const BASE = 'https://api.insee.fr/series/BDM/data/SERIES_BDM';
export interface INSEEValue {
value: number | null;
- period: string; // ex: "2024-Q3" ou "2024"
- unit?: string;
+ period: string; // ex: "2026-Q1" → on extrait l'année
+ year: string; // ex: "2026"
+ label?: string;
}
-interface SDMXObs {
- [period: string]: [number | null, ...unknown[]];
-}
+/**
+ * Parse la réponse SDMX-ML XML d'INSEE BDM.
+ * Extrait les observations
+ * et retourne la plus récente avec une valeur non nulle.
+ */
+function parseSDMXXML(xml: string): INSEEValue {
+ // Extraire toutes les observations
+ const obsRegex = //g;
+ const observations: { period: string; value: number }[] = [];
-interface SDMXSeries {
- observations: SDMXObs;
- attributes?: unknown[];
-}
+ for (const match of xml.matchAll(obsRegex)) {
+ const attrs = match[1];
+ const period = attrs.match(/TIME_PERIOD="([^"]+)"/)?.[1];
+ const valStr = attrs.match(/OBS_VALUE="([^"]+)"/)?.[1];
+ if (period && valStr) {
+ const value = parseFloat(valStr);
+ if (!isNaN(value)) {
+ observations.push({ period, value });
+ }
+ }
+ }
-interface SDMXDataset {
- series?: Record;
+ if (observations.length === 0) return { value: null, period: '', year: '' };
+
+ // La plus récente (elles sont triées chronologiquement dans la réponse)
+ const last = observations[observations.length - 1];
+ // Extraire l'année : "2026-Q1" → "2026", "2025-A" → "2025", "2025" → "2025"
+ const year = last.period.split('-')[0];
+
+ return { value: last.value, period: last.period, year };
}
/**
@@ -31,67 +52,43 @@ interface SDMXDataset {
*/
export async function fetchSerie(serieId: string): Promise {
try {
- const url = `${BASE}/data/SERIES_BDM/${serieId}?lastNObservations=4&format=sdmx-json`;
+ const url = `${BASE}/${serieId}?lastNObservations=4`;
const res = await fetch(url, {
headers: {
- Accept: 'application/json',
- // Pas besoin de token pour les séries publiques
+ // Pas de token — API publique
+ Accept: 'application/xml',
},
signal: AbortSignal.timeout(12_000),
});
if (!res.ok) {
console.warn(`[insee] ${serieId} → HTTP ${res.status}`);
- return { value: null, period: '' };
+ return { value: null, period: '', year: '' };
}
- const data = await res.json();
-
- // Parcourir la structure SDMX-JSON
- const datasets: SDMXDataset[] = data?.dataSets || [];
- const firstDataset = datasets[0];
- if (!firstDataset?.series) return { value: null, period: '' };
-
- const firstSeries = Object.values(firstDataset.series)[0] as SDMXSeries;
- if (!firstSeries?.observations) return { value: null, period: '' };
-
- // Trouver la période la plus récente
- const structure = data?.structure;
- const timeDimension = structure?.dimensions?.observation?.find(
- (d: { id: string }) => d.id === 'TIME_PERIOD'
- );
-
- const obsEntries = Object.entries(firstSeries.observations);
- if (obsEntries.length === 0) return { value: null, period: '' };
-
- // L'index SDMX-JSON → période via timeDimension.values
- const lastEntry = obsEntries[obsEntries.length - 1];
- const obsIdx = parseInt(lastEntry[0]);
- const period = timeDimension?.values?.[obsIdx]?.id || lastEntry[0];
- const value = (lastEntry[1] as unknown[])[0] as number | null;
-
- return { value, period };
+ const xml = await res.text();
+ return parseSDMXXML(xml);
} catch (e) {
console.warn(`[insee] ${serieId} → ${(e as Error).message}`);
- return { value: null, period: '' };
+ return { value: null, period: '', year: '' };
}
}
/**
* Séries INSEE courantes (IDs BDM)
- * Source: https://www.bdm.insee.fr/bdm2/choixCriteres?codeGroupe=CONSO_MENAGES
+ * Trouver les IDs sur : https://www.bdm.insee.fr
*/
export const SERIES_INSEE = {
// PIB
- PIB_VOLUME_T: '010565692', // PIB volume, variation trimestrielle
+ PIB_VOLUME_T: '010565692', // PIB volume, variation trimestrielle CVS
PIB_VOLUME_A: '010565695', // PIB volume, variation annuelle
// Inflation
IPC_ENSEMBLE: '001759970', // IPC ensemble, variation annuelle
- IPC_ALIMENTAIRE: '001759971', // IPC alimentation
+ IPC_ALIMENTAIRE: '001759971', // IPC alimentation, variation annuelle
// Emploi
- TAUX_CHOMAGE: '001688526', // Taux de chômage BIT France métro
+ TAUX_CHOMAGE: '001688526', // Taux de chômage BIT France métro (trimestriel)
TAUX_EMPLOI: '001688527', // Taux d'emploi 15-64 ans
EMPLOI_TOTAL: '001688535', // Emploi total (milliers)
@@ -100,6 +97,6 @@ export const SERIES_INSEE = {
DETTE_PUBLIQUE: '001639746', // Dette publique % PIB (Maastricht)
// Ménages
- POUVOIR_ACHAT: '010565799', // Pouvoir d'achat des ménages
+ POUVOIR_ACHAT: '010565799', // Pouvoir d'achat par unité de consommation
TAUX_EPARGNE: '001637643', // Taux d'épargne des ménages
} as const;
diff --git a/src/scripts/fetch-indicators.ts b/src/scripts/fetch-indicators.ts
index df31138..e8c5130 100644
--- a/src/scripts/fetch-indicators.ts
+++ b/src/scripts/fetch-indicators.ts
@@ -41,7 +41,7 @@ interface IndicateursJSON {
generated_at: string;
source: string;
indicators: IndicateurResult[];
- insee: Record;
+ insee: Record;
}
// ─── FETCH ────────────────────────────────────────────────────────────────────
@@ -51,7 +51,7 @@ async function main() {
const startTime = Date.now();
const indicators: IndicateurResult[] = [];
- const inseeData: Record = {};
+ const inseeData: Record = {};
// 1. Eurostat — fetch en parallèle par batch de 5
console.log(`[fetch-indicators] Fetch ${INDICATEURS.filter(i => i.source === 'eurostat').length} indicateurs Eurostat...`);