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
This commit is contained in:
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
// integrateur.ts — moteur `autonomie.integrateur` : contexte + pondération des
|
||||
// axes + allocation du budget + agrégation.
|
||||
//
|
||||
// Deux modes :
|
||||
// - 'global' (défaut) : le pipeline complet d'origine (`selectionner`), avec
|
||||
// couplages et réajustement GLOBAUX entre axes — celui de calculs ;
|
||||
// - 'par_axe' : chaque axe est calculé par son moteur d'axe sur son budget,
|
||||
// puis les sorties sont additionnées — celui du menu de la plateforme.
|
||||
// Le mode « aménagements cochés » (recalcul sans relancer l'allocation) est
|
||||
// P1.5 : une liste transmise est ignorée et signalée.
|
||||
import { Trace } from '../commun/contrat.js';
|
||||
import { COEFFICIENTS } from '../commun/coefficients.js';
|
||||
import { deriveContexte } from '../commun/contexte.js';
|
||||
import { VERSION } from '../version.js';
|
||||
import { calculeAxe, enregistreLectures, traceSelection } from './axe.js';
|
||||
import { selectionner } from './noyau/algo.js';
|
||||
import { alloueBudget } from './noyau/alloue-budget.js';
|
||||
import { configPourAxes } from './noyau/axes-actifs.js';
|
||||
import { pondereAxes } from './noyau/pondere-axes.js';
|
||||
import { AXES } from './noyau/types.js';
|
||||
import { aCaptage, batterieSelectionnee, kwcSelectionnes, mlHaiesSelectionnes, tauxAlim, tauxEau, tauxEnergie } from './taux.js';
|
||||
/** Statut agrégé : BUDGET_INSUFFISANT l'emporte, puis AJOUTER_OPTIONNEL, sinon OK. */
|
||||
export function agregeStatuts(statuts) {
|
||||
if (statuts.includes('BUDGET_INSUFFISANT'))
|
||||
return 'BUDGET_INSUFFISANT';
|
||||
if (statuts.includes('AJOUTER_OPTIONNEL'))
|
||||
return 'AJOUTER_OPTIONNEL';
|
||||
return 'OK';
|
||||
}
|
||||
export function calculeIntegrateur(entrees, donnees) {
|
||||
const trace = new Trace();
|
||||
const mode = entrees.mode ?? 'global';
|
||||
const coefficients_base = donnees.coefficients ?? COEFFICIENTS;
|
||||
const { table: coefficients, lues } = enregistreLectures(coefficients_base);
|
||||
const config = configPourAxes(donnees.algo_config, entrees.axes_actifs);
|
||||
const inputs = { ...entrees.autodiag, budget_eur: entrees.budget_total };
|
||||
if (entrees.amenagements_coches && entrees.amenagements_coches.length) {
|
||||
trace.avertit('aménagements cochés ignorés : le mode « coché » arrive en P1.5');
|
||||
}
|
||||
const ctx = deriveContexte(inputs.code_postal, donnees.france_climat, coefficients);
|
||||
trace.absorbe(ctx);
|
||||
const contexte = ctx.sorties;
|
||||
const poids = pondereAxes(inputs, contexte, config, false);
|
||||
const budgets = alloueBudget(entrees.budget_total, poids);
|
||||
const axes = {};
|
||||
const taux_couverture = {};
|
||||
let total = 0;
|
||||
let status;
|
||||
let message_status;
|
||||
let couplages = [];
|
||||
if (mode === 'par_axe') {
|
||||
const statuts = [];
|
||||
for (const axe of AXES) {
|
||||
const actif = !entrees.axes_actifs || entrees.axes_actifs[axe] !== false;
|
||||
if (!actif || budgets[axe] <= 0) {
|
||||
axes[axe] = { postes: [], typologies: [], sous_total_eur_ht: 0, budget_alloue: budgets[axe], status: 'OK', taux_couverture: null, taux_detail: null };
|
||||
taux_couverture[axe] = null;
|
||||
continue;
|
||||
}
|
||||
const r = calculeAxe(axe, { autodiag: entrees.autodiag, budget_axe: budgets[axe], contexte }, { ...donnees, coefficients: coefficients_base });
|
||||
trace.absorbe(r);
|
||||
const s = r.sorties;
|
||||
axes[axe] = { postes: s.postes, typologies: s.typologies, sous_total_eur_ht: s.sous_total_eur_ht, budget_alloue: s.budget_alloue, status: s.status, taux_couverture: s.taux_couverture, taux_detail: s.taux_detail };
|
||||
taux_couverture[axe] = s.taux_couverture;
|
||||
total += s.sous_total_eur_ht;
|
||||
statuts.push(s.status);
|
||||
couplages = couplages.concat(s.couplages);
|
||||
}
|
||||
status = agregeStatuts(statuts);
|
||||
}
|
||||
else {
|
||||
const r = selectionner(inputs, donnees.options, donnees.postes, donnees.france_climat, { config, coefficients }, false);
|
||||
const tous = [];
|
||||
for (const axe of AXES) {
|
||||
const a = r.axes[axe];
|
||||
const postes = a?.postes ?? [];
|
||||
tous.push(...postes);
|
||||
let t;
|
||||
if (axe === 'eau')
|
||||
t = tauxEau({ surface_toit_m2: inputs.surface_toit_m2, nb_personnes: inputs.nb_personnes, pluviometrie_mm: contexte.pluviometrie_mm, a_captage: aCaptage(postes) }, coefficients, trace);
|
||||
else if (axe === 'energie')
|
||||
t = tauxEnergie({ kwc: kwcSelectionnes(postes), batterie_kwh: batterieSelectionnee(postes), orientation_toit: inputs.orientation_toit, nb_personnes: inputs.nb_personnes, contexte }, coefficients, trace);
|
||||
else
|
||||
t = tauxAlim({ ml: mlHaiesSelectionnes(postes), nb_personnes: inputs.nb_personnes }, coefficients, trace);
|
||||
axes[axe] = { postes, typologies: postes.map((p) => p.typologie_choisie), sous_total_eur_ht: a?.sous_total_eur_ht ?? 0, budget_alloue: a?.budget_alloue ?? budgets[axe], status: r.status, taux_couverture: t.taux, taux_detail: t.detail };
|
||||
taux_couverture[axe] = t.taux;
|
||||
}
|
||||
total = r.total_eur_ht;
|
||||
status = r.status;
|
||||
message_status = r.message_status;
|
||||
couplages = r.couplages;
|
||||
traceSelection(trace, coefficients_base, lues, tous, donnees.options, couplages);
|
||||
}
|
||||
const sorties = {
|
||||
mode,
|
||||
contexte,
|
||||
poids,
|
||||
budgets,
|
||||
axes,
|
||||
total_eur_ht: total,
|
||||
budget_cible: entrees.budget_total,
|
||||
ecart_budget_pct: entrees.budget_total > 0 ? +(((total - entrees.budget_total) / entrees.budget_total) * 100).toFixed(2) : 0,
|
||||
status,
|
||||
message_status,
|
||||
couplages,
|
||||
taux_couverture,
|
||||
};
|
||||
return trace.resultat(sorties, VERSION);
|
||||
}
|
||||
export const integrateur = {
|
||||
id: 'autonomie.integrateur',
|
||||
calculer: calculeIntegrateur,
|
||||
};
|
||||
export default integrateur;
|
||||
Reference in New Issue
Block a user