diff --git a/src/15_conso_aux.js b/src/15_conso_aux.js index f0188933..a1520b6f 100644 --- a/src/15_conso_aux.js +++ b/src/15_conso_aux.js @@ -236,16 +236,28 @@ 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). + * + * 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 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é - 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,11 +267,8 @@ 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 ( + 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 49192768..8d661708 100644 --- a/src/15_conso_aux.spec.js +++ b/src/15_conso_aux.spec.js @@ -299,6 +299,73 @@ 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); + }); + + /** + * 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')]);