// contexte.ts — moteur `commun.contexte` : code postal → département → zone. // UNE seule version (le noyau autonomie l'utilise aussi). Corse 2A/2B et // DOM 971-976 corrigés : le code d'origine (algo.js) prenait deux chiffres et // retombait sur H2a pour ces départements. import { Trace } from './contrat.js'; import { COEFFICIENTS } from './coefficients.js'; import { VERSION } from '../version.js'; /** Zone biogéographique numérique → libellé (V2 calibrage.js, inchangé). */ export const ZONE_BIOGEO_MAP = { 1: 'C', // Continental 2: 'M', // Montagnard 3: 'OC', // Océanique côtier 4: 'OC', // Océanique intérieur 5: 'MED', // Méditerranéen continental 6: 'MED', // Méditerranéen côtier 7: 'MED', // Pré-alpin méditerranéen 8: 'OC', // Semi-océanique }; /** Sous-zone RT (H1a, H2d…) → classe principale. */ export function zoneRtToClass(zone_rt) { if (!zone_rt) return 'H2'; if (zone_rt.startsWith('H1')) return 'H1'; if (zone_rt.startsWith('H2')) return 'H2'; if (zone_rt === 'H3') return 'H3'; return 'H2'; } /** * Département depuis un code postal : DOM (97x → trois caractères), Corse * (20000-20190 → 2A, 20200-20620 → 2B, autre 20xxx → 2A), sinon deux chiffres. * Reprend `deptFromCp` de calculs (scripts/autonomie/data.js), seule version * qui gérait ces cas. Renvoie null si le code postal est inexploitable. */ export function departementDepuisCodePostal(code_postal) { const cp = String(code_postal ?? '').trim(); if (!/^\d{5}$/.test(cp)) return cp.length >= 2 && /^\d{2}/.test(cp) ? cp.slice(0, 2) : null; if (cp.startsWith('97')) return cp.slice(0, 3); if (cp.startsWith('20')) { const n = Number(cp); if (n >= 20000 && n <= 20190) return '2A'; if (n >= 20200 && n <= 20620) return '2B'; return '2A'; } return cp.slice(0, 2); } /** * Dérive le contexte climatique. Fonction pure, utilisée par le moteur * `commun.contexte` et par le noyau autonomie (`selectionner`). */ export function deriveContexte(code_postal, climat, coefficients = COEFFICIENTS) { const trace = new Trace(); const departement = departementDepuisCodePostal(code_postal); const ligne = departement ? climat.find((c) => c.dept === departement) : undefined; if (!ligne) { const d = coefficients.contexte_defaut; trace.avertit(`contexte par défaut appliqué (${d.valeur.zone_rt}, ${d.valeur.pluviometrie_mm} mm, ${d.valeur.dju} DJU) : ` + (departement ? `département ${departement} absent de france_climat` : `code postal « ${code_postal ?? ''} » inexploitable`)); trace.source('contexte_defaut', d.valeur.zone_rt, d.source, d.date); return trace.resultat({ departement, zone_climatique_rt: d.valeur.zone_rt, zone_rt_class: zoneRtToClass(d.valeur.zone_rt), zone_biogeo: ZONE_BIOGEO_MAP[d.valeur.zone_biogeo_code] ?? 'OC', zone_biogeo_code: d.valeur.zone_biogeo_code, pluviometrie_mm: d.valeur.pluviometrie_mm, dju: d.valeur.dju, }, VERSION); } const d = coefficients.contexte_defaut; const zone_biogeo_code = ligne.zone_biogeo ?? d.valeur.zone_biogeo_code; if (ligne.zone_biogeo == null) trace.avertit(`zone biogéographique absente pour ${departement}, défaut ${d.valeur.zone_biogeo_code}`); const pluviometrie_mm = ligne.pluvio_mm_an ?? d.valeur.pluviometrie_mm; if (ligne.pluvio_mm_an == null) trace.avertit(`pluviométrie absente pour ${departement}, défaut ${d.valeur.pluviometrie_mm} mm`); const dju = ligne.dju_base_18 ?? d.valeur.dju; if (ligne.dju_base_18 == null) trace.avertit(`DJU absent pour ${departement}, défaut ${d.valeur.dju}`); trace.source(`france_climat.${departement}`, ligne.zone_rt, String(ligne.source_climat ?? 'france_climat'), String(ligne.source_date ?? '')); return trace.resultat({ departement, zone_climatique_rt: ligne.zone_rt, zone_rt_class: zoneRtToClass(ligne.zone_rt), zone_biogeo: ZONE_BIOGEO_MAP[zone_biogeo_code] ?? 'OC', zone_biogeo_code, pluviometrie_mm, dju, }, VERSION); } export const contexte = { id: 'commun.contexte', calculer(entrees, donnees) { return deriveContexte(entrees.code_postal, donnees.france_climat, donnees.coefficients ?? COEFFICIENTS); }, };