Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/11_ecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,27 @@ export default function calc_ecs(
surfaceImmeuble,
nombreAppartements,
isImmeubleSystemEcsIndividuels,
becsParMois
becsParMois,
isImmeubleMultiEcs = false
) {
const de = ecs.donnee_entree;
const di = {};
const du = {};

// La conso de chaque générateur ECS doit être ramenée au prorata de la surface du logement
di.ratio_besoin_ecs = 1;
if (virtualisationECS) {
if (isImmeubleMultiEcs) {
/**
* Immeuble avec plusieurs installations ECS (collectives ou mixtes) : ratio de besoin =
* surface habitable desservie par l'installation / surface habitable de l'immeuble,
* sans normalisation ni plafonnement. Le besoin est exprimé par unité dimensionnée (/ rdim),
* la consommation étant remultipliée par rdim lors de l'agrégation (conso.js, cle_repartition_ecs).
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §11.4
* @see Moteur_DPE.dll Calcul_batiment.Calcul_Cecs() et Calcul_Cecs_installation_2()
*/
di.ratio_besoin_ecs =
Number(de.surface_habitable) / Number(surfaceImmeuble) / (Number(de.rdim) || 1);
} else if (virtualisationECS) {
di.ratio_besoin_ecs = de.cle_repartition_ecs || 1;
} else if (isImmeubleSystemEcsIndividuels) {
if (nombreAppartements) {
Expand Down
98 changes: 98 additions & 0 deletions src/11_ecs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,104 @@ describe('calc_ecs - ratio de besoin ECS', () => {
});
});

/**
* Immeuble à plusieurs installations ECS collectives ou mixtes (#232) :
* ratio = Sh_ecs / Sh immeuble / (rdim || 1), prorata brut sans normalisation ni plafond.
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §11.4
* @see Moteur_DPE.dll Calcul_batiment.Calcul_Cecs() (l.579-593)
*/
describe('calc_ecs - immeuble à plusieurs installations ECS (prorata des surfaces)', () => {
/** Calcule le ratio d'une installation dans le cas immeuble multi-ECS. */
function calcImmeuble(de, surfaceImmeuble, options = {}) {
const ecs = makeEcs({ tv_rendement_distribution_ecs_id: '5', ...de });
calc_ecs(
{},
ecs,
1000,
1500,
1,
'ca1',
'h1a',
'immeuble',
options.virtualisation ?? false,
surfaceImmeuble,
options.nombreAppartements ?? null,
options.individuels ?? false,
{},
options.multiEcs ?? true
);
return ecs.donnee_intermediaire;
}

test('autotest IC1-0-2 : 2 installations collectives 20 et 155 m² sur 175 m²', () => {
const di1 = calcImmeuble({ enum_type_installation_id: '2', surface_habitable: 20 }, 175);
const di2 = calcImmeuble({ enum_type_installation_id: '2', surface_habitable: 155 }, 175);

expect(di1.ratio_besoin_ecs).toBeCloseTo(20 / 175, 9); // 0,1143
expect(di2.ratio_besoin_ecs).toBeCloseTo(155 / 175, 9); // 0,8857
expect(di1.ratio_besoin_ecs + di2.ratio_besoin_ecs).toBeCloseTo(1, 9);
expect(di1.besoin_ecs).toBeCloseTo((1000 * 20) / 175, 9);
});

test('somme des surfaces < Sh immeuble (461 sur 922) : prorata brut 0,5 sans normalisation', () => {
const di = calcImmeuble({ enum_type_installation_id: '2', surface_habitable: 461 }, 922);

expect(di.ratio_besoin_ecs).toBe(0.5);
expect(di.besoin_ecs).toBe(500);
});

test('somme des surfaces > Sh immeuble : prorata brut sans plafonnement', () => {
const di = calcImmeuble({ enum_type_installation_id: '2', surface_habitable: 7706 }, 8726);

expect(di.ratio_besoin_ecs).toBeCloseTo(7706 / 8726, 9);
});

test('immeuble mixte : installation individuelle 800 m², rdim 13, sur 2400 m² => besoin par unité', () => {
const di = calcImmeuble(
{ enum_type_installation_id: '1', surface_habitable: 800, rdim: 13 },
2400
);

expect(di.ratio_besoin_ecs).toBeCloseTo(800 / 2400 / 13, 9); // 0,02564
expect(di.ratio_besoin_ecs).toBeCloseTo(0.02564, 5);
});

test('rdim nul ou absent : diviseur 1', () => {
expect(
calcImmeuble({ surface_habitable: 50, rdim: 0, enum_type_installation_id: '2' }, 200)
.ratio_besoin_ecs
).toBe(0.25);
expect(
calcImmeuble({ surface_habitable: '50', enum_type_installation_id: '2' }, '200')
.ratio_besoin_ecs
).toBe(0.25);
});

test('besoin_ecs_depensier suit le même ratio que besoin_ecs', () => {
const di = calcImmeuble({ enum_type_installation_id: '2', surface_habitable: 20 }, 175);

expect(di.besoin_ecs_depensier).toBeCloseTo((1500 * 20) / 175, 9);
expect(di.besoin_ecs_depensier / di.besoin_ecs).toBeCloseTo(1.5, 9);
});

test('prioritaire sur la clé de répartition et la branche « tout individuel »', () => {
const di = calcImmeuble(
{ enum_type_installation_id: '2', surface_habitable: 100, cle_repartition_ecs: 0.9 },
200,
{ virtualisation: true, individuels: true, nombreAppartements: 4 }
);

expect(di.ratio_besoin_ecs).toBe(0.5);
});

test('sans indicateur multi-ECS (valeur par défaut) : comportement historique conservé (1 / rdim)', () => {
const ecs = makeEcs({ tv_rendement_distribution_ecs_id: '5', surface_habitable: 100, rdim: 4 });
calc_ecs({}, ecs, 1000, 1500, 1, 'ca1', 'h1a', 'immeuble', false, 200, null, false, {});

expect(ecs.donnee_intermediaire.ratio_besoin_ecs).toBe(0.25);
});
});

describe('calc_ecs - rendement de distribution', () => {
test('la ligne forfaitaire alimente rendement_distribution et l’identifiant de table', () => {
const de = { tv_rendement_distribution_ecs_id: '5' };
Expand Down
25 changes: 23 additions & 2 deletions src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,17 +323,37 @@ export function calcul_3cl(inputDpe, options) {
let becs = apport_et_besoin.besoin_ecs;
let becs_dep = apport_et_besoin.besoin_ecs_depensier;
let isImmeubleSystemEcsIndividuels = false;
let isImmeubleMultiEcs = false;

/**
* 11.4 Plusieurs systèmes d’ECS (limité à 2 systèmes différents par logement)
* Les besoins en ECS pour chaque générateur sont / 2
*
* Immeuble avec plusieurs installations ECS (collectives ou mixtes) : le besoin est réparti
* au prorata de la surface habitable desservie par chaque installation (Sh_ecs / Sh immeuble),
* sans normalisation ni plafonnement (cf. calc_ecs).
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §11.4
* @see Guide Tribu TE-211208 p. 24 ; Moteur_DPE.dll Calcul_batiment.Calcul_Cecs()
*/
if (ecs.length > 1) {
// Immeuble avec différents systèmes individuels
isImmeubleSystemEcsIndividuels =
th === 'immeuble' && ecs.every((e) => e.donnee_entree.enum_type_installation_id === '1');

if (!isImmeubleSystemEcsIndividuels) {
if (th === 'immeuble' && !isImmeubleSystemEcsIndividuels) {
isImmeubleMultiEcs =
Number(cg.surface_habitable_immeuble) > 0 &&
ecs.every((e) => Number(e.donnee_entree.surface_habitable) > 0);

if (!isImmeubleMultiEcs) {
console.warn(
`Immeuble avec plusieurs installations ECS : surface habitable de l'immeuble ou d'une installation ECS absente ou nulle.
Repli sur la division du besoin ECS par 2 (§11.4).`
);
}
}

if (!isImmeubleSystemEcsIndividuels && !isImmeubleMultiEcs) {
becs /= 2;
becs_dep /= 2;
}
Expand Down Expand Up @@ -438,7 +458,8 @@ export function calcul_3cl(inputDpe, options) {
dpe.logement.caracteristique_generale.surface_habitable_immeuble,
dpe.logement.caracteristique_generale.nombre_appartement,
isImmeubleSystemEcsIndividuels,
apport_et_besoin.besoin_ecs_par_mois || {}
apport_et_besoin.besoin_ecs_par_mois || {},
isImmeubleMultiEcs
);

if (!dpeGenereImmeuble) {
Expand Down
77 changes: 76 additions & 1 deletion src/engine.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ describe('calcul_3cl - surfaces immeuble / appartement et prorata', () => {
expect(conso_aux_distribution_ecs.mock.calls[0][3]).toBe(200);
});

test('immeuble : systèmes ECS non tous individuels => besoin divisé par deux', () => {
test('immeuble : systèmes ECS non tous individuels sans surfaces => repli sur la division par deux', () => {
enums.methode_application_dpe_log = { 1: 'dpe immeuble collectif' };
const dpe = makeDpe();
dpe.logement.installation_ecs_collection.installation_ecs = [
Expand All @@ -578,11 +578,86 @@ describe('calcul_3cl - surfaces immeuble / appartement et prorata', () => {
generateur_ecs_collection: { generateur_ecs: [] }
}
];
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});

calcul_3cl(dpe, { sanitize: false });

expect(calc_ecs.mock.calls[0][11]).toBe(false);
expect(calc_ecs.mock.calls[0][2]).toBe(50); // 100 / 2
expect(calc_ecs.mock.calls[0][13]).toBe(false); // pas de prorata des surfaces
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Repli sur la division'));
warn.mockRestore();
});

/**
* Immeuble à plusieurs installations ECS collectives ou mixtes : répartition au prorata des
* surfaces (cf. calc_ecs), sans division par 2.
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §11.4
*/
describe('immeuble à plusieurs installations ECS collectives ou mixtes (#232)', () => {
function makeImmeubleMultiEcs(surfaces, surfaceImmeuble = 200) {
enums.methode_application_dpe_log = { 1: 'dpe immeuble collectif' };
const dpe = makeDpe();
dpe.logement.caracteristique_generale.surface_habitable_immeuble = surfaceImmeuble;
dpe.logement.installation_ecs_collection.installation_ecs = surfaces.map((s, i) => ({
donnee_entree: { enum_type_installation_id: i === 0 ? '1' : '2', surface_habitable: s },
generateur_ecs_collection: { generateur_ecs: [] }
}));
return dpe;
}

test('surfaces renseignées : besoin non divisé et prorata des surfaces signalé à calc_ecs', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
calcul_3cl(makeImmeubleMultiEcs([20, 155]), { sanitize: false });

expect(calc_ecs).toHaveBeenCalledTimes(2);
calc_ecs.mock.calls.forEach((call) => {
expect(call[2]).toBe(100); // besoin_ecs non divisé
expect(call[3]).toBe(120); // besoin_ecs_depensier non divisé
expect(call[11]).toBe(false);
expect(call[13]).toBe(true);
});
expect(warn).not.toHaveBeenCalled();
warn.mockRestore();
});

test.each([
['une surface d’installation nulle', [0, 155], 200],
['une surface d’installation absente', [undefined, 155], 200],
['la surface de l’immeuble nulle', [20, 155], 0]
])('%s : repli sur la division par deux avec avertissement', (_, surfaces, sh) => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
calcul_3cl(makeImmeubleMultiEcs(surfaces, sh), { sanitize: false });

expect(calc_ecs.mock.calls[0][2]).toBe(50);
expect(calc_ecs.mock.calls[0][3]).toBe(60);
expect(calc_ecs.mock.calls[0][13]).toBe(false);
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Repli sur la division'));
warn.mockRestore();
});

test('appartement avec deux installations renseignées : division par deux inchangée', () => {
const dpe = makeImmeubleMultiEcs([40, 40]);
enums.methode_application_dpe_log = { 1: 'dpe appartement individuel' };

calcul_3cl(dpe, { sanitize: false });

expect(calc_ecs.mock.calls[0][2]).toBe(50);
expect(calc_ecs.mock.calls[0][13]).toBe(false);
});

test('immeuble dont toutes les installations sont individuelles : branche existante inchangée', () => {
const dpe = makeImmeubleMultiEcs([100, 100]);
dpe.logement.installation_ecs_collection.installation_ecs.forEach(
(e) => (e.donnee_entree.enum_type_installation_id = '1')
);

calcul_3cl(dpe, { sanitize: false });

expect(calc_ecs.mock.calls[0][2]).toBe(100);
expect(calc_ecs.mock.calls[0][11]).toBe(true);
expect(calc_ecs.mock.calls[0][13]).toBe(false);
});
});

test('maison avec deux systèmes ECS => besoin divisé par deux', () => {
Expand Down
Loading