diff --git a/dist/autonomie/noyau/algo.js b/dist/autonomie/noyau/algo.js index aa39977..2eb2a51 100644 --- a/dist/autonomie/noyau/algo.js +++ b/dist/autonomie/noyau/algo.js @@ -55,22 +55,40 @@ export function finaliseSelection(selection, budget_total, options, postes, inpu }; let status_final = final_pre.status; let message_final = final_pre.message; - for (let passe = 0; passe < 2; passe++) { + // Correctif v0.1.1 (SPEC menu §12 n° 2 et 3) : `reajusteBudget` renvoie en + // écho le journal qu'il reçoit ; l'origine le ré-ajoutait à chaque tour (chaque + // FORCE apparaissait deux à quatre fois). Le journal ne s'enrichit que des + // lignes nouvelles de `appliqueCouplages`. La boucle ne se contentait que du + // dépassement : un réajustement qui lâche des accessoires forcés (principal + // retiré ou changé) laissait le total loin sous le budget sans rien recombler. + // Désormais : couplages → dans la bande [−underspend, +overrun] ? fin ; + // sinon réajuste (CAS A ou B) → couplages…, trois tours au plus, et toujours + // une passe couplages en dernier. Si le dernier forçage refranchit le seuil, + // le statut le dit. + const tol_under = (config.tolerances && config.tolerances.budget_underspend_pct) || 15; + const seuil_under = budget_total * (1 - tol_under / 100); + const dans_la_bande = (t) => t <= seuil_over && t >= seuil_under; + const prend = (avec) => { + etat = { axes: avec.axes, couplages_appliques: [...etat.couplages_appliques, ...(avec.couplages_appliques || [])] }; + }; + for (let passe = 0; passe < 3; passe++) { // IMPORTANT: appliqueCouplages attend selection = { eau, energie, alim } directement - const avec = appliqueCouplages(etat.axes, options, postes, reglages, false); - etat = { - axes: avec.axes, - couplages_appliques: [...etat.couplages_appliques, ...(avec.couplages_appliques || [])], - }; - if (sommeAxes(etat.axes) <= seuil_over) + prend(appliqueCouplages(etat.axes, options, postes, reglages, false)); + if (dans_la_bande(sommeAxes(etat.axes))) break; const re = reajusteBudget(etat, budget_total, options, postes, inputs, contexte, reglages, false); - etat = { - axes: re.axes, - couplages_appliques: [...etat.couplages_appliques, ...(re.couplages_appliques || [])], - }; + etat = { axes: re.axes, couplages_appliques: etat.couplages_appliques }; status_final = re.status; message_final = re.message; + // Bloqué (rien à ajouter, ou plancher atteint) ou dernier tour : une passe couplages et fin + if (passe === 2 || re.status !== 'OK') { + prend(appliqueCouplages(etat.axes, options, postes, reglages, false)); + break; + } + } + if (sommeAxes(etat.axes) > seuil_over && status_final === 'OK') { + status_final = 'BUDGET_INSUFFISANT'; + message_final = 'Budget insuffisant : les couplages requis portent le total à ' + sommeAxes(etat.axes) + ' EUR pour un budget de ' + Math.round(budget_total) + ' EUR.'; } return { axes: etat.axes, diff --git a/dist/autonomie/noyau/couplages.d.ts b/dist/autonomie/noyau/couplages.d.ts index d30b565..00bcbf4 100644 --- a/dist/autonomie/noyau/couplages.d.ts +++ b/dist/autonomie/noyau/couplages.d.ts @@ -3,6 +3,17 @@ export interface ResultatCouplages { axes: Selection; couplages_appliques: string[]; } +/** Codes requis (alias résolus) par les lignes NON forcées de la sélection. */ +export declare function codesRequisPar(axes: Selection, alias: Record): Set; +/** Codes des lignes NON forcées (les choix de postes, par opposition aux accessoires forcés). */ +export declare function codesChoisis(axes: Selection): Set; +/** + * Retire EN PLACE les lignes forcées devenues inutiles et recalcule les + * sous-totaux : celles que plus aucune ligne ne requiert (orphelines), et + * celles dont la typologie est déjà le choix d'un poste (le requis est + * satisfait par ce choix, la ligne forcée est absorbée). Rend le journal. + */ +export declare function retireForcesOrphelins(axes: Selection, alias: Record): string[]; /** * Post-traitement déterministe des couplages. * diff --git a/dist/autonomie/noyau/couplages.js b/dist/autonomie/noyau/couplages.js index 83727d5..2562fd7 100644 --- a/dist/autonomie/noyau/couplages.js +++ b/dist/autonomie/noyau/couplages.js @@ -8,6 +8,13 @@ // Note (origine, conservée) : le marqueur `_conflit_avec` de la passe 2 est // posé sur des copies de postes et n'atteint jamais la sortie ; le // « downgrade planifié » n'a donc jamais lieu. À corriger en patch, avec test. +// +// Correctif v0.1.1 (SPEC menu §12 n° 2 et 5) : une ligne forcée que plus +// aucune ligne ne requiert est RETIRÉE (passe 0). À l'origine, un accessoire +// forcé (conduit, ballon tampon, filière d'épuration) survivait au retrait ou +// au downgrade de son principal, verrouillé pour toujours par +// `force_par_couplage` — d'où des conduits sans poêle et des planchers de +// coût infranchissables (statut INSUFFISANT à 6 000 € mais OK à 3 000 €). import { cout_typologie, dimension_optimale } from './dimension.js'; function copieSelection(selection) { const copie = {}; @@ -20,6 +27,62 @@ function copieSelection(selection) { } return copie; } +const AXES_LIST = ['eau', 'energie', 'alim']; +/** Codes requis (alias résolus) par les lignes NON forcées de la sélection. */ +export function codesRequisPar(axes, alias) { + const requis = new Set(); + for (const axe of AXES_LIST) { + for (const p of axes[axe]?.postes ?? []) { + if (p.force_par_couplage) + continue; + for (const r of p.couplages_requis || []) + requis.add(alias[r] || r); + } + } + return requis; +} +/** Codes des lignes NON forcées (les choix de postes, par opposition aux accessoires forcés). */ +export function codesChoisis(axes) { + const codes = new Set(); + for (const axe of AXES_LIST) + for (const p of axes[axe]?.postes ?? []) + if (!p.force_par_couplage) + codes.add(p.typologie_choisie); + return codes; +} +/** + * Retire EN PLACE les lignes forcées devenues inutiles et recalcule les + * sous-totaux : celles que plus aucune ligne ne requiert (orphelines), et + * celles dont la typologie est déjà le choix d'un poste (le requis est + * satisfait par ce choix, la ligne forcée est absorbée). Rend le journal. + */ +export function retireForcesOrphelins(axes, alias) { + const requis = codesRequisPar(axes, alias); + const choisis = codesChoisis(axes); + const log = []; + for (const axe of AXES_LIST) { + const a = axes[axe]; + if (!a) + continue; + const gardes = []; + for (const p of a.postes) { + if (p.force_par_couplage && !requis.has(p.typologie_choisie)) { + log.push('RETIRE "' + p.typologie_choisie + '" (plus requis par aucun poste) -' + (p.cout_eur_ht || 0) + 'EUR dans axe ' + axe); + } + else if (p.force_par_couplage && choisis.has(p.typologie_choisie)) { + log.push('ABSORBE "' + p.typologie_choisie + '" (déjà le choix d\'un poste) -' + (p.cout_eur_ht || 0) + 'EUR dans axe ' + axe); + } + else { + gardes.push(p); + } + } + if (gardes.length !== a.postes.length) { + a.postes = gardes; + a.sous_total_eur_ht = gardes.reduce((s, p) => s + (p.cout_eur_ht || 0), 0); + } + } + return log; +} /** * Post-traitement déterministe des couplages. * @@ -41,10 +104,11 @@ export function appliqueCouplages(selection_recue, options, postes, reglages, _d function resolveCode(code) { return alias[code] || code; } - const axes_list = ['eau', 'energie', 'alim']; + const axes_list = AXES_LIST; + // Passe 0 (v0.1.1) : lâcher les accessoires forcés dont le principal a disparu. + const couplages_log = retireForcesOrphelins(selection, alias); const tous_postes = axes_list.flatMap((axe) => (selection[axe] && selection[axe].postes ? selection[axe].postes : []).map((p) => Object.assign({}, p, { _axe: axe }))); const codes_selectionnes = new Set(tous_postes.map((p) => p.typologie_choisie)); - const couplages_log = []; // Passe 1 : couplages_requis for (const poste of tous_postes) { const requis_list = poste.couplages_requis || []; diff --git a/dist/autonomie/noyau/index.d.ts b/dist/autonomie/noyau/index.d.ts index b383940..8d38b08 100644 --- a/dist/autonomie/noyau/index.d.ts +++ b/dist/autonomie/noyau/index.d.ts @@ -1,4 +1,5 @@ -export { selectionner } from './algo.js'; +export { selectionner, finaliseSelection } from './algo.js'; +export type { SelectionFinalisee } from './algo.js'; export { dimension_optimale, cout_typologie } from './dimension.js'; export { adequation_contexte } from './adequation.js'; export type { AdequationDebug } from './adequation.js'; @@ -6,7 +7,7 @@ export { pondereAxes } from './pondere-axes.js'; export type { PoidsDebug } from './pondere-axes.js'; export { alloueBudget } from './alloue-budget.js'; export { selectionnePoste } from './selectionne-poste.js'; -export { appliqueCouplages } from './couplages.js'; +export { appliqueCouplages, codesChoisis, codesRequisPar, retireForcesOrphelins } from './couplages.js'; export type { ResultatCouplages } from './couplages.js'; export { reajusteBudget } from './reajuste.js'; export type { EntreeReajuste, ResultatReajuste } from './reajuste.js'; diff --git a/dist/autonomie/noyau/index.js b/dist/autonomie/noyau/index.js index 2f95e87..d04cfe6 100644 --- a/dist/autonomie/noyau/index.js +++ b/dist/autonomie/noyau/index.js @@ -1,12 +1,12 @@ // @transformer/moteurs/autonomie/noyau — le cœur V2 du simulateur autonomie, // pipeline complet (celui de calculs.trans-former.fr), sans singleton ni I/O. -export { selectionner } from './algo.js'; +export { selectionner, finaliseSelection } from './algo.js'; export { dimension_optimale, cout_typologie } from './dimension.js'; export { adequation_contexte } from './adequation.js'; export { pondereAxes } from './pondere-axes.js'; export { alloueBudget } from './alloue-budget.js'; export { selectionnePoste } from './selectionne-poste.js'; -export { appliqueCouplages } from './couplages.js'; +export { appliqueCouplages, codesChoisis, codesRequisPar, retireForcesOrphelins } from './couplages.js'; export { reajusteBudget } from './reajuste.js'; export { configPourAxes, auMoinsUnAxe } from './axes-actifs.js'; export { clamp01, clamp, roundToStep, ZONE_BIOGEO_MAP, zoneRtToClass } from './calibrage.js'; diff --git a/dist/autonomie/noyau/reajuste.js b/dist/autonomie/noyau/reajuste.js index 12f44c6..31c5f2a 100644 --- a/dist/autonomie/noyau/reajuste.js +++ b/dist/autonomie/noyau/reajuste.js @@ -7,6 +7,21 @@ // Note (origine, conservée) : les messages utilisent toLocaleString('fr-FR') // (Intl). Texte de présentation, non assert par les tests ; à sortir du noyau // en patch si l'octet-pour-octet inter-environnements devient un besoin. +// +// Correctifs v0.1.1 (SPEC menu §12 n° 1, 2, 3, 5, 7) : +// - CAS A : un downgrade ne cible jamais une typologie déjà présente (le conduit +// forcé se retrouvait en double) ; après chaque retrait ou downgrade, les +// accessoires forcés que plus rien ne requiert sont lâchés (le plancher de +// coût n'est plus verrouillé par des couplages orphelins). +// - CAS B, stratégie 1 : une ligne forcée n'est jamais « upgradée » (elle est +// une contrainte, pas un choix ; l'origine la triait en tête avec son score +// 0,8 et la transformait, puis la passe suivante re-forçait l'original) ; +// la cible d'un upgrade n'est jamais une typologie déjà présente. +// - CAS B, stratégie 2 : un poste = une ligne. L'origine ajoutait une 2e, 3e… +// option au même poste (jusqu'à cinq `basse_planche_bec`), parce qu'elle +// testait le code et pas le poste, sur une liste figée avant la boucle. +// La quantité d'un poste est portée par `dimension`, pas par des lignes. +import { codesChoisis, retireForcesOrphelins } from './couplages.js'; import { cout_typologie, dimension_optimale } from './dimension.js'; import { parseJsonArr } from './types.js'; export function reajusteBudget(apres_couplages, budget_total, options, postes, inputs, contexte_derived, reglages, debug = false) { @@ -15,11 +30,46 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i const tolerances = config.tolerances || {}; const overrun_seuil = 1 + (tolerances.budget_overrun_pct || 5) / 100; const underspend_seuil = 1 - (tolerances.budget_underspend_pct || 15) / 100; + const alias = config.couplages_alias || {}; const axes = apres_couplages.axes; const axes_list = ['eau', 'energie', 'alim']; function getTousPostes() { return axes_list.flatMap((axe) => (axes[axe] && axes[axe].postes ? axes[axe].postes : []).map((p) => Object.assign({}, p, { _axe: axe }))); } + /** Lâche les accessoires forcés orphelins ou absorbés ; vrai si quelque chose a bougé. */ + function lacheOrphelins() { + const retires = retireForcesOrphelins(axes, alias); + if (retires.length && log) + log.push(...retires); + return retires.length > 0; + } + /** + * Coût des lignes forcées que lâcherait le passage de `ligne` à la typologie + * `nouveau` (null = retrait de la ligne) : ses requis que plus personne ne + * demande, et la ligne forcée que le nouveau choix absorbe. Sert aux + * arbitrages (CAS A « au plus près du budget », CAS B « tient dans le seuil ») + * pour que le total prévu soit celui que la passe couplages rendra. + */ + function coutLibere(ligne, nouveau, instantane) { + const requis_autres = new Set(); + for (const p of instantane) { + if (p.force_par_couplage || p === ligne || (p.typologie_choisie === ligne.typologie_choisie && p._axe === ligne._axe)) + continue; + for (const r of p.couplages_requis || []) + requis_autres.add(alias[r] || r); + } + if (nouveau) + for (const r of parseJsonArr(nouveau.couplages_requis)) + requis_autres.add(alias[r] || r); + let libere = 0; + for (const p of instantane) { + if (!p.force_par_couplage) + continue; + if (!requis_autres.has(p.typologie_choisie) || (nouveau && p.typologie_choisie === nouveau.code)) + libere += p.cout_eur_ht || 0; + } + return libere; + } function calcTotal() { return axes_list.reduce((s, axe) => s + (axes[axe] ? axes[axe].sous_total_eur_ht || 0 : 0), 0); } @@ -47,11 +97,15 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i // seuil). Desormais : parmi TOUS les downgrades possibles on choisit // celui dont le total resultant est le plus proche du budget cible — // evite a la fois le surcout et le sous-equipement massif. - const tous = getTousPostes().filter((p) => !p.force_par_couplage && !p.manuel); + const instantane = getTousPostes(); + const tous = instantane.filter((p) => !p.force_par_couplage && !p.manuel); + const choisis = codesChoisis(axes); const candidats = []; for (const poste_candidat of tous) { const poste_id_num = _posteIdNum(poste_candidat, options); - const alternatives = options.filter((o) => o.poste_id === poste_id_num && o.actif !== false && o.code !== poste_candidat.typologie_choisie); + // v0.1.1 : jamais vers le choix d'un autre poste (une ligne forcée de ce + // code, elle, est absorbée par la passe couplages) + const alternatives = options.filter((o) => o.poste_id === poste_id_num && o.actif !== false && o.code !== poste_candidat.typologie_choisie && !choisis.has(o.code)); const alts_scored = alternatives .map((alt) => { const dim = dimension_optimale(alt, inputs, contexte_derived, coefficients); @@ -63,7 +117,8 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i candidats.push({ poste: poste_candidat, alt: a, - diff: (poste_candidat.cout_eur_ht || 0) - Math.round(a.cout), + // v0.1.1 : la baisse réelle compte les accessoires forcés que ce changement lâche + diff: (poste_candidat.cout_eur_ht || 0) - Math.round(a.cout) + coutLibere(poste_candidat, a.alt, instantane), }); } } @@ -88,17 +143,16 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i typologie_choisie: meilleur.alt.code, nom_affichage: meilleur.alt.nom, description_pedago: meilleur.alt.description_pedago || '', - dimension: meilleur.dim || p.dimension, + dimension: dimensionDe(meilleur.alt, meilleur.dim), cout_eur_ht: Math.round(meilleur.cout), couplages_requis: parseJsonArr(meilleur.alt.couplages_requis), couplages_exclus: parseJsonArr(meilleur.alt.couplages_exclus), _downgraded: true, }); recalcSousTotal(axe_poste); - total -= diff; downgraded = true; if (log) - log.push('Downgrade ' + poste_candidat.typologie_choisie + ' -> ' + meilleur.alt.code + ' -' + diff + 'EUR'); + log.push('Downgrade ' + poste_candidat.typologie_choisie + ' -> ' + meilleur.alt.code + ' -' + diff + 'EUR (accessoires compris)'); } } if (!downgraded) { @@ -110,7 +164,6 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i const ax = axes[axe_poste]; const idx = ax.postes.findIndex((p) => p.typologie_choisie === pire.typologie_choisie); if (idx >= 0) { - total -= ax.postes[idx].cout_eur_ht || 0; ax.postes.splice(idx, 1); recalcSousTotal(axe_poste); downgraded = true; @@ -121,6 +174,10 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i if (!downgraded) break; } + // v0.1.1 : le principal a pu disparaître ou changer — ses accessoires forcés + // ne comptent plus dans le plancher. + lacheOrphelins(); + total = calcTotal(); } if (total > budget_total * overrun_seuil) { status = 'BUDGET_INSUFFISANT'; @@ -142,8 +199,6 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i // ne jamais y réinjecter de postes optionnels. Marqueur posé par // axes-actifs.ts (configPourAxes). Absent = comportement historique inchangé. const axes_desactives = new Set(config._axes_desactives || []); - // Identifier les postes BDD non encore sélectionnés (optionnels manquants) - const codes_selectionnes = new Set(getTousPostes().map((p) => p.typologie_choisie)); // Construire mapping poste_code → axe via postes BDD const posteIdToAxe = {}; if (postes) { @@ -156,18 +211,26 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i while (total < budget_total * underspend_seuil && iterations < 80) { iterations++; let upgraded = false; - // Stratégie 1 : upgrader des typologies existantes - const tous = getTousPostes().filter((p) => !p.manuel); + // v0.1.1 : état réel à chaque tour (l'origine figeait la liste des codes + // avant la boucle et ne la mettait pas à jour après un upgrade) + const instantane = getTousPostes(); + const codes_selectionnes = codesChoisis(axes); + const postes_occupes = new Set(instantane.map((p) => _posteIdNum(p, options))); + // Stratégie 1 : upgrader des typologies existantes (jamais une ligne forcée : + // c'est une contrainte, la passe couplages re-forcerait l'original) + const tous = instantane.filter((p) => !p.manuel && !p.force_par_couplage); tous.sort((a, b) => (b.score_adequation || 0) - (a.score_adequation || 0)); for (const poste_candidat of tous) { const poste_id_num = _posteIdNum(poste_candidat, options); - const alternatives = options.filter((o) => o.poste_id === poste_id_num && o.actif !== false && o.code !== poste_candidat.typologie_choisie); + const alternatives = options.filter((o) => o.poste_id === poste_id_num && o.actif !== false && o.code !== poste_candidat.typologie_choisie && !codes_selectionnes.has(o.code)); const alts_scored = alternatives .map((alt) => { const dim = dimension_optimale(alt, inputs, contexte_derived, coefficients); const cout = cout_typologie(alt, dim, inputs); // Estimer le coût des couplages requis non encore sélectionnés - const codes_sel = new Set(getTousPostes().map((p) => p.typologie_choisie)); + // (v0.1.1 : la ligne qu'on remplace ne compte pas — un requis égal à + // son code actuel devra être forcé après l'upgrade) + const codes_sel = new Set(instantane.filter((p) => !(p.typologie_choisie === poste_candidat.typologie_choisie && p._axe === poste_candidat._axe)).map((p) => p.typologie_choisie)); const alias = (config && config.couplages_alias) || {}; const requis_list = parseJsonArr(alt.couplages_requis); let cout_couplages_requis = 0; @@ -184,10 +247,12 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i return { alt, dim, cout, cout_couplages_requis }; // Ne conserver que les upgrades qui ne dépassent pas le budget global // en incluant le coût estimé des couplages requis non encore satisfaits + // (v0.1.1 : et en déduisant les accessoires forcés que le changement lâche) }) .filter((a) => { - const diff_potential = a.cout + a.cout_couplages_requis - (poste_candidat.cout_eur_ht || 0); - return a.cout > (poste_candidat.cout_eur_ht || 0) && total + diff_potential <= budget_total * overrun_seuil; + const diff_potential = a.cout + a.cout_couplages_requis - (poste_candidat.cout_eur_ht || 0) - coutLibere(poste_candidat, a.alt, instantane); + // v0.1.1 : un « upgrade » dont l'effet net fait baisser le total n'en est pas un + return a.cout > (poste_candidat.cout_eur_ht || 0) && diff_potential > 0 && total + diff_potential <= budget_total * overrun_seuil; }); if (alts_scored.length > 0) { alts_scored.sort((a, b) => a.cout - b.cout); @@ -202,7 +267,7 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i typologie_choisie: meilleur.alt.code, nom_affichage: meilleur.alt.nom, description_pedago: meilleur.alt.description_pedago || '', - dimension: meilleur.dim || p.dimension, + dimension: dimensionDe(meilleur.alt, meilleur.dim), cout_eur_ht: Math.round(meilleur.cout), // IMPORTANT: mettre à jour les couplages de la nouvelle typo (pas hériter ancienne) couplages_requis: parseJsonArr(meilleur.alt.couplages_requis), @@ -219,10 +284,13 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i } } // Stratégie 2 : ajouter des postes optionnels non encore sélectionnés + // (v0.1.1 : un poste = une ligne ; la quantité passe par `dimension`) if (!upgraded) { const postes_manquants = options.filter((o) => { if (o.actif === false || codes_selectionnes.has(o.code)) return false; + if (postes_occupes.has(o.poste_id)) + return false; // Ne pas ajouter de poste appartenant à un axe désactivé (V3) if (axes_desactives.size > 0 && posteIdToAxe[o.poste_id] && axes_desactives.has(posteIdToAxe[o.poste_id])) return false; @@ -260,14 +328,15 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i notes_techniques: best.opt.notes_techniques || '', flag_diy: best.opt.flag_diy || false, complexite_pose: best.opt.complexite_pose || 2, - couplages_requis: [], - couplages_exclus: [], + // v0.1.1 : la ligne dit ce qu'elle requiert (l'origine écrivait [] : + // un poêle ajouté ici n'aurait jamais eu son conduit) + couplages_requis: parseJsonArr(best.opt.couplages_requis), + couplages_exclus: parseJsonArr(best.opt.couplages_exclus), manuel: false, _added_cas_b: true, }); ax.sous_total_eur_ht += Math.round(best.cout); total += Math.round(best.cout); - codes_selectionnes.add(best.opt.code); upgraded = true; if (log) log.push('Ajout optionnel ' + best.opt.code + ' +' + Math.round(best.cout) + 'EUR'); @@ -293,6 +362,16 @@ export function reajusteBudget(apres_couplages, budget_total, options, postes, i debug_reajust: log, }; } +/** + * Dimension d'une ligne qui change de typologie : celle calculée pour la + * nouvelle option, sinon le forfait de l'option (même convention que + * selectionnePoste). v0.1.1 : l'origine héritait de la dimension de la ligne + * remplacée (une batterie au plomb affichée « 0,4 kWc », une chaudière « 6 ml »), + * et le taux de couverture énergie lisait ces kWc fantômes. + */ +function dimensionDe(alt, dim) { + return dim || { valeur: alt.quantite_default || 1, unite: alt.unite || '' }; +} function _posteIdNum(poste_result, options) { const typo = options.find((o) => o.code === poste_result.typologie_choisie); return typo ? typo.poste_id : -1; diff --git a/dist/version.d.ts b/dist/version.d.ts index 561ad91..45dceff 100644 --- a/dist/version.d.ts +++ b/dist/version.d.ts @@ -1 +1 @@ -export declare const VERSION = "0.1.0"; +export declare const VERSION = "0.1.1"; diff --git a/dist/version.js b/dist/version.js index 1b9b898..d561374 100644 --- a/dist/version.js +++ b/dist/version.js @@ -2,4 +2,4 @@ // Doit être égale à "version" de package.json : un test le vérifie // (test/paquet.test.mjs). On ne lit pas package.json ici : le paquet ne fait // aucune I/O, et un JSON importé serait embarqué par les bundlers. -export const VERSION = '0.1.0'; +export const VERSION = '0.1.1'; diff --git a/package.json b/package.json index b0083e2..b537cb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@transformer/moteurs", - "version": "0.1.0", + "version": "0.1.1", "description": "Moteurs de calcul purs pour la rénovation et l'autonomie d'une maison, partagés par calculs.trans-former.fr et plateforme.trans-former.fr. Code et coefficients sourcés ; jamais de prix dans le paquet.", "type": "module", "license": "MIT", diff --git a/src/version.ts b/src/version.ts index 977cb51..29930fd 100644 --- a/src/version.ts +++ b/src/version.ts @@ -2,4 +2,4 @@ // Doit être égale à "version" de package.json : un test le vérifie // (test/paquet.test.mjs). On ne lit pas package.json ici : le paquet ne fait // aucune I/O, et un JSON importé serait embarqué par les bundlers. -export const VERSION = '0.1.0' +export const VERSION = '0.1.1'