@open3cl/engine 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,145 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
3
+ import { BesoinChService } from './besoin-ch.service.js';
4
+ import { mois_liste } from '../../../../../utils.js';
5
+
6
+ /** @type {BesoinChService} **/
7
+ let service;
8
+
9
+ /** @type {FrTvStore} **/
10
+ let tvStore;
11
+
12
+ describe('Calcul des besoins en chauffage du logement', () => {
13
+ beforeEach(() => {
14
+ tvStore = new FrTvStore();
15
+ service = new BesoinChService(tvStore);
16
+ });
17
+
18
+ test.each([
19
+ {
20
+ inertie: 1,
21
+ expected: 0.24
22
+ },
23
+ {
24
+ inertie: 2,
25
+ expected: 0.24
26
+ },
27
+ {
28
+ inertie: 3,
29
+ expected: 0.23
30
+ },
31
+ {
32
+ inertie: 4,
33
+ expected: 0.22
34
+ }
35
+ ])(
36
+ 'Determination de la fraction des besoins de chauffage couverts par les apports gratuits pour un mois donné avec inertie = $inertie',
37
+ ({ inertie, expected }) => {
38
+ /** @type {Contexte} */
39
+ const ctx = { inertie: { id: inertie } };
40
+
41
+ /** @type { Logement } **/
42
+ const logement = {
43
+ donnees_de_calcul: {
44
+ apportsInterneCh: { Janvier: 102.5 },
45
+ apportsSolaire: { Janvier: 12.5 }
46
+ },
47
+ sortie: { deperdition: { deperdition_enveloppe: 25.9 } }
48
+ };
49
+ expect(service.fractionBesoinCh(ctx, logement, 'Janvier', 18)).toBeCloseTo(expected, 2);
50
+ }
51
+ );
52
+
53
+ test('Besoin de chauffage hors pertes récupérées et fraction des apports gratuits pour un mois', () => {
54
+ /** @type {Contexte} */
55
+ const ctx = { inertie: { id: 1 } };
56
+
57
+ /** @type { Logement } **/
58
+ const logement = {
59
+ donnees_de_calcul: {
60
+ apportsInterneCh: { Janvier: 102.5 },
61
+ apportsSolaire: { Janvier: 12.5 }
62
+ },
63
+ sortie: { deperdition: { deperdition_enveloppe: 25.9 } }
64
+ };
65
+
66
+ const besoinHP = service.besoinChHorsPertesRecuperees(ctx, logement, 'Janvier', 12.5);
67
+ expect(besoinHP.besoinChMoisHP).toBeCloseTo(0.2139, 4);
68
+ expect(besoinHP.fractionApportGratuitMois).toBeCloseTo(4.2412, 4);
69
+ });
70
+
71
+ test('Besoin total de chauffage hors pertes récupérées et fraction des apports gratuits', () => {
72
+ vi.spyOn(tvStore, 'getData').mockReturnValue(10);
73
+
74
+ /** @type {Contexte} */
75
+ const ctx = {
76
+ inertie: { id: 1, ilpa: 1 },
77
+ altitude: { value: '400-800m' },
78
+ zoneClimatique: { value: 'h3c' }
79
+ };
80
+
81
+ /** @type { Logement } **/
82
+ const logement = {
83
+ donnees_de_calcul: {
84
+ apportsInterneCh: {
85
+ Janvier: 102.5,
86
+ Février: 102.5,
87
+ Mars: 102.5,
88
+ Avril: 102.5,
89
+ Mai: 102.5,
90
+ Juin: 102.5,
91
+ Juillet: 102.5,
92
+ Aout: 102.5,
93
+ Septembre: 102.5,
94
+ Octobre: 102.5,
95
+ Novembre: 102.5,
96
+ Décembre: 102.5
97
+ },
98
+ apportsSolaire: {
99
+ Janvier: 102.5,
100
+ Février: 102.5,
101
+ Mars: 102.5,
102
+ Avril: 102.5,
103
+ Mai: 102.5,
104
+ Juin: 102.5,
105
+ Juillet: 102.5,
106
+ Aout: 102.5,
107
+ Septembre: 102.5,
108
+ Octobre: 102.5,
109
+ Novembre: 102.5,
110
+ Décembre: 102.5
111
+ },
112
+ besoinChauffageHP: {},
113
+ besoinChauffageDepensierHP: {}
114
+ },
115
+ sortie: { deperdition: { deperdition_enveloppe: 25.9 } }
116
+ };
117
+
118
+ const besoinTotalHP = service.execute(ctx, logement);
119
+ expect(besoinTotalHP.besoin_ch_hp).toBeCloseTo(1.1388, 4);
120
+ expect(besoinTotalHP.besoin_ch_depensier_hp).toBeCloseTo(1.1388, 4);
121
+ expect(besoinTotalHP.fraction_apport_gratuit_ch).toBeCloseTo(0.6336, 4);
122
+ expect(besoinTotalHP.fraction_apport_gratuit_depensier_ch).toBeCloseTo(0.6336, 4);
123
+
124
+ for (const mois of mois_liste) {
125
+ expect(tvStore.getData).toHaveBeenCalledWith(
126
+ 'dh19',
127
+ ctx.altitude.value,
128
+ ctx.zoneClimatique.value,
129
+ mois,
130
+ ctx.inertie.ilpa
131
+ );
132
+
133
+ expect(tvStore.getData).toHaveBeenCalledWith(
134
+ 'dh21',
135
+ ctx.altitude.value,
136
+ ctx.zoneClimatique.value,
137
+ mois,
138
+ ctx.inertie.ilpa
139
+ );
140
+
141
+ expect(logement.donnees_de_calcul.besoinChauffageHP[mois]).toBeCloseTo(0.0949, 4);
142
+ expect(logement.donnees_de_calcul.besoinChauffageDepensierHP[mois]).toBeCloseTo(0.0949, 4);
143
+ }
144
+ });
145
+ });
@@ -0,0 +1,168 @@
1
+ import { mois_liste } from '../../../../../utils.js';
2
+ import { inject } from 'dioma';
3
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
4
+
5
+ /**
6
+ * Calcul des pertes récupérées de génération de chauffage
7
+ * Chapitre 9.1.1 Consommation de chauffage
8
+ *
9
+ * Données calculées
10
+ * — pertes_generateur_ch_recup : pertes récupérées de génération pour le chauffage (kWh)
11
+ * — pertes_generateur_ch_recup_depensier : pertes récupérées de génération pour le chauffage (kWh) pour le scénario dépensier
12
+ *
13
+ * Methode_de_calcul_3CL_DPE_2021 - Page 57
14
+ * Octobre 2021
15
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
16
+ */
17
+ export class PerteChRecupService {
18
+ /**
19
+ * @type {FrTvStore}
20
+ */
21
+ #tvStore;
22
+
23
+ /**
24
+ * @param tvStore {FrTvStore}
25
+ */
26
+ constructor(tvStore = inject(FrTvStore)) {
27
+ this.#tvStore = tvStore;
28
+ }
29
+
30
+ /**
31
+ * Pertes récupérées des générateurs de chauffage pour le chauffage (kWh)
32
+ *
33
+ * @param ctx {Contexte}
34
+ * @param logement {Logement}
35
+ * @return {{pertes_generateur_ch_recup: number, pertes_generateur_ch_recup_depensier: number}}
36
+ */
37
+ execute(ctx, logement) {
38
+ return {
39
+ pertes_generateur_ch_recup: this.pertesGenerateurChRecup(ctx, logement, false) / 1000,
40
+ pertes_generateur_ch_recup_depensier: this.pertesGenerateurChRecup(ctx, logement, true) / 1000
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Pertes récupérées des générateurs de chauffage pour le chauffage (Wh)
46
+ * 9.1.1 Consommation de chauffage
47
+ *
48
+ * @param ctx {Contexte}
49
+ * @param logement {Logement}
50
+ * @param depensier {boolean}
51
+ * @returns {number}
52
+ */
53
+ pertesGenerateurChRecup(ctx, logement, depensier) {
54
+ const generateursWithPertesGeneration = this.generateursWithPertesGeneration(logement);
55
+
56
+ return mois_liste.reduce((acc, mois) => {
57
+ return (
58
+ acc +
59
+ this.Qrec(
60
+ generateursWithPertesGeneration,
61
+ this.#tvStore.getData(
62
+ depensier ? 'nref21' : 'nref19',
63
+ ctx.altitude.value,
64
+ ctx.zoneClimatique.value,
65
+ mois,
66
+ ctx.inertie.ilpa
67
+ ),
68
+ depensier
69
+ ? logement.donnees_de_calcul.besoinChauffageDepensierHP[mois]
70
+ : logement.donnees_de_calcul.besoinChauffageHP[mois]
71
+ )
72
+ );
73
+ }, 0);
74
+ }
75
+
76
+ /**
77
+ * Pertes récupérées des générateurs de chauffage pour le chauffage
78
+ * 9.1.1 Consommation de chauffage
79
+ *
80
+ * Seules les pertes des générateurs en volume chauffé sont récupérables.
81
+ * Les pertes récupérées des générateurs d’air chaud sont nulles.
82
+ *
83
+ * @param logement {Logement}
84
+ */
85
+ generateursWithPertesGeneration(logement) {
86
+ const installationsCh =
87
+ logement.installation_chauffage_collection?.installation_chauffage || [];
88
+
89
+ return installationsCh.reduce((acc, installation) => {
90
+ // Liste des générateurs de chauffage pour lesquels il y a une récupération d'énergie
91
+ return acc.concat(
92
+ (installation.generateur_chauffage_collection?.generateur_chauffage || []).filter(
93
+ (generateurChauffage) => {
94
+ const generateurChauffageDE = generateurChauffage.donnee_entree;
95
+
96
+ /**
97
+ * 50 - générateur à air chaud à combustion avant 2006
98
+ * 51 - générateur à air chaud à combustion standard a partir de 2006
99
+ * 52 - générateur à air chaud à combustion à condensation a partir de 2006
100
+ */
101
+ return (
102
+ (generateurChauffageDE.position_volume_chauffe ?? 0) === 1 &&
103
+ ![50, 51, 52].includes(parseInt(generateurChauffageDE.enum_type_generateur_ch_id))
104
+ );
105
+ }
106
+ )
107
+ );
108
+ }, []);
109
+ }
110
+
111
+ /**
112
+ * Pertes récupérées de génération pour le chauffage sur le mois j (Wh)
113
+ *
114
+ * @param generateurs {GenerateurChauffage[]}
115
+ * @param nref {number}
116
+ * @param besoinChauffageMois {number}
117
+ * @constructor
118
+ */
119
+ Qrec(generateurs, nref, besoinChauffageMois) {
120
+ return generateurs.reduce((acc, generateur) => {
121
+ const generateurChauffageDE = generateur.donnee_entree;
122
+ const generateurChauffageDI = generateur.donnee_intermediaire;
123
+
124
+ /**
125
+ * Part des pertes par les parois égale à 0,75 pour les équipements à ventouse ou assistés par ventilateur
126
+ * et 0,5 pour les autres
127
+ * @type {number}
128
+ */
129
+ const Cper = parseInt(generateurChauffageDE.presence_ventouse || 0) === 1 ? 0.75 : 0.5;
130
+
131
+ /**
132
+ * Durée pendant laquelle les pertes sont récupérées sur le mois (h)
133
+ */
134
+ let Dperj;
135
+ switch (parseInt(generateurChauffageDE.enum_usage_generateur_id)) {
136
+ case 1:
137
+ // Pour les générateurs assurant le chauffage uniquement
138
+ Dperj = Math.min(
139
+ nref,
140
+ this.#pertes_gen_ch(besoinChauffageMois, generateurChauffageDI.pn)
141
+ );
142
+ break;
143
+ case 2:
144
+ // Pour les générateurs assurant l’ECS uniquement
145
+ Dperj = this.#pertes_gen_ecs(nref);
146
+ break;
147
+ case 3:
148
+ // Pour les générateurs assurant le chauffage et l’ECS
149
+ Dperj = Math.min(
150
+ nref,
151
+ this.#pertes_gen_ch(besoinChauffageMois, generateurChauffageDI.pn) +
152
+ this.#pertes_gen_ecs(nref)
153
+ );
154
+ break;
155
+ }
156
+
157
+ return acc + 0.48 * Cper * generateurChauffageDI.qp0 * Dperj || 0;
158
+ }, 0);
159
+ }
160
+
161
+ #pertes_gen_ch(Bch, pn) {
162
+ return (1.3 * Bch * 1000) / (0.3 * pn);
163
+ }
164
+
165
+ #pertes_gen_ecs(nref) {
166
+ return (nref * 1790) / 8760;
167
+ }
168
+ }
@@ -0,0 +1,313 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import corpus from '../../../../../../test/corpus-sano.json';
3
+ import { expect_or, getAdemeFileJson } from '../../../../../../test/test-helpers.js';
4
+ import { DpeNormalizerService } from '../../../../normalizer/domain/dpe-normalizer.service.js';
5
+ import { ContexteBuilder } from '../../contexte.builder.js';
6
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
7
+ import { mois_liste } from '../../../../../utils.js';
8
+ import { PerteChRecupService } from './perte-ch-recup.service.js';
9
+ import { BesoinChService } from './besoin-ch.service.js';
10
+ import { ApportGratuitService } from '../apport_gratuit/apport-gratuit.service.js';
11
+ import { PRECISION_PERCENT } from '../../../../../../test/constant.js';
12
+
13
+ /** @type {PerteChRecupService} **/
14
+ let service;
15
+
16
+ /** @type {BesoinChService} **/
17
+ let besoinChService;
18
+
19
+ /** @type {ApportGratuitService} **/
20
+ let apportGratuitService;
21
+
22
+ /** @type {FrTvStore} **/
23
+ let tvStore;
24
+
25
+ /** @type {DpeNormalizerService} **/
26
+ let normalizerService;
27
+
28
+ /** @type {ContexteBuilder} **/
29
+ let contexteBuilder;
30
+
31
+ describe('Calcul des pertes de génération de chauffage récupérées', () => {
32
+ beforeEach(() => {
33
+ tvStore = new FrTvStore();
34
+ service = new PerteChRecupService(tvStore);
35
+ besoinChService = new BesoinChService();
36
+ apportGratuitService = new ApportGratuitService();
37
+ normalizerService = new DpeNormalizerService();
38
+ contexteBuilder = new ContexteBuilder();
39
+ });
40
+
41
+ test.each([
42
+ {
43
+ label: 'Installation CH uniquement avec ventouse, utilisation de nref',
44
+ enumUsageGenerateurId: 1,
45
+ presenceVentouse: 1,
46
+ expected: 562.5
47
+ },
48
+ {
49
+ label: 'Installation CH uniquement sans ventouse, utilisation de nref',
50
+ enumUsageGenerateurId: 1,
51
+ presenceVentouse: 0,
52
+ expected: 375
53
+ },
54
+ {
55
+ label: "Installation CH uniquement sans ventouse, pas d'utilisation de nref",
56
+ enumUsageGenerateurId: 1,
57
+ presenceVentouse: 0,
58
+ nref: 500,
59
+ expected: 7971.08
60
+ },
61
+ {
62
+ label: 'Installation ECS uniquement avec ventouse',
63
+ enumUsageGenerateurId: 2,
64
+ presenceVentouse: 1,
65
+ expected: 114.94
66
+ },
67
+ {
68
+ label: 'Installation ECS uniquement sans ventouse',
69
+ enumUsageGenerateurId: 2,
70
+ presenceVentouse: 0,
71
+ expected: 76.627
72
+ },
73
+ {
74
+ label: 'Installation ECS + CH avec ventouse, utilisation de nref',
75
+ enumUsageGenerateurId: 3,
76
+ presenceVentouse: 1,
77
+ expected: 562.5
78
+ },
79
+ {
80
+ label: 'Installation ECS + CH sans ventouse, utilisation de nref',
81
+ enumUsageGenerateurId: 3,
82
+ presenceVentouse: 0,
83
+ expected: 375
84
+ },
85
+ {
86
+ label: "Installation ECS + CH sans ventouse, pas d'utilisation de nref",
87
+ enumUsageGenerateurId: 3,
88
+ presenceVentouse: 0,
89
+ nref: 450,
90
+ expected: 10729.642
91
+ }
92
+ ])(
93
+ 'Calcul des pertes récupérées de génération pour le chauffage sur un mois pour $label',
94
+ ({ enumUsageGenerateurId, presenceVentouse, nref = undefined, expected }) => {
95
+ /** @type {GenerateurChauffage[]} */
96
+ const generateurs = [
97
+ {
98
+ donnee_entree: {
99
+ presence_ventouse: presenceVentouse,
100
+ enum_usage_generateur_id: enumUsageGenerateurId
101
+ },
102
+ donnee_intermediaire: { pn: 25000, qp0: 125 }
103
+ }
104
+ ];
105
+
106
+ expect(service.Qrec(generateurs, nref ?? 12.5, 1532.9)).toBeCloseTo(expected, 3);
107
+ }
108
+ );
109
+
110
+ test('Filtre des générateurs avec pertes de génération', () => {
111
+ const generateur1 = {
112
+ donnee_entree: {}
113
+ };
114
+ const generateur2 = {
115
+ donnee_entree: { position_volume_chauffe: 0, enum_type_generateur_ch_id: 45 }
116
+ };
117
+ const generateur3 = {
118
+ donnee_entree: { position_volume_chauffe: 0, enum_type_generateur_ch_id: 50 }
119
+ };
120
+ const generateur4 = {
121
+ donnee_entree: { position_volume_chauffe: 1, enum_type_generateur_ch_id: 50 }
122
+ };
123
+ const generateur5 = {
124
+ donnee_entree: { position_volume_chauffe: 1, enum_type_generateur_ch_id: 48 }
125
+ };
126
+
127
+ /** @type {Logement} */
128
+ let logement = { installation_chauffage_collection: {} };
129
+ expect(service.generateursWithPertesGeneration(logement)).toStrictEqual([]);
130
+
131
+ logement.installation_chauffage_collection = { installation_chauffage: [] };
132
+ expect(service.generateursWithPertesGeneration(logement)).toStrictEqual([]);
133
+
134
+ logement.installation_chauffage_collection = {
135
+ installation_chauffage: [{ generateur_chauffage_collection: {} }]
136
+ };
137
+ expect(service.generateursWithPertesGeneration(logement)).toStrictEqual([]);
138
+
139
+ logement.installation_chauffage_collection = {
140
+ installation_chauffage: [{ generateur_chauffage_collection: { generateur_chauffage: [] } }]
141
+ };
142
+ expect(service.generateursWithPertesGeneration(logement)).toStrictEqual([]);
143
+
144
+ logement.installation_chauffage_collection = {
145
+ installation_chauffage: [
146
+ {
147
+ generateur_chauffage_collection: {
148
+ generateur_chauffage: [generateur1, generateur2, generateur3]
149
+ }
150
+ },
151
+ { generateur_chauffage_collection: { generateur_chauffage: [generateur4, generateur5] } }
152
+ ]
153
+ };
154
+ expect(service.generateursWithPertesGeneration(logement)).toStrictEqual([generateur5]);
155
+ });
156
+
157
+ test.each([
158
+ {
159
+ label: 'Installation CH en mode conventionnel',
160
+ depensier: false,
161
+ expected: 288
162
+ },
163
+ {
164
+ label: 'Installation CH en mode dépensier',
165
+ depensier: true,
166
+ expected: 540
167
+ }
168
+ ])('Pertes de chauffage récupérées pour $label', ({ depensier, expected }) => {
169
+ vi.spyOn(tvStore, 'getData').mockReturnValue(depensier ? 1.5 : 0.8);
170
+
171
+ /** @type {Contexte} */
172
+ const ctx = {
173
+ altitude: { value: '400-800m' },
174
+ zoneClimatique: { value: 'h1a' },
175
+ inertie: { ilpa: 1 }
176
+ };
177
+
178
+ /** @type {Logement} */
179
+ const logement = {
180
+ installation_chauffage_collection: {
181
+ installation_chauffage: [
182
+ {
183
+ generateur_chauffage_collection: {
184
+ generateur_chauffage: [
185
+ {
186
+ donnee_entree: {
187
+ position_volume_chauffe: 1,
188
+ enum_type_generateur_ch_id: 48,
189
+ enum_usage_generateur_id: 1
190
+ },
191
+ donnee_intermediaire: { pn: 25000, qp0: 125 }
192
+ }
193
+ ]
194
+ }
195
+ }
196
+ ]
197
+ },
198
+ donnees_de_calcul: {
199
+ besoinChauffageHP: {
200
+ Janvier: 102.5,
201
+ Février: 102.5,
202
+ Mars: 102.5,
203
+ Avril: 102.5,
204
+ Mai: 102.5,
205
+ Juin: 102.5,
206
+ Juillet: 102.5,
207
+ Aout: 102.5,
208
+ Septembre: 102.5,
209
+ Octobre: 102.5,
210
+ Novembre: 102.5,
211
+ Décembre: 102.5
212
+ },
213
+ besoinChauffageDepensierHP: {
214
+ Janvier: 112.5,
215
+ Février: 112.5,
216
+ Mars: 112.5,
217
+ Avril: 112.5,
218
+ Mai: 112.5,
219
+ Juin: 112.5,
220
+ Juillet: 112.5,
221
+ Aout: 112.5,
222
+ Septembre: 112.5,
223
+ Octobre: 112.5,
224
+ Novembre: 112.5,
225
+ Décembre: 112.5
226
+ }
227
+ }
228
+ };
229
+
230
+ expect(service.pertesGenerateurChRecup(ctx, logement, depensier)).toBeCloseTo(expected, 3);
231
+ for (const mois of mois_liste) {
232
+ expect(tvStore.getData).toHaveBeenCalledWith(
233
+ depensier ? 'nref21' : 'nref19',
234
+ '400-800m',
235
+ 'h1a',
236
+ mois,
237
+ 1
238
+ );
239
+ }
240
+ });
241
+
242
+ describe("Test d'intégration des installations CH", () => {
243
+ test.each(corpus)('vérification des pertes de génération ch pour dpe %s', (ademeId) => {
244
+ let dpeRequest = getAdemeFileJson(ademeId);
245
+ dpeRequest = normalizerService.normalize(dpeRequest);
246
+ dpeRequest.logement.donnees_de_calcul = {
247
+ apportsInterneDepensier: [],
248
+ apportsInterneCh: [],
249
+ apportsSolaire: [],
250
+ besoinChauffageHP: [],
251
+ besoinChauffageDepensierHP: []
252
+ };
253
+
254
+ /** @type {Contexte} */
255
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
256
+ apportGratuitService.apportInterne(ctx, dpeRequest.logement);
257
+ apportGratuitService.apportSolaire(ctx, dpeRequest.logement);
258
+ besoinChService.execute(ctx, dpeRequest.logement);
259
+ const pertesGeneration = service.execute(ctx, dpeRequest.logement);
260
+
261
+ const expectedValue = dpeRequest.logement.sortie.apport_et_besoin.pertes_generateur_ch_recup;
262
+ const calculatedValue = pertesGeneration.pertes_generateur_ch_recup;
263
+
264
+ expect_or(
265
+ () =>
266
+ expect(Math.abs(calculatedValue - expectedValue) / (expectedValue || 1)).toBeLessThan(
267
+ PRECISION_PERCENT
268
+ ),
269
+ () =>
270
+ expect(
271
+ Math.abs(calculatedValue - expectedValue * 1000) / (expectedValue * 1000 || 1)
272
+ ).toBeLessThan(PRECISION_PERCENT)
273
+ );
274
+ });
275
+
276
+ test.each(corpus)(
277
+ 'vérification des pertes de génération ch depensier des installations CH pour dpe %s',
278
+ (ademeId) => {
279
+ let dpeRequest = getAdemeFileJson(ademeId);
280
+ dpeRequest = normalizerService.normalize(dpeRequest);
281
+ dpeRequest.logement.donnees_de_calcul = {
282
+ apportsInterneDepensier: [],
283
+ apportsInterneCh: [],
284
+ apportsSolaire: [],
285
+ besoinChauffageHP: [],
286
+ besoinChauffageDepensierHP: []
287
+ };
288
+
289
+ /** @type {Contexte} */
290
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
291
+ apportGratuitService.apportInterne(ctx, dpeRequest.logement);
292
+ apportGratuitService.apportSolaire(ctx, dpeRequest.logement);
293
+ besoinChService.execute(ctx, dpeRequest.logement);
294
+ const pertesGeneration = service.execute(ctx, dpeRequest.logement);
295
+
296
+ const expectedValue =
297
+ dpeRequest.logement.sortie.apport_et_besoin.pertes_generateur_ch_recup_depensier;
298
+ const calculatedValue = pertesGeneration.pertes_generateur_ch_recup_depensier;
299
+
300
+ expect_or(
301
+ () =>
302
+ expect(Math.abs(calculatedValue - expectedValue) / (expectedValue || 1)).toBeLessThan(
303
+ PRECISION_PERCENT
304
+ ),
305
+ () =>
306
+ expect(
307
+ Math.abs(calculatedValue - expectedValue * 1000) / (expectedValue * 1000 || 1)
308
+ ).toBeLessThan(PRECISION_PERCENT)
309
+ );
310
+ }
311
+ );
312
+ });
313
+ });
@@ -29,20 +29,23 @@ export class PerteEcsRecupService {
29
29
  }
30
30
 
31
31
  /**
32
+ * Pertes de distribution et de stockage ECS (kWh)
32
33
  * @param ctx {Contexte}
33
34
  * @param logement {Logement}
34
- * @return {{pertes_distribution_ecs_recup: number, pertes_distribution_ecs_recup_depensier: number, pertes_stockage_ecs_recup: number}}
35
+ * @return {{pertes_distribution_ecs_recup: number, pertes_distribution_ecs_recup_depensier: number, pertes_stockage_ecs_recup: number, pertes_stockage_ecs_recup_depensier: number}}
35
36
  */
36
37
  execute(ctx, logement) {
37
38
  return {
38
- pertes_distribution_ecs_recup: this.pertesDistributionEcsRecup(ctx, logement, false),
39
- pertes_distribution_ecs_recup_depensier: this.pertesDistributionEcsRecup(ctx, logement, true),
40
- pertes_stockage_ecs_recup: this.pertesStockageEcsRecup(ctx, logement)
39
+ pertes_distribution_ecs_recup: this.pertesDistributionEcsRecup(ctx, logement, false) / 1000,
40
+ pertes_distribution_ecs_recup_depensier:
41
+ this.pertesDistributionEcsRecup(ctx, logement, true) / 1000,
42
+ pertes_stockage_ecs_recup: this.pertesStockageEcsRecup(ctx, logement, false) / 1000,
43
+ pertes_stockage_ecs_recup_depensier: this.pertesStockageEcsRecup(ctx, logement, true) / 1000
41
44
  };
42
45
  }
43
46
 
44
47
  /**
45
- * Pertes récupérées de distribution d’ECS pour le chauffage
48
+ * Pertes récupérées de distribution d’ECS pour le chauffage (Wh)
46
49
  * 9.1.1 Consommation de chauffage
47
50
  *
48
51
  * @param ctx {Contexte}
@@ -89,14 +92,15 @@ export class PerteEcsRecupService {
89
92
  }
90
93
 
91
94
  /**
92
- * Pertes récupérées du stockage d’ECS pour le chauffage
95
+ * Pertes récupérées du stockage d’ECS pour le chauffage (Wh)
93
96
  * 9.1.1 Consommation de chauffage
94
97
  *
95
98
  * @param ctx {Contexte}
96
99
  * @param logement {Logement}
100
+ * @param depensier {boolean}
97
101
  * @returns {number}
98
102
  */
99
- pertesStockageEcsRecup(ctx, logement) {
103
+ pertesStockageEcsRecup(ctx, logement, depensier) {
100
104
  const installationsEcs = logement.installation_ecs_collection?.installation_ecs || [];
101
105
 
102
106
  let pertesStockage = installationsEcs.reduce((acc, installation) => {
@@ -111,7 +115,7 @@ export class PerteEcsRecupService {
111
115
  acc +
112
116
  pertesStockage *
113
117
  this.#tvStore.getData(
114
- 'nref19',
118
+ depensier ? 'nref21' : 'nref19',
115
119
  ctx.altitude.value,
116
120
  ctx.zoneClimatique.value,
117
121
  mois,