@open3cl/engine 1.6.1 → 1.6.2

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.
Files changed (107) hide show
  1. package/10_besoin_fr.spec.js +122 -0
  2. package/10_clim.spec.js +116 -0
  3. package/11_besoin_ecs.spec.js +69 -0
  4. package/11_ecs.spec.js +282 -0
  5. package/11_nadeq.spec.js +37 -0
  6. package/12.4_pac.spec.js +131 -0
  7. package/13.2_generateur_combustion.js +2 -0
  8. package/13.2_generateur_combustion.spec.js +537 -0
  9. package/13.2_generateur_combustion_bouilleur.js +4 -1
  10. package/13.2_generateur_combustion_bouilleur.spec.js +74 -0
  11. package/13.2_generateur_combustion_ch.spec.js +323 -0
  12. package/13.2_generateur_combustion_chaudiere.spec.js +85 -0
  13. package/13.2_generateur_pac.spec.js +64 -0
  14. package/13_rendement_distribution_ecs.spec.js +38 -0
  15. package/14_generateur_ecs.js +4 -4
  16. package/14_generateur_ecs.spec.js +745 -0
  17. package/15_conso_aux.spec.js +225 -0
  18. package/16.2_production_enr.spec.js +188 -0
  19. package/16_conso_eclairage.spec.js +62 -0
  20. package/2021_04_13_confort_ete.spec.js +159 -0
  21. package/2021_04_13_qualite_isolation.js +1 -1
  22. package/2021_04_13_qualite_isolation.spec.js +286 -0
  23. package/3.1_b.js +1 -1
  24. package/3.1_b.spec.js +367 -0
  25. package/3.2.1_mur.spec.js +812 -39
  26. package/3.2.2_plancher_bas.spec.js +643 -78
  27. package/3.2.3_plancher_haut.spec.js +452 -0
  28. package/3.3.1.4_porte.spec.js +114 -0
  29. package/3.3_baie_vitree.spec.js +449 -295
  30. package/3.4_pont_thermique.spec.js +890 -0
  31. package/3_deperdition.spec.js +397 -0
  32. package/4_ventilation.js +1 -1
  33. package/4_ventilation.spec.js +406 -0
  34. package/5_conso_ventilation.spec.js +186 -0
  35. package/6.2_surface_sud_equivalente.spec.js +138 -0
  36. package/8_intermittence.spec.js +33 -0
  37. package/9_besoin_ch.js +2 -2
  38. package/9_besoin_ch.spec.js +381 -0
  39. package/9_chauffage.js +4 -0
  40. package/9_chauffage.spec.js +478 -92
  41. package/9_conso_ch.spec.js +502 -248
  42. package/9_emetteur_ch.spec.js +339 -0
  43. package/9_generateur_ch.spec.js +467 -81
  44. package/apport_et_besoin.spec.js +126 -0
  45. package/conso.js +1 -1
  46. package/conso.spec.js +471 -82
  47. package/core/assets/domain/add-additionnal-ue-values-tables.spec.js +53 -0
  48. package/core/assets/domain/synchronize-solicitations-tables.spec.js +25 -0
  49. package/core/assets/domain/synchronize-valeur-tables.spec.js +48 -0
  50. package/core/conf/infrastructure/application.config.spec.js +35 -0
  51. package/core/file/infrastructure/adapter/csv-parser.store.spec.js +31 -13
  52. package/core/file/infrastructure/adapter/file.store.spec.js +148 -12
  53. package/core/tv/infrastructure/assets/additional-ue-values.spec.js +36 -0
  54. package/core/util/infrastructure/object-util.spec.js +20 -0
  55. package/core/util/logger/log-service.js +16 -7
  56. package/core/util/logger/log-service.spec.js +101 -0
  57. package/dpe-sanitizer.service.spec.js +138 -0
  58. package/engine.js +4 -1
  59. package/engine.spec.js +854 -0
  60. package/enums.spec.js +46 -0
  61. package/features/dpe/domain/models/type-generateur.model.js +1 -2
  62. package/features/dpe/domain/models/type-generateur.model.spec.js +25 -0
  63. package/features/dpe/domain/models/type-habitation.model.spec.js +41 -0
  64. package/features/dpe/domain/models/type-stockage.model.spec.js +18 -0
  65. package/features/dpe/domain/models/type-ventilation.model.spec.js +19 -0
  66. package/features/dpe/infrastructure/ch/chTv.store.spec.js +4 -4
  67. package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.js +14 -2
  68. package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.spec.js +16 -0
  69. package/features/dpe/infrastructure/tv.store.js +28 -9
  70. package/features/dpe/infrastructure/tv.store.spec.js +44 -0
  71. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +2 -1
  72. package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.spec.js +18 -0
  73. package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +33 -1
  74. package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +21 -1
  75. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +41 -1
  76. package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +44 -1
  77. package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +2 -1
  78. package/features/engine/domain/ch/generateur-ch.service.js +2 -0
  79. package/features/engine/domain/ch/generateur-ch.service.spec.js +117 -2
  80. package/features/engine/domain/ch/installation-ch.service.spec.js +14 -0
  81. package/features/engine/domain/conso/froid/conso-froid.service.spec.js +82 -1
  82. package/features/engine/domain/constants.spec.js +14 -0
  83. package/features/engine/domain/contexte.builder.spec.js +21 -0
  84. package/features/engine/domain/ecs/generateur-ecs.service.spec.js +11 -0
  85. package/features/engine/domain/ecs/installation-ecs.service.spec.js +55 -2
  86. package/features/engine/domain/engine.service.spec.js +227 -4
  87. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +124 -1
  88. package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +270 -2
  89. package/features/engine/domain/enveloppe/deperdition.service.spec.js +141 -0
  90. package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +2 -1
  91. package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +117 -1
  92. package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +134 -1
  93. package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +126 -1
  94. package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +18 -1
  95. package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +2 -1
  96. package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +67 -1
  97. package/features/normalizer/domain/dpe-normalizer.service.js +1 -1
  98. package/features/normalizer/domain/dpe-normalizer.service.spec.js +53 -2
  99. package/ficheTechnique.spec.js +26 -0
  100. package/index.spec.js +54 -0
  101. package/package.json +5 -2
  102. package/preprocessor/index.js +20 -0
  103. package/preprocessor/index.spec.js +15 -0
  104. package/tv.spec.js +38 -0
  105. package/utils.js +43 -1
  106. package/utils.spec.js +518 -2
  107. package/output.js +0 -1
@@ -0,0 +1,225 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+
3
+ /**
4
+ * Dépendances mockées uniquement pour `conso_aux_distribution_ch` :
5
+ * - `enums` : libellés de classe d'altitude / zone climatique ;
6
+ * - `tvs.nref19` : nombre d'heures de fonctionnement mensuel ;
7
+ * - `mois_liste` / `Tbase` : réduits à des valeurs contrôlées.
8
+ * `conso_aux_gen` n'utilise aucune de ces dépendances (calcul autonome).
9
+ */
10
+ vi.mock('./enums.js', () => ({
11
+ default: {
12
+ classe_altitude: { 1: 'ca1' },
13
+ zone_climatique: { 1: 'h1a' }
14
+ }
15
+ }));
16
+
17
+ vi.mock('./tv.js', () => ({
18
+ default: {
19
+ nref19: { 0: { ca1: { Janvier: { h1a: 100 } } } }
20
+ }
21
+ }));
22
+
23
+ vi.mock('./utils.js', () => ({
24
+ mois_liste: ['Janvier'],
25
+ Tbase: { ca1: { h1: -9 } }
26
+ }));
27
+
28
+ const { conso_aux_gen, conso_aux_distribution_ch } = await import('./15_conso_aux.js');
29
+
30
+ /**
31
+ * 15.1 Consommation des auxiliaires de génération
32
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §15.1
33
+ */
34
+ describe('conso_aux_gen - auxiliaires de génération', () => {
35
+ let di;
36
+
37
+ beforeEach(() => {
38
+ di = { pn: 20000 };
39
+ });
40
+
41
+ test('chaudière gaz (chauffage) : facteurs G=20, H=1.6 appliqués', () => {
42
+ const de = { enum_type_generateur_ch_id: '90' };
43
+ conso_aux_gen(di, de, 'ch', 1000, 1200, 100);
44
+ expect(di.conso_auxiliaire_generation_ch).toBeCloseTo(2.6, 10);
45
+ expect(di.conso_auxiliaire_generation_ch_depensier).toBeCloseTo(3.12, 10);
46
+ });
47
+
48
+ test('générateur hors des plages connues : consommation nulle (G=H=0)', () => {
49
+ const de = { enum_type_generateur_ch_id: '1' };
50
+ conso_aux_gen(di, de, 'ch', 1000, 1200, 100);
51
+ expect(di.conso_auxiliaire_generation_ch).toBe(0);
52
+ expect(di.conso_auxiliaire_generation_ch_depensier).toBe(0);
53
+ });
54
+
55
+ test("chaudière bois : facteurs appliqués seulement en présence d'un ventilateur", () => {
56
+ const avecVentilateur = { pn: 20000 };
57
+ conso_aux_gen(
58
+ avecVentilateur,
59
+ { enum_type_generateur_ch_id: '60', presenceVentilateur: 1 },
60
+ 'ch',
61
+ 1000,
62
+ 1200,
63
+ 100
64
+ );
65
+ expect(avecVentilateur.conso_auxiliaire_generation_ch).toBeCloseTo(14.165, 10);
66
+
67
+ const sansVentilateur = { pn: 20000 };
68
+ conso_aux_gen(
69
+ sansVentilateur,
70
+ { enum_type_generateur_ch_id: '60', presenceVentilateur: 0 },
71
+ 'ch',
72
+ 1000,
73
+ 1200,
74
+ 100
75
+ );
76
+ expect(sansVentilateur.conso_auxiliaire_generation_ch).toBe(0);
77
+ });
78
+
79
+ test('chaudière gaz avec Pn > 400 kW : puissance plafonnée à 400 kW', () => {
80
+ const diCap = { pn: 500000 };
81
+ conso_aux_gen(diCap, { enum_type_generateur_ch_id: '90' }, 'ch', 1000, 1200, 100);
82
+ expect(diCap.conso_auxiliaire_generation_ch).toBeCloseTo(1.65, 10);
83
+ expect(diCap.conso_auxiliaire_generation_ch_depensier).toBeCloseTo(1.584, 10);
84
+ });
85
+
86
+ test('type ECS : aucun prorata de surface chauffée appliqué', () => {
87
+ const de = { enum_type_generateur_ecs_id: '50' };
88
+ conso_aux_gen(di, de, 'ecs', 800, 1000, 100);
89
+ expect(di.conso_auxiliaire_generation_ecs).toBeCloseTo(2.08, 10);
90
+ expect(di.conso_auxiliaire_generation_ecs_depensier).toBeCloseTo(2.6, 10);
91
+ });
92
+
93
+ test('générateur à air chaud avec Pn > 300 kW : puissance plafonnée à 300 kW', () => {
94
+ // enum_type_generateur_ch_id = 50 => générateur à air chaud (H=4), G=0
95
+ const diAir = { pn: 500000 };
96
+ conso_aux_gen(diAir, { enum_type_generateur_ch_id: '50' }, 'ch', 1000, 1200, 100);
97
+ // g=0, h=4, pe plafonné à 300000 => Paux = 0 + 4*300 = 1200
98
+ // conso = (1 * (1200 * 1000 * 1)) / 300000 = 4
99
+ expect(diAir.conso_auxiliaire_generation_ch).toBeCloseTo(4, 10);
100
+ // depensier utilise di.pn (500000, non plafonné) : (1200 * 1200) / 500000 = 2.88
101
+ expect(diAir.conso_auxiliaire_generation_ch_depensier).toBeCloseTo(2.88, 10);
102
+ });
103
+
104
+ test('chaudière bois avec ventilateur et Pn > 70 kW : puissance plafonnée à 70 kW', () => {
105
+ // enum_type_generateur_ch_id = 60 => chaudière bois (G=73.3, H=10.5)
106
+ const diBois = { pn: 100000 };
107
+ conso_aux_gen(
108
+ diBois,
109
+ { enum_type_generateur_ch_id: '60', presenceVentilateur: 1 },
110
+ 'ch',
111
+ 1000,
112
+ 1200,
113
+ 100
114
+ );
115
+ // pe plafonné à 70000 => Paux = 73.3 + 10.5*70 = 808.3
116
+ // conso = (1 * (808.3 * 1000 * 1)) / 70000
117
+ expect(diBois.conso_auxiliaire_generation_ch).toBeCloseTo((808.3 * 1000) / 70000, 10);
118
+ });
119
+
120
+ test('type de générateur inconnu (ni ch ni ecs) : facteurs nuls, consommation nulle', () => {
121
+ // Couvre la branche de repli `values[type] || []` de getFacteur
122
+ const diInconnu = { pn: 20000 };
123
+ conso_aux_gen(diInconnu, {}, 'autre', 1000, 1200, 100);
124
+ expect(diInconnu.conso_auxiliaire_generation_autre).toBe(0);
125
+ expect(diInconnu.conso_auxiliaire_generation_autre_depensier).toBe(0);
126
+ });
127
+ });
128
+
129
+ /**
130
+ * 15.2 Consommation des auxiliaires de distribution de chauffage
131
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §15.2
132
+ */
133
+ describe('conso_aux_distribution_ch - auxiliaires de distribution', () => {
134
+ const emCh = [
135
+ {
136
+ donnee_entree: {
137
+ enum_type_emission_distribution_id: '10',
138
+ enum_temp_distribution_ch_id: '3'
139
+ }
140
+ }
141
+ ];
142
+
143
+ test('puissance de circulateur plafonnée à 30 W pour une faible déperdition', () => {
144
+ const di = {};
145
+ // GV très faible => la puissance calculée reste sous le plancher de 30 W
146
+ conso_aux_distribution_ch(emCh, {}, di, {}, 100, 1, 1, '0', 1);
147
+ // Pcircem19 = 30 (plancher), nref19 = 100 => 30 * 100 / 1000 = 3
148
+ expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(3, 10);
149
+ });
150
+
151
+ test('une déperdition élevée augmente la consommation au-dessus du plancher', () => {
152
+ const di = {};
153
+ conso_aux_distribution_ch(emCh, {}, di, {}, 100, 1, 1, '0', 100000);
154
+ expect(di.conso_auxiliaire_distribution_ch).toBeGreaterThan(3);
155
+ });
156
+
157
+ test('émetteur plancher/plafond chauffant (deltaPem=15, Fcot=0,156)', () => {
158
+ // Type 6 => première liste : deltaPem=15, Fcot=0,156, deltaDim=7,5 (temp id 3)
159
+ const em = [
160
+ {
161
+ donnee_entree: {
162
+ enum_type_emission_distribution_id: '6',
163
+ enum_temp_distribution_ch_id: '3'
164
+ }
165
+ }
166
+ ];
167
+ const di = {};
168
+ conso_aux_distribution_ch(em, {}, di, {}, 100, 1, 1, '0', 100000);
169
+ // valeur de référence de régression
170
+ expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(215.1781621610199, 9);
171
+ });
172
+
173
+ test('émetteur radiateur monotube (deltaPem=30)', () => {
174
+ // Type 24 => deuxième liste : deltaPem=30
175
+ const em = [
176
+ {
177
+ donnee_entree: {
178
+ enum_type_emission_distribution_id: '24',
179
+ enum_temp_distribution_ch_id: '3'
180
+ }
181
+ }
182
+ ];
183
+ const di = {};
184
+ conso_aux_distribution_ch(em, {}, di, {}, 100, 1, 1, '0', 100000);
185
+ // valeur de référence de régression
186
+ expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(372.082418731648, 9);
187
+ });
188
+
189
+ test('plusieurs émetteurs : Fcot forcé à 0,802 (cas le plus défavorable)', () => {
190
+ // Deux émetteurs de type plancher chauffant : sans le forçage, Fcot=0,156.
191
+ const em = [
192
+ {
193
+ donnee_entree: {
194
+ enum_type_emission_distribution_id: '6',
195
+ enum_temp_distribution_ch_id: '3'
196
+ }
197
+ },
198
+ {
199
+ donnee_entree: {
200
+ enum_type_emission_distribution_id: '6',
201
+ enum_temp_distribution_ch_id: '3'
202
+ }
203
+ }
204
+ ];
205
+ const di = {};
206
+ conso_aux_distribution_ch(em, {}, di, {}, 100, 1, 1, '0', 100000);
207
+ // valeur de référence de régression (Fcot=0,802 malgré des planchers chauffants)
208
+ expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(260.5604692803713, 9);
209
+ });
210
+
211
+ test('température de distribution haute (id 4) : deltaDim = 15', () => {
212
+ const em = [
213
+ {
214
+ donnee_entree: {
215
+ enum_type_emission_distribution_id: '10',
216
+ enum_temp_distribution_ch_id: '4'
217
+ }
218
+ }
219
+ ];
220
+ const di = {};
221
+ conso_aux_distribution_ch(em, {}, di, {}, 100, 1, 1, '0', 100000);
222
+ // valeur de référence de régression
223
+ expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(136.51553416032854, 9);
224
+ });
225
+ });
@@ -249,4 +249,192 @@ describe('production ENR unit tests', () => {
249
249
  }
250
250
  });
251
251
  });
252
+
253
+ test('should reuse an existing donnee_intermediaire when computing conso elec ac', () => {
254
+ // Le noeud donnee_intermediaire préexiste : il doit être conservé (et non recréé)
255
+ const productionElecEnr = {
256
+ donnee_intermediaire: { valeur_existante: 42 },
257
+ panneaux_pv_collection: {
258
+ panneaux_pv: [{ nombre_module: 8, enum_orientation_pv_id: 1, enum_inclinaison_pv_id: 2 }]
259
+ }
260
+ };
261
+
262
+ const productionElectricite = {};
263
+ const conso = {
264
+ ef_conso: {},
265
+ sortie_par_energie_collection: {
266
+ sortie_par_energie: [
267
+ { enum_type_energie_id: '1', conso_5_usages: 1500, conso_ch: 1000, conso_ecs: 1500 }
268
+ ]
269
+ }
270
+ };
271
+
272
+ productionENR.calculateConsoElecAc(
273
+ productionElectricite,
274
+ productionElecEnr,
275
+ conso,
276
+ 1,
277
+ 'maison',
278
+ 10
279
+ );
280
+
281
+ // la clé préexistante est préservée, les champs calculés sont ajoutés
282
+ expect(productionElecEnr.donnee_intermediaire.valeur_existante).toBe(42);
283
+ expect(productionElecEnr.donnee_intermediaire.production_pv).toBe(2826.8015616000007);
284
+ expect(productionElecEnr.donnee_intermediaire.conso_elec_ac).toBeGreaterThan(0);
285
+ });
286
+
287
+ test('should get ppv when panneaux_pv is a single object (not an array)', () => {
288
+ const productionElecEnr = {
289
+ panneaux_pv_collection: {
290
+ panneaux_pv: {
291
+ nombre_module: 8,
292
+ enum_orientation_pv_id: 1,
293
+ enum_inclinaison_pv_id: 2
294
+ }
295
+ }
296
+ };
297
+ // même résultat qu'avec un tableau à un élément
298
+ expect(productionENR.getPpv(productionElecEnr, 1)).toBe(2826.8015616000007);
299
+ });
300
+
301
+ test('should update ep conso with a coeff ep override', () => {
302
+ const productionElectricite = {
303
+ conso_elec_ac_ecs: 200,
304
+ conso_elec_ac_ch: 150,
305
+ conso_elec_ac_fr: 100,
306
+ conso_elec_ac_eclairage: 250,
307
+ conso_elec_ac_auxiliaire: 300
308
+ };
309
+ const conso = {
310
+ ep_conso: {
311
+ ep_conso_ecs: 1000,
312
+ ep_conso_ch: 500,
313
+ ep_conso_fr: 800,
314
+ ep_conso_eclairage: 900,
315
+ ep_conso_totale_auxiliaire: 1250,
316
+ ep_conso_5_usages: 5000,
317
+ ep_conso_5_usages_m2: 100
318
+ }
319
+ };
320
+
321
+ // coeff_ep_override = 2 : chaque poste est minoré de 2 * autoconso
322
+ productionENR.updateEPConso(productionElectricite, conso, 10, 2);
323
+
324
+ expect(conso.ep_conso).toStrictEqual({
325
+ ep_conso_ecs: 600, // 1000 - 2*200
326
+ ep_conso_ch: 200, // 500 - 2*150
327
+ ep_conso_fr: 600, // 800 - 2*100
328
+ ep_conso_eclairage: 400, // 900 - 2*250
329
+ ep_conso_totale_auxiliaire: 650, // 1250 - 2*300
330
+ ep_conso_5_usages: 3000, // 5000 - 2*1000
331
+ ep_conso_5_usages_m2: 300 // floor(3000 / 10)
332
+ });
333
+ });
334
+
335
+ test('should update ep conso with the default coeff ep (1.9) when no override', () => {
336
+ const productionElectricite = {
337
+ conso_elec_ac_ecs: 200,
338
+ conso_elec_ac_ch: 150,
339
+ conso_elec_ac_fr: 100,
340
+ conso_elec_ac_eclairage: 250,
341
+ conso_elec_ac_auxiliaire: 300
342
+ };
343
+ const conso = {
344
+ ep_conso: {
345
+ ep_conso_ecs: 1000,
346
+ ep_conso_ch: 500,
347
+ ep_conso_fr: 800,
348
+ ep_conso_eclairage: 900,
349
+ ep_conso_totale_auxiliaire: 1250,
350
+ ep_conso_5_usages: 5000,
351
+ ep_conso_5_usages_m2: 100
352
+ }
353
+ };
354
+
355
+ // pas d'override => coefficient par défaut 1.9
356
+ productionENR.updateEPConso(productionElectricite, conso, 10);
357
+
358
+ expect(conso.ep_conso).toStrictEqual({
359
+ ep_conso_ecs: 620, // 1000 - 1.9*200
360
+ ep_conso_ch: 215, // 500 - 1.9*150
361
+ ep_conso_fr: 610, // 800 - 1.9*100
362
+ ep_conso_eclairage: 425, // 900 - 1.9*250
363
+ ep_conso_totale_auxiliaire: 680, // 1250 - 1.9*300
364
+ ep_conso_5_usages: 3100, // 5000 - 1.9*1000
365
+ ep_conso_5_usages_m2: 310 // floor(3100 / 10)
366
+ });
367
+ });
368
+
369
+ test.each([
370
+ ['une installation absente', null],
371
+ ["l'absence de production photovoltaïque", { donnee_entree: { presence_production_pv: 0 } }]
372
+ ])('calculateEnr retourne des autoconsommations nulles pour %s', (_label, productionElecEnr) => {
373
+ const conso = { ef_conso: {}, ep_conso: {} };
374
+
375
+ const ret = productionENR.calculateEnr(productionElecEnr, conso, 10, 'maison', 1, 100);
376
+
377
+ expect(ret).toStrictEqual({
378
+ production_pv: 0,
379
+ conso_elec_ac: 0,
380
+ conso_elec_ac_ch: 0,
381
+ conso_elec_ac_ecs: 0,
382
+ conso_elec_ac_fr: 0,
383
+ conso_elec_ac_eclairage: 0,
384
+ conso_elec_ac_auxiliaire: 0,
385
+ conso_elec_ac_autre_usage: 0
386
+ });
387
+ });
388
+
389
+ test('calculateEnr calcule et applique les autoconsommations en présence de PV', () => {
390
+ const productionElecEnr = {
391
+ donnee_entree: { presence_production_pv: 1 },
392
+ panneaux_pv_collection: {
393
+ panneaux_pv: [{ nombre_module: 8, enum_orientation_pv_id: 1, enum_inclinaison_pv_id: 2 }]
394
+ }
395
+ };
396
+ const conso = {
397
+ ef_conso: {
398
+ conso_ecs: 1000,
399
+ conso_ch: 500,
400
+ conso_fr: 800,
401
+ conso_eclairage: 900,
402
+ conso_totale_auxiliaire: 1250,
403
+ conso_5_usages: 1500,
404
+ conso_5_usages_m2: 100
405
+ },
406
+ ep_conso: {
407
+ ep_conso_ecs: 3000,
408
+ ep_conso_ch: 1500,
409
+ ep_conso_fr: 2000,
410
+ ep_conso_eclairage: 1800,
411
+ ep_conso_totale_auxiliaire: 2500,
412
+ ep_conso_5_usages: 10000,
413
+ ep_conso_5_usages_m2: 100
414
+ },
415
+ sortie_par_energie_collection: {
416
+ sortie_par_energie: [
417
+ { enum_type_energie_id: '1', conso_5_usages: 1500, conso_ch: 1000, conso_ecs: 1500 }
418
+ ]
419
+ }
420
+ };
421
+
422
+ const ret = productionENR.calculateEnr(productionElecEnr, conso, 10, 'maison', 1, 100);
423
+
424
+ // valeurs de référence de régression
425
+ expect(ret).toStrictEqual({
426
+ production_pv: 2826.8015616000007,
427
+ conso_elec_ac: 403.3632077277818,
428
+ conso_elec_ac_ch: 17.146151231786686,
429
+ conso_elec_ac_ecs: 64.29806711920007,
430
+ conso_elec_ac_fr: 171.46151231786683,
431
+ conso_elec_ac_eclairage: 38.57884027152004,
432
+ conso_elec_ac_auxiliaire: 0,
433
+ conso_elec_ac_autre_usage: 21.14245810055866
434
+ });
435
+
436
+ // les consommations d'énergie finale sont bien minorées par l'autoconsommation
437
+ expect(conso.ef_conso.conso_ecs).toBeLessThan(1000);
438
+ expect(conso.ef_conso.conso_5_usages).toBeLessThan(1500);
439
+ });
252
440
  });
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+
3
+ /**
4
+ * Le module ne dépend que du mapping enum_zone_climatique_id -> libellé de zone.
5
+ * On le mocke afin d'isoler le test du contenu réel de `enums`.
6
+ */
7
+ vi.mock('./enums.js', () => ({
8
+ default: {
9
+ zone_climatique: {
10
+ 1: 'h1a',
11
+ 2: 'h1b',
12
+ 3: 'h1c',
13
+ 4: 'h2a',
14
+ 5: 'h2b',
15
+ 6: 'h2c',
16
+ 7: 'h2d',
17
+ 8: 'h3'
18
+ }
19
+ }
20
+ }));
21
+
22
+ const { default: calc_conso_eclairage } = await import('./16_conso_eclairage.js');
23
+
24
+ /**
25
+ * 16.1 Consommation d'éclairage (Cecl)
26
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §16.1
27
+ *
28
+ * Cecl = (C * Pecl * Nh) / 1000 avec C = 0.9 (interrupteur) et Pecl = 1.4 W/m²
29
+ * Nh = nombre d'heures de fonctionnement de l'éclairage, fonction de la zone climatique.
30
+ */
31
+ describe("Calcul de la consommation d'éclairage (Cecl)", () => {
32
+ // [enum_zone_climatique_id, libellé, Nh, Cecl attendu]
33
+ test.each([
34
+ ['1', 'h1a', 1500, (0.9 * 1.4 * 1500) / 1000],
35
+ ['2', 'h1b', 1445, (0.9 * 1.4 * 1445) / 1000],
36
+ ['3', 'h1c', 1476, (0.9 * 1.4 * 1476) / 1000],
37
+ ['4', 'h2a', 1500, (0.9 * 1.4 * 1500) / 1000],
38
+ ['5', 'h2b', 1531, (0.9 * 1.4 * 1531) / 1000],
39
+ ['6', 'h2c', 1566, (0.9 * 1.4 * 1566) / 1000],
40
+ ['7', 'h2d', 1566, (0.9 * 1.4 * 1566) / 1000],
41
+ ['8', 'h3', 1506, (0.9 * 1.4 * 1506) / 1000]
42
+ ])(
43
+ 'zone climatique %s (%s) : Nh=%d heures => Cecl attendu',
44
+ (zc_id, _libelle, _nh, cecl_attendu) => {
45
+ expect(calc_conso_eclairage(zc_id)).toBeCloseTo(cecl_attendu, 10);
46
+ }
47
+ );
48
+
49
+ test('la consommation est identique pour deux zones ayant le même Nh (h1a et h2a)', () => {
50
+ expect(calc_conso_eclairage('1')).toBeCloseTo(calc_conso_eclairage('4'), 10);
51
+ });
52
+
53
+ test("la consommation croît avec le nombre d'heures (h1b < h3 < h2c)", () => {
54
+ expect(calc_conso_eclairage('2')).toBeLessThan(calc_conso_eclairage('8'));
55
+ expect(calc_conso_eclairage('8')).toBeLessThan(calc_conso_eclairage('6'));
56
+ });
57
+
58
+ test('retourne NaN pour un identifiant de zone climatique inconnu', () => {
59
+ // enums.zone_climatique[zc_id] est undefined => Nh[undefined] est undefined => NaN
60
+ expect(calc_conso_eclairage('99')).toBeNaN();
61
+ });
62
+ });
@@ -0,0 +1,159 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+
3
+ /**
4
+ * Dépendances mockées pour isoler `calc_confort_ete` :
5
+ * - `enums.classe_inertie` : détermine le caractère « lourd » de l'inertie ;
6
+ * - `getKeyByValue` : réduit à un passe-plat renvoyant directement le libellé
7
+ * de confort (on assertera donc `enum_indicateur_confort_ete_id` = libellé) ;
8
+ * - `requestInput` : passe-plat lisant le champ demandé dans les données d'entrée.
9
+ */
10
+ vi.mock('./enums.js', () => ({
11
+ default: {
12
+ classe_inertie: { 1: 'légère', 2: 'moyenne', 3: 'lourde', 4: 'très lourde' },
13
+ indicateur_confort_ete: { 1: 'insuffisant', 2: 'moyen', 3: 'bon' }
14
+ }
15
+ }));
16
+
17
+ vi.mock('./utils.js', () => ({
18
+ getKeyByValue: (object, value) => value,
19
+ requestInput: (de, du, field) => de[field]
20
+ }));
21
+
22
+ const { default: calc_confort_ete } = await import('./2021_04_13_confort_ete.js');
23
+
24
+ /** Fabrique une baie vitrée : `enum_orientation_id` sert au caractère traversant,
25
+ * `orientation` et `type_fermeture` à la protection solaire. */
26
+ function baie({ orientationId, orientation = 'sud', typeFermeture = 'volet' }) {
27
+ return {
28
+ donnee_entree: {
29
+ enum_orientation_id: orientationId,
30
+ orientation,
31
+ type_fermeture: typeFermeture
32
+ },
33
+ donnee_utilisateur: {}
34
+ };
35
+ }
36
+
37
+ /** Fabrique un plancher haut avec son type d'adjacence et d'isolation. */
38
+ function plancherHaut({ typeAdjacence = 'extérieur', typeIsolation = 'isolé' }) {
39
+ return {
40
+ donnee_entree: { type_adjacence: typeAdjacence, type_isolation: typeIsolation },
41
+ donnee_utilisateur: {}
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Indicateur de confort d'été
47
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - annexe confort d'été
48
+ */
49
+ describe("calc_confort_ete - indicateur de confort d'été", () => {
50
+ test('inertie lourde + aspect traversant : niveau « bon »', () => {
51
+ // deux orientations distinctes => aspect_traversant = 1 ; inertie lourde = 1 => nv_bon = 2
52
+ const bv = [baie({ orientationId: '1' }), baie({ orientationId: '2' })];
53
+ const ph = [plancherHaut({ typeIsolation: 'isolé' })];
54
+
55
+ const ret = calc_confort_ete('3', bv, ph);
56
+
57
+ expect(ret.inertie_lourde).toBe(1);
58
+ expect(ret.aspect_traversant).toBe(1);
59
+ expect(ret.enum_indicateur_confort_ete_id).toBe('bon');
60
+ });
61
+
62
+ test('inertie « très lourde » également considérée comme lourde', () => {
63
+ const bv = [baie({ orientationId: '1' }), baie({ orientationId: '2' })];
64
+ const ph = [plancherHaut({})];
65
+
66
+ const ret = calc_confort_ete('4', bv, ph);
67
+
68
+ expect(ret.inertie_lourde).toBe(1);
69
+ });
70
+
71
+ test('une seule orientation : aspect non traversant', () => {
72
+ const bv = [baie({ orientationId: '1' }), baie({ orientationId: '1' })];
73
+ const ph = [plancherHaut({})];
74
+
75
+ const ret = calc_confort_ete('2', bv, ph);
76
+
77
+ expect(ret.aspect_traversant).toBe(0);
78
+ });
79
+
80
+ test('inertie légère sans aspect traversant : niveau « moyen »', () => {
81
+ // nv_bon = 0 (< 2) mais protections OK => moyen
82
+ const bv = [baie({ orientationId: '1' })];
83
+ const ph = [plancherHaut({})];
84
+
85
+ const ret = calc_confort_ete('1', bv, ph);
86
+
87
+ expect(ret.inertie_lourde).toBe(0);
88
+ expect(ret.enum_indicateur_confort_ete_id).toBe('moyen');
89
+ });
90
+
91
+ test("toiture non isolée donnant sur l'extérieur : niveau « insuffisant »", () => {
92
+ const bv = [baie({ orientationId: '1' }), baie({ orientationId: '2' })];
93
+ const ph = [plancherHaut({ typeAdjacence: 'extérieur', typeIsolation: 'non isolé' })];
94
+
95
+ const ret = calc_confort_ete('3', bv, ph);
96
+
97
+ expect(ret.isolation_toiture).toBe(0);
98
+ expect(ret.enum_indicateur_confort_ete_id).toBe('insuffisant');
99
+ });
100
+
101
+ test("toiture d'isolation inconnue donnant sur l'extérieur : isolation nulle", () => {
102
+ const bv = [baie({ orientationId: '1' })];
103
+ const ph = [plancherHaut({ typeAdjacence: 'extérieur', typeIsolation: 'inconnu' })];
104
+
105
+ const ret = calc_confort_ete('3', bv, ph);
106
+
107
+ expect(ret.isolation_toiture).toBe(0);
108
+ });
109
+
110
+ test('toiture non isolée mais non extérieure : isolation préservée', () => {
111
+ const bv = [baie({ orientationId: '1' }), baie({ orientationId: '2' })];
112
+ const ph = [plancherHaut({ typeAdjacence: 'local non chauffé', typeIsolation: 'non isolé' })];
113
+
114
+ const ret = calc_confort_ete('3', bv, ph);
115
+
116
+ expect(ret.isolation_toiture).toBe(1);
117
+ });
118
+
119
+ test('baie non nord sans fermeture : protection solaire nulle => « insuffisant »', () => {
120
+ const bv = [
121
+ baie({
122
+ orientationId: '1',
123
+ orientation: 'sud',
124
+ typeFermeture: 'abscence de fermeture pour la baie vitrée'
125
+ })
126
+ ];
127
+ const ph = [plancherHaut({})];
128
+
129
+ const ret = calc_confort_ete('3', bv, ph);
130
+
131
+ expect(ret.protection_solaire_exterieure).toBe(0);
132
+ expect(ret.enum_indicateur_confort_ete_id).toBe('insuffisant');
133
+ });
134
+
135
+ test('baie nord sans fermeture : ignorée, protection solaire préservée', () => {
136
+ const bv = [
137
+ baie({
138
+ orientationId: '1',
139
+ orientation: 'nord',
140
+ typeFermeture: 'abscence de fermeture pour la baie vitrée'
141
+ }),
142
+ baie({
143
+ orientationId: '2',
144
+ orientation: 'nord',
145
+ typeFermeture: 'abscence de fermeture pour la baie vitrée'
146
+ })
147
+ ];
148
+ const ph = [plancherHaut({})];
149
+
150
+ const ret = calc_confort_ete('3', bv, ph);
151
+
152
+ expect(ret.protection_solaire_exterieure).toBe(1);
153
+ });
154
+
155
+ test("le brasseur d'air est toujours absent (0) dans ce calcul", () => {
156
+ const ret = calc_confort_ete('3', [baie({ orientationId: '1' })], [plancherHaut({})]);
157
+ expect(ret.brasseur_air).toBe(0);
158
+ });
159
+ });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @returns {number}
3
3
  */
4
- function qualite_isol(dep, surface, seuil1, seuil2, seuil3) {
4
+ export function qualite_isol(dep, surface, seuil1, seuil2, seuil3) {
5
5
  /**
6
6
  * 1 - Très bonne
7
7
  * 2 - Bonne