From 4c1fd4e2cfdea0606bd0d81b4654afd168a69f71 Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Fri, 16 Oct 2026 06:48:02 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(clim):=20proratiser=20la=20conso=20de?= =?UTF-8?q?=20froid=20au=20logement=20pour=20les=20dpe=20g=C3=A9n=C3=A9r?= =?UTF-8?q?=C3=A9s=20depuis=20l'immeuble=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/10_clim.js | 16 ++++++++++++++-- src/10_clim.spec.js | 26 +++++++++++++++++++++++++ src/conso.js | 16 ++++++++++++++-- src/conso.spec.js | 46 ++++++++++++++++++++++++++++++++++++++++++++- src/engine.js | 13 ++++++++++++- src/engine.spec.js | 32 +++++++++++++++++++++++++++++++ 6 files changed, 143 insertions(+), 6 deletions(-) diff --git a/src/10_clim.js b/src/10_clim.js index 504c56be1..18b1e7aee 100644 --- a/src/10_clim.js +++ b/src/10_clim.js @@ -14,10 +14,22 @@ function tv_seer(di, de, du, zc_id) { } } -export default function calc_clim(clim, bfr, bfr_dep, zc_id, Sh) { +/** + * @param clim {object} climatisation + * @param bfr {number} besoin de froid du bâtiment + * @param bfr_dep {number} besoin de froid dépensier du bâtiment + * @param zc_id {string} zone climatique + * @param Sh {number} surface de référence du calcul (SH immeuble pour un DPE généré depuis l'immeuble) + * @param prorataLogement {number} part du logement dans la consommation de froid de l'installation + * (Sh_logement / SH_immeuble pour un DPE appartement généré à partir de l'immeuble, 1 sinon). Les + * données intermédiaires restent à l'échelle de l'installation, comme dans le DPE de référence ; + * le prorata n'est appliqué qu'à l'agrégation des consommations du logement (conso.js). + * @see https://github.com/Open3CL/engine/issues/303 + */ +export default function calc_clim(clim, bfr, bfr_dep, zc_id, Sh, prorataLogement = 1) { const de = clim.donnee_entree; const di = clim.donnee_intermediaire || {}; - const du = {}; + const du = { prorata_logement: prorataLogement }; const rs = de.surface_clim / Sh; di.besoin_fr = bfr * rs; diff --git a/src/10_clim.spec.js b/src/10_clim.spec.js index a9b13c190..709fea3f5 100644 --- a/src/10_clim.spec.js +++ b/src/10_clim.spec.js @@ -105,6 +105,32 @@ describe('calc_clim - consommation de climatisation', () => { expect(c.donnee_intermediaire.eer).toBe(3); }); + /** + * DPE appartement généré à partir de l'immeuble : la part du logement est transmise via + * donnee_utilisateur, les données intermédiaires restant à l'échelle de l'installation. + * @see https://github.com/Open3CL/engine/issues/303 + */ + test('prorata logement : mémorisé, données intermédiaires à l’échelle de l’installation', () => { + tv.mockReturnValue({ tv_seer_id: '5', eer: 3 }); + const c = clim({ surface_clim: 100, enum_methode_saisie_carac_sys_id: '1' }); + + calc_clim(c, 1000, 1500, '1', 100, 0.25); + + expect(c.donnee_utilisateur.prorata_logement).toBe(0.25); + // inchangé par le prorata : 0.9 * 1000 / 3 + expect(c.donnee_intermediaire.besoin_fr).toBeCloseTo(1000, 9); + expect(c.donnee_intermediaire.conso_fr).toBeCloseTo(300, 9); + }); + + test('prorata logement absent : 1 par défaut', () => { + tv.mockReturnValue({ tv_seer_id: '5', eer: 3 }); + const c = clim({ surface_clim: 100, enum_methode_saisie_carac_sys_id: '1' }); + + calc_clim(c, 1000, 1500, '1', 100); + + expect(c.donnee_utilisateur.prorata_logement).toBe(1); + }); + test('aucune ligne forfaitaire trouvée : EER indéfini, conso non finie', () => { tv.mockReturnValue(null); const c = clim({ surface_clim: 50, enum_methode_saisie_carac_sys_id: '1' }); diff --git a/src/conso.js b/src/conso.js index d76858837..eb9cfdaec 100644 --- a/src/conso.js +++ b/src/conso.js @@ -983,6 +983,16 @@ function getChauffageConso(gen_ch, field, coef, prorataChauffage, prefix, facteu }, 0); } +/** + * Part du logement dans la consommation de froid d'une installation (1 par défaut), positionnée par + * calc_clim pour les DPE appartement générés à partir de l'immeuble (issue #303). + * @param fr {object} climatisation + * @return {number} + */ +export function prorataFroidLogement(fr) { + return fr.donnee_utilisateur?.prorata_logement ?? 1; +} + function calc_conso_pond( Sh, zc_id, @@ -1086,13 +1096,15 @@ function calc_conso_pond( ret.fr = fr_list.reduce((acc, fr) => { const typeEnergie = getTypeEnergie(fr.donnee_entree, 'fr'); const conso = - fr.donnee_intermediaire.conso_fr * (typeEnergie === 'électricité fr' ? fAc('fr') : 1); + fr.donnee_intermediaire.conso_fr * + prorataFroidLogement(fr) * + (typeEnergie === 'électricité fr' ? fAc('fr') : 1); return acc + getConso(coef, typeEnergie, conso); }, 0); ret.fr_depensier = fr_list.reduce((acc, fr) => { - const conso = fr.donnee_intermediaire.conso_fr_depensier; + const conso = fr.donnee_intermediaire.conso_fr_depensier * prorataFroidLogement(fr); const typeEnergie = getTypeEnergie(fr.donnee_entree, 'fr'); return acc + getConso(coef, typeEnergie, conso); diff --git a/src/conso.spec.js b/src/conso.spec.js index c377a9919..865abd94b 100644 --- a/src/conso.spec.js +++ b/src/conso.spec.js @@ -71,7 +71,8 @@ const { ARRETES_RESEAU_CHALEUR, COEF_GES_AUTRES_RESEAUX_CHALEUR, indexArreteReseauChaleur, - getContenuCo2ReseauChaleur + getContenuCo2ReseauChaleur, + prorataFroidLogement } = await import('./conso.js'); const { tv } = await import('./utils.js'); @@ -298,6 +299,49 @@ describe('calc_conso - agrégation des consommations', () => { expect(net.cout.cout_eclairage).toBeCloseTo(brut.cout.cout_eclairage, 9); }); + /** + * DPE appartement généré à partir de l'immeuble : conso de froid ramenée au logement par le + * prorata mémorisé dans donnee_utilisateur.prorata_logement. + * @see https://github.com/Open3CL/engine/issues/303 + */ + test('froid : conso et conso dépensière proratisées au logement', () => { + const fr = (du) => [ + { + donnee_entree: { enum_type_energie_id: '1' }, + donnee_intermediaire: { conso_fr: 400, conso_fr_depensier: 1000 }, + donnee_utilisateur: du + } + ]; + const plein = calc_conso(100, 1, 1, [], [], [], fr(undefined), 1, 1, DATE_DPE, coef_ep); + const lot = calc_conso( + 100, + 1, + 1, + [], + [], + [], + fr({ prorata_logement: 0.25 }), + 1, + 1, + DATE_DPE, + coef_ep + ); + + expect(plein.ef_conso.conso_fr).toBeCloseTo(400, 9); + expect(lot.ef_conso.conso_fr).toBeCloseTo(100, 9); + expect(lot.ef_conso.conso_fr_depensier).toBeCloseTo(250, 9); + expect(lot.emission_ges.emission_ges_fr).toBeCloseTo( + plein.emission_ges.emission_ges_fr * 0.25, + 9 + ); + }); + + test('prorataFroidLogement : valeur mémorisée, 1 par défaut', () => { + expect(prorataFroidLogement({ donnee_utilisateur: { prorata_logement: 0.3 } })).toBe(0.3); + expect(prorataFroidLogement({ donnee_utilisateur: {} })).toBe(1); + expect(prorataFroidLogement({})).toBe(1); + }); + test('autoconsommation PV : froid électrique minoré, froid non électrique non minoré', () => { const frElec = [ { diff --git a/src/engine.js b/src/engine.js index c200f8dc7..059aa3278 100644 --- a/src/engine.js +++ b/src/engine.js @@ -330,7 +330,18 @@ export function calcul_3cl(inputDpe, options) { const bfr = apport_et_besoin.besoin_fr; const bfr_dep = apport_et_besoin.besoin_fr_depensier; - clim.forEach((clim) => calc_clim(clim, bfr, bfr_dep, zc_id, ShChauffageAndEcs)); + /** + * DPE appartement généré à partir de l'immeuble : besoin et conso de froid calculés à l'échelle de + * l'installation (immeuble), puis ramenés au logement au prorata Sh_logement / SH_immeuble, comme le + * chauffage et l'ECS (Tribu `Calcul_installation_froid.cs` l.62-64, `Calcul_batiment.cs` + * l.1114-1138). + * @see https://github.com/Open3CL/engine/issues/303 + */ + const prorataFroid = + dpeGenereImmeuble && Number(ShChauffageAndEcs) > Number(Sh) && Number(Sh) > 0 + ? Number(Sh) / Number(ShChauffageAndEcs) + : 1; + clim.forEach((clim) => calc_clim(clim, bfr, bfr_dep, zc_id, ShChauffageAndEcs, prorataFroid)); /** * La consommation ECS est obtenu pour certains types de DPE par virtualisation des générateurs collectifs en générateurs individuels virtuels diff --git a/src/engine.spec.js b/src/engine.spec.js index f6ab8cefd..28a0b5e90 100644 --- a/src/engine.spec.js +++ b/src/engine.spec.js @@ -776,6 +776,38 @@ describe('calcul_3cl - surfaces immeuble / appartement et prorata', () => { // Auxiliaires ECS : Sh = surface logement, ramenée à l'immeuble dans le calcul. expect(conso_aux_distribution_ecs.mock.calls[0][3]).toBe(80); expect(conso_aux_distribution_ecs.mock.calls[0][4]).toBe(200); + // Froid : pas un DPE généré depuis l'immeuble, conso de froid non proratisée (#303). + expect(calc_clim.mock.calls[0][4]).toBe(200); + expect(calc_clim.mock.calls[0][5]).toBe(1); + }); + + /** + * @see https://github.com/Open3CL/engine/issues/303 + */ + test.each(['10', '11', '12', '13', '33', '34', '38', '39', '40'])( + "DPE appartement généré à partir de l'immeuble (map %s) : conso de froid proratisée Sh / SH", + (mapId) => { + enums.methode_application_dpe_log = { [mapId]: 'dpe appartement généré immeuble' }; + const dpe = makeDpe(); + dpe.logement.caracteristique_generale.enum_methode_application_dpe_log_id = mapId; + + calcul_3cl(dpe, { sanitize: false }); + + // Besoin calculé sur SH immeuble (200), part du logement = 80 / 200. + expect(calc_clim.mock.calls[0][4]).toBe(200); + expect(calc_clim.mock.calls[0][5]).toBeCloseTo(0.4, 9); + } + ); + + test("DPE généré depuis l'immeuble avec SH immeuble ≤ Sh logement : prorata de froid neutre", () => { + enums.methode_application_dpe_log = { 10: 'dpe appartement généré immeuble' }; + const dpe = makeDpe(); + dpe.logement.caracteristique_generale.enum_methode_application_dpe_log_id = '10'; + dpe.logement.caracteristique_generale.surface_habitable_immeuble = 80; + + calcul_3cl(dpe, { sanitize: false }); + + expect(calc_clim.mock.calls[0][5]).toBe(1); }); test('appartement : rapport SH immeuble / SH logement transmis au calcul du coût', () => { From d0688e4e0821cd94f1683825109a030d831839ee Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Fri, 16 Oct 2026 10:34:32 +0000 Subject: [PATCH 2/2] =?UTF-8?q?chore(corpus):=20retirer=202=20dpe=20=C3=A0?= =?UTF-8?q?=20export=20incoh=C3=A9rent=20sur=20le=20froid=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2313E4450644F : conso_5_usages exporté avec le froid non proratisé (428,94), conso_fr proratisé (85,8). 2377E0818271A : conso_fr = 0 malgré une clim de 662,77 m², totale_auxiliaire < auxiliaire_ventilation, ep ECS / ECS dépensier inversés. --- test/corpus/files/corpus_dpe.csv | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/corpus/files/corpus_dpe.csv b/test/corpus/files/corpus_dpe.csv index f2636cf74..75f265fa0 100644 --- a/test/corpus/files/corpus_dpe.csv +++ b/test/corpus/files/corpus_dpe.csv @@ -2343,7 +2343,6 @@ N°DPE 2462E2975513I 2478E2292798R 2478E2345309I -2313E4450644F 2395E1959846L 2262E0408340V 2313E4060537C @@ -7276,7 +7275,6 @@ N°DPE 2378E0897934A 2478E0437257S 2395E3575457I -2377E0818271A 2313E0617495K 2313E3972378J 2363E1036397Y