Files
JulesandClaude Fable 5.1 69809df5d1 LOT P1.0 : paquet @transformer/moteurs v0.1.0
Extraction du cœur autonomie de calculs.trans-former.fr (12 modules, sans
singleton, couplages sans mutation, console derrière debug), découpage en trois
moteurs d'axe + intégrateur (mode global = calculs, mode par_axe = plateforme),
moteurs commun.contexte (Corse 2A/2B, DOM 97x corrigés) et commun.geometrie,
table des coefficients harmonisés (docs/CONSTANTES.md pour Jules, R-7),
validation structurelle des données (schemas/), tests hors ligne : 28 assertions
d'origine + épingle générée depuis le code d'astro-pro + écarts d'harmonisation
figés + contrat des moteurs. dist/ committé.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UBHYPdeQYy2m1GVJp6b12
2026-09-28 17:06:49 +02:00

103 lines
4.4 KiB
JavaScript

// 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);
},
};