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
7 changes: 5 additions & 2 deletions src/11_besoin_ecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ export function calc_besoin_ecs_j(ca, mois, zc, nadeq, depensier) {
export default function calc_besoin_ecs(ca, zc, nadeq) {
const ret = {
besoin_ecs: 0,
besoin_ecs_depensier: 0
besoin_ecs_depensier: 0,
besoin_ecs_par_mois: {}
};
for (const mois of mois_liste) {
ret.besoin_ecs += calc_besoin_ecs_j(ca, mois, zc, nadeq, false);
const becs_j = calc_besoin_ecs_j(ca, mois, zc, nadeq, false);
ret.besoin_ecs += becs_j;
ret.besoin_ecs_depensier += calc_besoin_ecs_j(ca, mois, zc, nadeq, true);
ret.besoin_ecs_par_mois[mois] = becs_j;
}
return ret;
}
101 changes: 99 additions & 2 deletions src/11_ecs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enums from './enums.js';
import calc_gen_ecs from './14_generateur_ecs.js';
import { tv, requestInput } from './utils.js';
import { tv, requestInput, mois_liste, Njj } from './utils.js';

function tv_rendement_distribution_ecs(di, de, du, pvc) {
let matcher = {};
Expand Down Expand Up @@ -49,6 +49,89 @@ function tv_rendement_distribution_ecs(di, de, du, pvc) {
}
}

/**
* Calcul de la consommation des auxiliaires de distribution ECS - CAS IMMEUBLE
* Calcul installation par installation pour les réseaux collectifs bouclés ou avec traçage.
*
* @param {object} de - donnee_entree de l'installation ECS
* @param {number} surfaceImmeuble - surface habitable totale de l'immeuble (m²)
* @param {object} becsParMois - besoin ECS mensuel de l'immeuble {Janvier: X, Février: X, ...} [kWh]
* @returns {number} conso_auxiliaire_distribution_ecs_installation [Wh]
*/
function calc_auxiliaire_distribution_ecs_immeuble(de, surfaceImmeuble, becsParMois) {
const type_installation = enums.type_installation[de.enum_type_installation_id];

// Seulement pour les installations collectives
if (!type_installation || !type_installation.includes('collective')) {
return 0;
}

const enum_bouclage_id = String(de.enum_bouclage_reseau_ecs_id);
const surface_installation = de.surface_habitable || 0;
const surface_immeuble_total = surfaceImmeuble || 1;
const ratio_surface = surface_installation / surface_immeuble_total;

// CAS 1 : réseau non bouclé → 0
if (enum_bouclage_id === '1') {
return 0;
}

// CAS 3 : traçage → 0.14 × BECS_annuel × ratio_surface [Wh]
if (enum_bouclage_id === '3') {
const becs_annuel_kwh = Object.values(becsParMois).reduce((acc, v) => acc + v, 0);
// becs_annuel est en kWh → × 1000 pour obtenir des Wh
return 0.14 * becs_annuel_kwh * 1000 * ratio_surface;
}

// CAS 2 : réseau bouclé → calcul complet 9 étapes [Wh]
if (enum_bouclage_id === '2') {
const Sh_inst = surface_installation;
const Niv_inst = Number(de.nombre_niveau_installation_ecs) || 1;

// Étape 3 : Calcul de Lb pour l'installation i
// Lb = 1.2 × (1.1 × Sh_inst/nombre_niveaux + 4 × nombre_niveaux)
const Lb = 1.2 * (1.1 * (Sh_inst / Niv_inst) + 4 * Niv_inst);

// Étape 4 : DeltaPb = 0.2 × Lb + 10
const DeltaPb = 0.2 * Lb + 10;

let Qcirc_annuel = 0;

for (const mois of mois_liste) {
const Njj_mois = Njj[mois];
const becs_immeuble_j_kwh = becsParMois[mois] || 0;

// Étape 1 : Qdwj_i [kWh]
const Qdwj_i = 0.24 * becs_immeuble_j_kwh * ratio_surface;

// Étape 2 : qdwj_i [m³/h]
// Note: la spec officielle utilise Qdwj en Wh et divise par 1000 ensuite.
// Le moteur fournit becs en kWh, donc Qdwj est en kWh → on ne divise pas par 1000.
const qdwj_i = Qdwj_i / (5.815 * 5 * Njj_mois);

// Étape 5 : Phyd,j [W]
// Phyd,j = DeltaPb [kPa] × 1000 × qdwj_i [m³/h] / 3600
const Phyd_j = (DeltaPb * 1000 * qdwj_i) / 3600;

// Étape 6 : Effcirb,j (rendement pompe)
const Effcirb_j = Phyd_j > 0 ? 0.035 * Math.pow(Phyd_j, 0.45) : 0;

// Étape 7 : Pcirb,j [W] = max(Phyd,j / Effcirb,j, 20)
const Pcirb_j = Effcirb_j > 0 ? Math.max(Phyd_j / Effcirb_j, 20) : 20;

// Étape 8 : Qcirb,j [Wh]
const Qcirb_j = (5 * Pcirb_j + 19 * 20) * Njj_mois;

Qcirc_annuel += Qcirb_j;
}

// Étape 9 : conso annuelle en Wh
return Qcirc_annuel;
}

return 0;
}

export default function calc_ecs(
dpe,
ecs,
Expand All @@ -61,7 +144,8 @@ export default function calc_ecs(
virtualisationECS,
surfaceImmeuble,
nombreAppartements,
isImmeubleSystemEcsIndividuels
isImmeubleSystemEcsIndividuels,
becsParMois
) {
const de = ecs.donnee_entree;
const di = {};
Expand Down Expand Up @@ -101,6 +185,19 @@ export default function calc_ecs(
0
);

// Calcul de la consommation des auxiliaires de distribution ECS pour l'immeuble
// Résultat en Wh, converti en kWh pour cohérence avec le reste du moteur
if (th === 'immeuble' && becsParMois && Object.keys(becsParMois).length > 0) {
const conso_aux_wh = calc_auxiliaire_distribution_ecs_immeuble(
de,
surfaceImmeuble,
becsParMois
);
di.conso_auxiliaire_distribution_ecs = conso_aux_wh / 1000;
} else {
di.conso_auxiliaire_distribution_ecs = 0;
}

ecs.donnee_intermediaire = di;
ecs.donnee_utilisateur = du;
}
140 changes: 139 additions & 1 deletion src/11_ecs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ vi.mock('./enums.js', () => ({
vi.mock('./utils.js', () => ({
set_bug_for_bug_compat: vi.fn(),
tv: vi.fn(),
requestInput: vi.fn()
requestInput: vi.fn(),
// Calendrier réduit à deux mois pour le calcul mensuel des auxiliaires de distribution
mois_liste: ['Janvier', 'Février'],
Njj: { Janvier: 31, Février: 28 }
}));

vi.mock('./14_generateur_ecs.js', () => ({
Expand Down Expand Up @@ -281,3 +284,138 @@ describe('calc_ecs - agrégation des consommations', () => {
expect(ecs.donnee_intermediaire.conso_ecs_depensier).toBe(28);
});
});

/**
* Consommation des auxiliaires de distribution ECS - cas immeuble
* Seuls les réseaux collectifs bouclés ou avec traçage consomment de l'énergie.
*/
describe('calc_ecs - auxiliaires de distribution ECS (immeuble)', () => {
/** Calcule l'ECS d'un immeuble et retourne la conso des auxiliaires de distribution [kWh]. */
function consoAuxDistribution(deExtra, becsParMois, surfaceImmeuble = 400, th = 'immeuble') {
const ecs = makeEcs({ tv_rendement_distribution_ecs_id: '5', ...deExtra });
calc_ecs(
{},
ecs,
100,
200,
1,
'ca1',
'h1a',
th,
false,
surfaceImmeuble,
null,
false,
becsParMois
);
return ecs.donnee_intermediaire.conso_auxiliaire_distribution_ecs;
}

test.each([
['logement autre qu’un immeuble', 'maison', { Janvier: 1000 }],
['besoin mensuel absent', 'immeuble', undefined],
['besoin mensuel vide', 'immeuble', {}]
])('%s : pas de consommation', (_libelle, th, becsParMois) => {
expect(
consoAuxDistribution(
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2' },
becsParMois,
400,
th
)
).toBe(0);
});

test.each([
['type d’installation inconnu', { enum_type_installation_id: '9' }],
['installation individuelle', { enum_type_installation_id: '1' }],
[
'réseau collectif non bouclé',
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '1' }
],
['type de bouclage non renseigné', { enum_type_installation_id: '2' }]
])('%s : pas de consommation', (_libelle, de) => {
expect(consoAuxDistribution(de, { Janvier: 1000 })).toBe(0);
});

test('réseau avec traçage : 0,14 × besoin annuel × ratio de surface', () => {
const conso = consoAuxDistribution(
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: 3, surface_habitable: 100 },
{ Janvier: 100, Février: 50 }
);

// 0,14 × 150 kWh × 1000 × (100 / 400) = 5250 Wh => 5,25 kWh
expect(conso).toBeCloseTo(5.25, 9);
});

test('réseau avec traçage sans surfaces renseignées : ratio de surface nul', () => {
const conso = consoAuxDistribution(
{ enum_type_installation_id: '3', enum_bouclage_reseau_ecs_id: '3' },
{ Janvier: 100 },
null
);

// surface_habitable absente => 0 ; surface de l'immeuble absente => 1
expect(conso).toBe(0);
});

test('réseau bouclé sans besoin : puissance minimale du circulateur de 20 W', () => {
const conso = consoAuxDistribution(
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
{ Janvier: 0 }
);

// Pcirb = 20 W chaque mois : (5 × 20 + 19 × 20) × (31 + 28) jours = 28320 Wh
expect(conso).toBeCloseTo(28.32, 9);
});

test('réseau bouclé à faible besoin : puissance du circulateur ramenée au minimum de 20 W', () => {
const conso = consoAuxDistribution(
{
enum_type_installation_id: '2',
enum_bouclage_reseau_ecs_id: '2',
surface_habitable: 100,
nombre_niveau_installation_ecs: 2
},
{ Janvier: 1000, Février: 1000 }
);

// Phyd / Effcirb < 20 W => Pcirb = 20 W, identique au cas sans besoin
expect(conso).toBeCloseTo(28.32, 9);
});

test('réseau bouclé : calcul complet de la consommation du circulateur', () => {
const conso = consoAuxDistribution(
{
enum_type_installation_id: '2',
enum_bouclage_reseau_ecs_id: '2',
surface_habitable: 100,
nombre_niveau_installation_ecs: '2'
},
{ Janvier: 10000 }
);

// Lb = 1,2 × (1,1 × 100 / 2 + 4 × 2) = 75,6 m ; ΔPb = 0,2 × Lb + 10 = 25,12 kPa
// Janvier : Phyd ≈ 4,645 W => Pcirb ≈ 66,49 W ; Février (besoin absent) : Pcirb = 20 W
// Référence de régression (calcul des 9 étapes de la méthode) : 35526,3879475995 Wh
expect(conso).toBeCloseTo(35.526387947599495, 9);
});

test('réseau bouclé sans nombre de niveaux : un seul niveau par défaut', () => {
const avecUnNiveau = consoAuxDistribution(
{
enum_type_installation_id: '2',
enum_bouclage_reseau_ecs_id: '2',
surface_habitable: 100,
nombre_niveau_installation_ecs: 1
},
{ Janvier: 10000 }
);
const sansNiveau = consoAuxDistribution(
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
{ Janvier: 10000 }
);

expect(sansNiveau).toBe(avecUnNiveau);
});
});
18 changes: 11 additions & 7 deletions src/conso.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export default function calc_conso(
null,
prorataECS,
prorataChauffage,
ecs,
conso_aux_distribution_ecs_total
),
ep_conso: calc_conso_pond(
Expand All @@ -262,6 +263,7 @@ export default function calc_conso(
coeffEp,
prorataECS,
prorataChauffage,
ecs,
conso_aux_distribution_ecs_total
),
emission_ges: calc_conso_pond(
Expand All @@ -275,6 +277,7 @@ export default function calc_conso(
coef_ges,
prorataECS,
prorataChauffage,
ecs,
conso_aux_distribution_ecs_total
),
cout: calc_conso_pond(
Expand All @@ -288,6 +291,7 @@ export default function calc_conso(
coef_cout,
prorataECS,
prorataChauffage,
ecs,
conso_aux_distribution_ecs_total
)
};
Expand Down Expand Up @@ -343,7 +347,8 @@ export default function calc_conso(
'',
null,
prorataECS,
prorataChauffage
prorataChauffage,
type_energie === 'électricité' ? ecs : []
);
conso_en = {
conso_ch: conso_en._ch,
Expand Down Expand Up @@ -474,7 +479,7 @@ function calc_conso_pond(
coef,
prorataECS,
prorataChauffage,
conso_aux_distribution_ecs_total
ecs_installations
) {
const ret = {};
ret.auxiliaire_ventilation = vt_list.reduce((acc, vt) => {
Expand Down Expand Up @@ -538,11 +543,10 @@ function calc_conso_pond(
return acc + getConso(coef, 'électricité auxiliaire', conso);
}, 0);

ret.auxiliaire_distribution_ecs = getConso(
coef,
'électricité auxiliaire',
conso_aux_distribution_ecs_total || 0
);
ret.auxiliaire_distribution_ecs = ecs_installations.reduce((acc, inst) => {
const conso = inst.donnee_intermediaire.conso_auxiliaire_distribution_ecs || 0;
return acc + getConso(coef, 'électricité auxiliaire', conso);
}, 0);

ret.ecs = getEcsConso(gen_ecs, 'conso_ecs', coef, prorataECS, prefix);

Expand Down
3 changes: 2 additions & 1 deletion src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ export function calcul_3cl(inputDpe, options) {
virtualisationECS,
dpe.logement.caracteristique_generale.surface_habitable_immeuble,
dpe.logement.caracteristique_generale.nombre_appartement,
isImmeubleSystemEcsIndividuels
isImmeubleSystemEcsIndividuels,
apport_et_besoin.besoin_ecs_par_mois || {}
);

if (!dpeGenereImmeuble) {
Expand Down
Loading
Loading