From bb4ffb3a1ed9aa70ec4d203883f78f3acb9d04b1 Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Fri, 2 Oct 2026 18:14:03 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(auxiliaires):=20r=C3=A9seau=20du=20circ?= =?UTF-8?q?ulateur=20selon=20la=20surface=20de=20l'installation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §15.2.1 : Lem = 5 x Fcot x (Niv + (Sh/Niv)^0,5) et le nombre de circulateurs max(1, Sh/400) utilisaient la surface habitable du bâtiment entier. Pour un immeuble à installations individuelles, Sh est la surface desservie par l'installation (surface_chauffee), comme le moteur de référence CSTB (Tribu). Autotests CSTB : IC1-0-1/11/12 Cauxiliaires +4,79 % -> 0 %. Fixtures ADEME : aux. distribution ch conformes 211 -> 215, sans régression. --- src/15_conso_aux.js | 20 +++++++++++--------- src/15_conso_aux.spec.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/15_conso_aux.js b/src/15_conso_aux.js index f0188933..5af09abd 100644 --- a/src/15_conso_aux.js +++ b/src/15_conso_aux.js @@ -236,16 +236,23 @@ function getPuissanceCirculateur(em_ch, de, di, du, surfaceHabitable, GV, Tbase) const nbNiveauChauffage = de.nombre_niveau_installation_ch || 1; + /** + * Surface desservie par l'installation : le réseau (Lem) et le nombre de circulateurs + * (Sh / 400) sont ceux de l'installation, pas ceux du bâtiment entier (ex. immeuble à + * installations individuelles par logement), comme le moteur de référence CSTB (Tribu). + */ + const surfaceInstallation = de.surface_chauffee || surfaceHabitable; + const nbCirculateurs = Math.max(1, surfaceInstallation / 400); + // Calcul de la longueur du réseau le plus défavorisé - const Lem = 5 * Fcot * (nbNiveauChauffage + (surfaceHabitable / nbNiveauChauffage) ** 0.5); + const Lem = 5 * Fcot * (nbNiveauChauffage + (surfaceInstallation / nbNiveauChauffage) ** 0.5); // Pertes de charge du réseau (kPa) const deltaPemnom = 0.15 * Lem + deltaPem; // Ratio du besoin couvert par l’équipement const nbGenerateurCascade = du.nbGenerateurCascade || 1; - const ratioSurfaceChauffage = - (de.surface_chauffee || surfaceHabitable) / (surfaceHabitable * nbGenerateurCascade); + const ratioSurfaceChauffage = surfaceInstallation / (surfaceHabitable * nbGenerateurCascade); // Chute nominale de température de dimensionnement const deltaDim = getDeltaDim(em_ch); @@ -255,12 +262,7 @@ function getPuissanceCirculateur(em_ch, de, di, du, surfaceHabitable, GV, Tbase) const Qvemnom = (Pnc * ratioSurfaceChauffage) / (1.163 * deltaDim); - return Math.max( - 30, - 6.44 * - ((deltaPemnom * Qvemnom) / Math.max(1, surfaceHabitable / 400)) ** 0.676 * - Math.max(1, surfaceHabitable / 400) - ); + return Math.max(30, 6.44 * ((deltaPemnom * Qvemnom) / nbCirculateurs) ** 0.676 * nbCirculateurs); } /** diff --git a/src/15_conso_aux.spec.js b/src/15_conso_aux.spec.js index 49192768..d181ead8 100644 --- a/src/15_conso_aux.spec.js +++ b/src/15_conso_aux.spec.js @@ -299,6 +299,39 @@ describe('conso_aux_distribution_ch - auxiliaires de distribution', () => { // Pcirc ∝ Qvemnom^0,676 et Qvemnom ∝ 1/ΔθDim : diviser ΔθDim par 2 multiplie Pcirc par 2^0,676 expect(toutBasse / bassepuisHaute).toBeCloseTo(2 ** 0.676, 9); }); + + /** + * Cas des autotests CSTB IC1-0-1/11/12 : immeuble de 175 m² à installations individuelles, + * l'installation étudiée dessert un logement de 80 m². Lem et le nombre de circulateurs sont + * ceux de l'installation (80 m²), pas ceux de l'immeuble (Tribu : Pcirc = 46,594 W). + */ + test("installation partielle : Lem calculée sur la surface desservie par l'installation", () => { + const calcul = (de, sh) => { + const di = {}; + conso_aux_distribution_ch(emCh, de, di, {}, sh, 1, 1, '0', 100000); + return di.conso_auxiliaire_distribution_ch; + }; + const partielle = calcul({ surface_chauffee: 80 }, 175); + // valeur de référence de régression + expect(partielle).toBeCloseTo(125.15085008075532, 9); + // Lem dépend de la seule surface de l'installation : même réseau que pour un logement seul + // de 80 m² recevant la même puissance (GV × 80 / 175) + const di = {}; + conso_aux_distribution_ch(emCh, {}, di, {}, 80, 1, 1, '0', (100000 * 80) / 175); + expect(partielle).toBeCloseTo(di.conso_auxiliaire_distribution_ch, 9); + }); + + test("installation de plus de 400 m² : nombre de circulateurs selon la surface de l'installation", () => { + const calcul = (de, sh) => { + const di = {}; + conso_aux_distribution_ch(emCh, de, di, {}, sh, 1, 1, '0', 1000000); + return di.conso_auxiliaire_distribution_ch; + }; + const partielle = calcul({ surface_chauffee: 800 }, 1600); + const di = {}; + conso_aux_distribution_ch(emCh, {}, di, {}, 800, 1, 1, '0', 500000); + expect(partielle).toBeCloseTo(di.conso_auxiliaire_distribution_ch, 9); + }); }); /** From 280e64f1eff4d6e9a174fb6c88782c3563ada4ea Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Sat, 3 Oct 2026 12:40:06 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(auxiliaires):=20circulateur=20dimension?= =?UTF-8?q?n=C3=A9=20par=20logement=20=C3=97=20rdim=20en=20DPE=20immeuble?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit En DPE immeuble, une installation décrite représente rdim logements identiques : le circulateur est dimensionné sur la surface d'un logement (surface_chauffee / rdim) puis multiplié par rdim, comme le moteur de référence CSTB (Tribu, Calcul_batiment.cs : Caux_dist_ch += Caux_dist_ch_installation × Rdim). Corpus dpe_immeuble_chauffage_individuel (vs main) : OK 7604 → 7687 (5 OK→KO, 88 KO→OK), aux de distribution à < 1 % de l'ADEME : 74 → 3726 / 4121. Ex. 2437E4022989X : 2 699,04 kWh (= ADEME), contre 1 714 sur main et 937 sans rdim. --- src/15_conso_aux.js | 11 ++++++++-- src/15_conso_aux.spec.js | 34 +++++++++++++++++++++++++++++ src/9_chauffage.js | 11 ++++++++++ src/9_chauffage.spec.js | 46 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/15_conso_aux.js b/src/15_conso_aux.js index 5af09abd..a1520b6f 100644 --- a/src/15_conso_aux.js +++ b/src/15_conso_aux.js @@ -240,8 +240,13 @@ function getPuissanceCirculateur(em_ch, de, di, du, surfaceHabitable, GV, Tbase) * Surface desservie par l'installation : le réseau (Lem) et le nombre de circulateurs * (Sh / 400) sont ceux de l'installation, pas ceux du bâtiment entier (ex. immeuble à * installations individuelles par logement), comme le moteur de référence CSTB (Tribu). + * + * En DPE immeuble, une installation décrite représente rdim logements identiques : le + * circulateur est dimensionné pour un logement (surface / rdim) puis multiplié par rdim + * (Tribu, Calcul_batiment.cs : Caux_dist_ch += Caux_dist_ch_installation × Rdim). */ - const surfaceInstallation = de.surface_chauffee || surfaceHabitable; + const rdim = de.rdim_installation_ch || 1; + const surfaceInstallation = (de.surface_chauffee || surfaceHabitable) / rdim; const nbCirculateurs = Math.max(1, surfaceInstallation / 400); // Calcul de la longueur du réseau le plus défavorisé @@ -262,7 +267,9 @@ function getPuissanceCirculateur(em_ch, de, di, du, surfaceHabitable, GV, Tbase) const Qvemnom = (Pnc * ratioSurfaceChauffage) / (1.163 * deltaDim); - return Math.max(30, 6.44 * ((deltaPemnom * Qvemnom) / nbCirculateurs) ** 0.676 * nbCirculateurs); + return ( + rdim * Math.max(30, 6.44 * ((deltaPemnom * Qvemnom) / nbCirculateurs) ** 0.676 * nbCirculateurs) + ); } /** diff --git a/src/15_conso_aux.spec.js b/src/15_conso_aux.spec.js index d181ead8..8d661708 100644 --- a/src/15_conso_aux.spec.js +++ b/src/15_conso_aux.spec.js @@ -332,6 +332,40 @@ describe('conso_aux_distribution_ch - auxiliaires de distribution', () => { conso_aux_distribution_ch(emCh, {}, di, {}, 800, 1, 1, '0', 500000); expect(partielle).toBeCloseTo(di.conso_auxiliaire_distribution_ch, 9); }); + + /** + * DPE immeuble : une installation décrite représente rdim logements identiques. Tribu + * dimensionne le circulateur d'un logement (surface / rdim) puis multiplie la consommation + * par rdim (Calcul_batiment.cs : Caux_dist_ch += Caux_dist_ch_installation × Rdim). + * Cas réel 2437E4022989X (LICIEL) : 4 installations de 281,5 m², rdim 4 → 2 699,04 kWh. + */ + test('immeuble, installation représentant rdim logements : circulateur par logement × rdim', () => { + const calcul = (de, sh, gv) => { + const di = {}; + conso_aux_distribution_ch(emCh, de, di, {}, sh, 1, 1, '0', gv); + return di.conso_auxiliaire_distribution_ch; + }; + const avecRdim = calcul({ surface_chauffee: 800, rdim_installation_ch: 4 }, 1600, 1000000); + // un logement de 200 m² recevant la puissance de 200 m² sur 1 600 m², multiplié par 4 + const logement = calcul({}, 200, (1000000 * 200) / 1600); + expect(avecRdim).toBeCloseTo(4 * logement, 9); + // valeur de référence de régression + expect(avecRdim).toBeCloseTo(1114.9777138082252, 9); + // sans rdim, l'installation de 800 m² aurait un seul réseau de 800 m² + expect(calcul({ surface_chauffee: 800 }, 1600, 1000000)).not.toBeCloseTo(avecRdim, 3); + }); + + test('rdim égal à 1 : aucun effet', () => { + const calcul = (de) => { + const di = {}; + conso_aux_distribution_ch(emCh, de, di, {}, 175, 1, 1, '0', 100000); + return di.conso_auxiliaire_distribution_ch; + }; + expect(calcul({ surface_chauffee: 80, rdim_installation_ch: 1 })).toBeCloseTo( + calcul({ surface_chauffee: 80 }), + 12 + ); + }); }); /** diff --git a/src/9_chauffage.js b/src/9_chauffage.js index 3becc6d7..83608a43 100644 --- a/src/9_chauffage.js +++ b/src/9_chauffage.js @@ -12,6 +12,16 @@ import enums from './enums.js'; /** * @param dpe {FullDpe} */ +/** + * DPE immeuble (même règle que calc_th du moteur : « appartement » ou « maison » priment). + * @param map_id {string|number} enum_methode_application_dpe_log_id + * @return {boolean} + */ +export function isImmeuble(map_id) { + const map = enums.methode_application_dpe_log?.[map_id] || ''; + return map.includes('immeuble') && !map.includes('appartement') && !map.includes('maison'); +} + export default function calc_chauffage( dpe, ch, @@ -60,6 +70,7 @@ export default function calc_chauffage( genChDe.ratio_virtualisation = de.ratio_virtualisation || 1; genChDe.cle_repartition_ch = de.cle_repartition_ch || 1; genChDe.surface_chauffee = de.surface_chauffee || Sh; + genChDe.rdim_installation_ch = isImmeuble(map_id) ? Number(de.rdim) || 1 : 1; genChDe.nombre_niveau_installation_ch = de.nombre_niveau_installation_ch || 1; genChDe.fch = Fch || 0.5; diff --git a/src/9_chauffage.spec.js b/src/9_chauffage.spec.js index 637694fd..71e12a0b 100644 --- a/src/9_chauffage.spec.js +++ b/src/9_chauffage.spec.js @@ -46,11 +46,20 @@ vi.mock('./13.2_generateur_combustion_ch.js', () => ({ vi.mock('./enums.js', () => ({ default: { classe_altitude: { 1: 'ca1', 2: 'ca2' }, + methode_application_dpe_log: { + 2: 'dpe appartement individuel chauffage individuel ecs individuel', + 6: 'dpe immeuble collectif chauffage individuel ecs individuel', + 10: 'dpe appartement généré à partir des données dpe immeuble chauffage individuel ecs individuel' + }, zone_climatique: { 1: 'h1a', 2: 'h2b', 3: 'h3c' } } })); -const { default: calc_chauffage, tauxChargeForGenerator } = await import('./9_chauffage.js'); +const { + default: calc_chauffage, + tauxChargeForGenerator, + isImmeuble +} = await import('./9_chauffage.js'); const { requestInput, tv, tvColumnIDs } = await import('./utils.js'); const { calc_emetteur_ch } = await import('./9_emetteur_ch.js'); const { calc_generateur_ch, checkForGeneratorType, hasConsoForAuxDistribution } = @@ -234,6 +243,41 @@ describe('calc_chauffage - mise à l’échelle du besoin selon la surface', () }); }); +describe('calc_chauffage - rdim de l’installation transmis au générateur', () => { + const rdimGen = (ch) => + ch.generateur_chauffage_collection.generateur_chauffage[0].donnee_entree.rdim_installation_ch; + + test('DPE immeuble : rdim de l’installation repris', () => { + const ch = installation([generateur()], [emetteur('1')], { rdim: '4' }); + appel(ch, { map_id: 6 }); + expect(rdimGen(ch)).toBe(4); + }); + + test('DPE immeuble sans rdim : 1', () => { + const ch = installation([generateur()], [emetteur('1')]); + appel(ch, { map_id: 6 }); + expect(rdimGen(ch)).toBe(1); + }); + + test('DPE appartement (y compris issu d’un DPE immeuble) : rdim ignoré', () => { + const ch = installation([generateur()], [emetteur('1')], { rdim: '4' }); + appel(ch, { map_id: 10 }); + expect(rdimGen(ch)).toBe(1); + appel(ch, { map_id: 2 }); + expect(rdimGen(ch)).toBe(1); + }); +}); + +describe('isImmeuble', () => { + test('immeuble uniquement, pas appartement issu d’un immeuble ni méthode inconnue', () => { + expect(isImmeuble(6)).toBe(true); + expect(isImmeuble('6')).toBe(true); + expect(isImmeuble(10)).toBe(false); + expect(isImmeuble(2)).toBe(false); + expect(isImmeuble(999)).toBe(false); + }); +}); + describe('calc_chauffage - facteur de couverture solaire (Fch)', () => { test('facteur saisi directement pris tel quel', () => { const ch = installation([generateur()], [emetteur('1')]);