@open3cl/engine 1.0.6 → 1.0.8

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 (72) hide show
  1. package/11_nadeq.spec.js +3 -2
  2. package/16.2_production_enr.spec.js +1 -0
  3. package/3.2.1_mur.spec.js +1 -0
  4. package/3.2.2_plancher_bas.spec.js +1 -0
  5. package/3.3_baie_vitree.spec.js +1 -0
  6. package/6.1_apport_gratuit.spec.js +1 -0
  7. package/7_inertie.spec.js +4 -3
  8. package/9_chauffage.spec.js +1 -0
  9. package/9_conso_ch.spec.js +1 -0
  10. package/9_generateur_ch.spec.js +1 -0
  11. package/conso.spec.js +1 -0
  12. package/core/assets/domain/synchronize-assets.spec.js +9 -9
  13. package/core/assets/domain/synchronize-c1-tables.spec.js +4 -4
  14. package/core/assets/domain/synchronize-dpe-ges-limit-values-tables.spec.js +6 -6
  15. package/core/assets/domain/synchronize-enum-tables.spec.js +10 -10
  16. package/core/assets/domain/synchronize-solicitations-tables.spec.js +6 -6
  17. package/core/assets/domain/synchronize-valeur-tables.spec.js +14 -14
  18. package/core/file/infrastructure/adapter/file.store.spec.js +5 -4
  19. package/core/tv/infrastructure/tvs.store.spec.js +3 -2
  20. package/core/util/infrastructure/object-util.spec.js +2 -1
  21. package/core/util/logger/log-service.js +47 -0
  22. package/features/dpe/domain/models/baie-ets.model.ts +12 -0
  23. package/features/dpe/domain/models/baie-vitree.model.ts +97 -0
  24. package/features/dpe/domain/models/climatisation.model.ts +25 -0
  25. package/features/dpe/domain/models/dpe.model.ts +194 -0
  26. package/features/dpe/domain/models/ets.model.ts +19 -0
  27. package/features/dpe/domain/models/installation-chauffage.model.ts +101 -0
  28. package/features/dpe/domain/models/installation-ecs.model.ts +76 -0
  29. package/features/dpe/domain/models/mur.model.ts +37 -0
  30. package/features/dpe/domain/models/plancher-bas.model.ts +38 -0
  31. package/features/dpe/domain/models/plancher-haut.model.ts +33 -0
  32. package/features/dpe/domain/models/pont-thermique.model.ts +21 -0
  33. package/features/dpe/domain/models/porte.model.ts +31 -0
  34. package/features/dpe/domain/models/production-elec-enr.model.ts +27 -0
  35. package/features/dpe/domain/models/sortie.model.ts +178 -0
  36. package/features/dpe/domain/models/type-habitation.model.js +8 -0
  37. package/features/dpe/domain/models/type-ventilation.model.js +8 -0
  38. package/features/dpe/domain/models/ventilation.model.ts +33 -0
  39. package/features/dpe/infrastructure/baieVitreeTv.store.js +292 -0
  40. package/features/dpe/infrastructure/baieVitreeTv.store.spec.js +352 -0
  41. package/features/dpe/infrastructure/tv.store.js +356 -0
  42. package/features/dpe/infrastructure/tv.store.spec.js +607 -0
  43. package/features/engine/domain/constants.js +1 -0
  44. package/features/engine/domain/contexte.builder.js +140 -0
  45. package/features/engine/domain/contexte.builder.spec.js +152 -0
  46. package/features/engine/domain/engine.service.js +139 -0
  47. package/features/engine/domain/engine.service.spec.js +7 -0
  48. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.js +292 -0
  49. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +484 -0
  50. package/features/engine/domain/enveloppe/deperdition-enveloppe.service.js +278 -0
  51. package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +282 -0
  52. package/features/engine/domain/enveloppe/deperdition.service.js +101 -0
  53. package/features/engine/domain/enveloppe/mur/deperdition-mur.service.js +168 -0
  54. package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +345 -0
  55. package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.js +169 -0
  56. package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +200 -0
  57. package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.js +136 -0
  58. package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +211 -0
  59. package/features/engine/domain/enveloppe/porte/deperdition-porte.service.js +56 -0
  60. package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +116 -0
  61. package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js +338 -0
  62. package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +442 -0
  63. package/features/engine/domain/models/contexte.model.ts +10 -0
  64. package/features/engine/domain/models/deperdition.model.ts +8 -0
  65. package/features/normalizer/domain/dpe-normalizer.service.js +24 -0
  66. package/features/normalizer/domain/dpe-normalizer.service.spec.js +3 -0
  67. package/ficheTechnique.spec.js +4 -3
  68. package/output.js +1 -0
  69. package/package.json +9 -8
  70. package/tv-v2.js +79121 -0
  71. package/utils.js +2 -2
  72. package/utils.spec.js +4 -3
@@ -0,0 +1,168 @@
1
+ import { logger } from '../../../../../core/util/logger/log-service.js';
2
+ import { inject } from 'dioma';
3
+ import { PRECISION } from '../../constants.js';
4
+ import { DeperditionService } from '../deperdition.service.js';
5
+ import { TvStore } from '../../../../dpe/infrastructure/tv.store.js';
6
+
7
+ /**
8
+ * Calcul des déperditions de l’enveloppe GV
9
+ * Chapitre 3.2.1 Calcul des Umur
10
+ *
11
+ * Méthode de calcul 3CL-DPE 2021
12
+ * Octobre 2021
13
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
14
+ */
15
+ export class DeperditionMurService extends DeperditionService {
16
+ /**
17
+ * @param tvStore {TvStore}
18
+ */
19
+ constructor(tvStore = inject(TvStore)) {
20
+ super(tvStore);
21
+ }
22
+
23
+ /**
24
+ * @param ctx {Contexte}
25
+ * @param murDE {MurDE}
26
+ * @return {MurDI}
27
+ */
28
+ execute(ctx, murDE) {
29
+ const umur0 = this.#umur0(murDE);
30
+ const umur = this.#umur(murDE, umur0, ctx);
31
+ const b = this.b({
32
+ enumTypeAdjacenceId: murDE.enum_type_adjacence_id,
33
+ surfaceAiu: murDE.surface_aiu,
34
+ surfaceAue: murDE.surface_aue,
35
+ enumCfgIsolationLncId: murDE.enum_cfg_isolation_lnc_id,
36
+ tvCoefReductionDeperditionId: murDE.tv_coef_reduction_deperdition_id,
37
+ zoneClimatiqueId: ctx.zoneClimatiqueId
38
+ });
39
+
40
+ /** @type {MurDI} */
41
+ return { umur0, umur, b };
42
+ }
43
+
44
+ /**
45
+ * @param murDE {MurDE}
46
+ * @param umur0 {number}
47
+ * @param ctx {Contexte}
48
+ * @return {number|undefined}
49
+ */
50
+ #umur(murDE, umur0, ctx) {
51
+ // On determine umur_nu (soit umur0 soit 2.5 comme valeur minimale forfaitaire)
52
+ const umurNu = Math.min(umur0, 2.5);
53
+
54
+ const enumPeriodeIsolationId = this.getEnumPeriodeIsolationId(
55
+ murDE.enum_periode_isolation_id,
56
+ ctx
57
+ );
58
+
59
+ // Selon l'isolation, on applique un calcul au mur nu pour simuler son isolation
60
+ let umur;
61
+ switch (murDE.enum_methode_saisie_u_id) {
62
+ case '1': // non isolé
63
+ umur = umurNu;
64
+ break;
65
+ case '2': // isolation inconnue (table forfaitaire)
66
+ umur = Math.min(
67
+ umurNu,
68
+ this.tvStore.getUmur(ctx.enumPeriodeConstructionId, ctx.zoneClimatiqueId, ctx.effetJoule)
69
+ );
70
+ break;
71
+ case '7': // année d'isolation différente de l'année de construction
72
+ case '8': // année de construction saisie
73
+ umur = Math.min(
74
+ umurNu,
75
+ this.tvStore.getUmur(enumPeriodeIsolationId, ctx.zoneClimatiqueId, ctx.effetJoule)
76
+ );
77
+ break;
78
+ case '3': // epaisseur isolation saisie justifiée par mesure ou observation
79
+ case '4': {
80
+ // epaisseur isolation saisie (en cm)
81
+ const epaisseurIsolation = murDE.epaisseur_isolation;
82
+ if (epaisseurIsolation) {
83
+ umur = 1 / (1 / umurNu + (murDE.epaisseur_isolation * 0.01) / 0.04);
84
+ } else {
85
+ logger.warn(
86
+ `Le mur ${murDE.description} ne possède pas de donnée d'entrée pour epaisseur_isolation
87
+ alors que methode_saisie_u = 'epaisseur isolation saisie'`
88
+ );
89
+ umur = Math.min(umur0, 2.5);
90
+ }
91
+ break;
92
+ }
93
+ case '5':
94
+ case '6':
95
+ // resistance isolation saisie
96
+ umur = 1 / (1 / umurNu + murDE.resistance_isolation);
97
+ break;
98
+ default:
99
+ // saisie direct de la valeur de u
100
+ umur = murDE.umur_saisi;
101
+ break;
102
+ }
103
+
104
+ return Math.round(parseFloat(umur) * PRECISION) / PRECISION;
105
+ }
106
+
107
+ /**
108
+ * @param murDE {MurDE}
109
+ * @return {number|undefined}
110
+ */
111
+ #umur0(murDE) {
112
+ let umur0;
113
+ switch (murDE.enum_methode_saisie_u0_id) {
114
+ case '1':
115
+ umur0 = this.tvStore.getUmur0(murDE.enum_materiaux_structure_mur_id);
116
+ break;
117
+ case '2':
118
+ umur0 = this.tvStore.getUmur0(
119
+ murDE.enum_materiaux_structure_mur_id,
120
+ murDE.epaisseur_structure
121
+ );
122
+ break;
123
+ case '5':
124
+ // Valeur u saisie directement umur0 vide
125
+ return;
126
+ default:
127
+ // Valeur saisie
128
+ return murDE.umur0_saisi;
129
+ }
130
+
131
+ /**
132
+ * Pour l’ensemble des parois, la présence d’un doublage apporte une résistance thermique supplémentaire
133
+ */
134
+ switch (murDE.enum_type_doublage_id) {
135
+ case '3':
136
+ // doublage indéterminé ou lame d'air inf 15 mm
137
+ umur0 = 1 / (1 / umur0 + 0.1);
138
+ break;
139
+ case '4': // doublage indéterminé ou lame d'air sup 15 mm
140
+ case '5': // doublage connu (plâtre brique bois)
141
+ umur0 = 1 / (1 / umur0 + 0.21);
142
+ break;
143
+ default:
144
+ // absence de doublage ou inconnu
145
+ break;
146
+ }
147
+
148
+ /**
149
+ * Pour les parois dites « anciennes », c’est-à-dire constituées de matériaux traditionnels à savoir pierres, terre, mur à
150
+ * colombage, brique ancienne, la présence d’un enduit isolant n’est pas considérée comme une isolation. Cependant,
151
+ * cet enduit apporte une correction d’isolation qu’il faut prendre en compte
152
+ *
153
+ * la paroi est une paroi ancienne sur laquelle a été appliquée un enduit isolant (Renduit=0,7m².K.W-1) 0 : non 1 : oui.
154
+ * (Attention ! nom de propriété pas tout à fait explicite)
155
+ * OBSOLETE > remplacé par enduit_isolant_paroi_ancienne
156
+ */
157
+ if (murDE.enduit_isolant_paroi_ancienne) {
158
+ umur0 = 1 / (1 / umur0 + 0.7);
159
+ }
160
+
161
+ /**
162
+ * @todo Comportement non clairement documenté
163
+ * Dans beaucoup de cas umur0 retourné en le min entre 2.5 et la valeur trouvée,
164
+ * mais pas de documentation sur ce point dans la méthode 3CL
165
+ */
166
+ return Math.min(2.5, Math.round(parseFloat(umur0) * PRECISION) / PRECISION);
167
+ }
168
+ }
@@ -0,0 +1,345 @@
1
+ import corpus from '../../../../../../test/corpus-sano.json';
2
+ import { getAdemeFileJson } from '../../../../../../test/test-helpers.js';
3
+ import { DeperditionMurService } from './deperdition-mur.service.js';
4
+ import { ContexteBuilder } from '../../contexte.builder.js';
5
+ import { DpeNormalizerService } from '../../../../normalizer/domain/dpe-normalizer.service.js';
6
+ import { TvStore } from '../../../../dpe/infrastructure/tv.store.js';
7
+ import { beforeEach, describe, expect, test } from 'vitest';
8
+
9
+ /** @type {DeperditionMurService} **/
10
+ let service;
11
+
12
+ /** @type {DpeNormalizerService} **/
13
+ let normalizerService;
14
+
15
+ /** @type {ContexteBuilder} **/
16
+ let contexteBuilder;
17
+
18
+ describe('Calcul de déperdition des murs', () => {
19
+ beforeEach(() => {
20
+ service = new DeperditionMurService(new TvStore());
21
+ normalizerService = new DpeNormalizerService();
22
+ contexteBuilder = new ContexteBuilder();
23
+ });
24
+
25
+ describe('Determination de uMur et uMur0', () => {
26
+ test.each([
27
+ {
28
+ label: 'cas 2187E1039187C mur 1',
29
+ enumMateriauxStructureMurId: '2',
30
+ enumMethodeSaisieUId: '8',
31
+ enumMethodeSaisieU0Id: '2',
32
+ paroiAncienne: false,
33
+ epaisseurStructure: 60,
34
+ enumTypeIsolationId: '9',
35
+ enumTypeDoublageId: '2',
36
+ enumPeriodeConstructionId: '2',
37
+ zoneClimatiqueId: '3',
38
+ umurExpected: 1,
39
+ umur0Expected: 1.8
40
+ },
41
+ {
42
+ label:
43
+ "(2287E0577966W) Mur 2 Sud, Est, Ouest (p1) - Mur en blocs de béton creux d'épaisseur ≥ 25 cm avec un doublage rapporté donnant sur l'extérieur",
44
+ enumMateriauxStructureMurId: '12',
45
+ enumMethodeSaisieUId: '7',
46
+ enumMethodeSaisieU0Id: '2',
47
+ paroiAncienne: false,
48
+ enumTypeIsolationId: '1',
49
+ enumTypeDoublageId: '4',
50
+ enumPeriodeConstructionId: '1',
51
+ enumIsolationId: '5',
52
+ epaisseurStructure: 25,
53
+ zoneClimatiqueId: '3',
54
+ umurExpected: 0.8, // 0.7 dans le DPE original, mais certainement lié à une erreur
55
+ umur0Expected: 1.55091
56
+ }
57
+ ])(
58
+ '$label',
59
+ ({
60
+ enumMateriauxStructureMurId,
61
+ enumMethodeSaisieUId,
62
+ enumMethodeSaisieU0Id,
63
+ enumTypeDoublageId,
64
+ enumTypeIsolationId,
65
+ umurSaisi,
66
+ epaisseurStructure,
67
+ paroiAncienne,
68
+ resistanceIsolation,
69
+ umurExpected,
70
+ umur0Expected,
71
+ enumPeriodeConstructionId = 1,
72
+ enumIsolationId,
73
+ effetJoule = false,
74
+ zoneClimatiqueId = '1'
75
+ }) => {
76
+ /** @type {Contexte} */
77
+ const ctx = {
78
+ effetJoule,
79
+ enumPeriodeConstructionId,
80
+ zoneClimatiqueId
81
+ };
82
+
83
+ /** @type {MurDE} */
84
+ const de = {
85
+ enum_materiaux_structure_mur_id: enumMateriauxStructureMurId,
86
+ enum_methode_saisie_u_id: enumMethodeSaisieUId,
87
+ enum_methode_saisie_u0_id: enumMethodeSaisieU0Id,
88
+ umur_saisi: umurSaisi,
89
+ resistance_isolation: resistanceIsolation,
90
+ paroi_ancienne: paroiAncienne,
91
+ epaisseur_structure: epaisseurStructure,
92
+ enum_type_doublage_id: enumTypeDoublageId,
93
+ enum_type_isolation_id: enumTypeIsolationId,
94
+ enum_periode_isolation_id: enumIsolationId
95
+ };
96
+
97
+ const di = service.execute(ctx, de);
98
+ expect(di.umur0).toBe(umur0Expected);
99
+ expect(di.umur).toBeCloseTo(umurExpected);
100
+ }
101
+ );
102
+
103
+ test("Mur 1 Sud, Est, Ouest (p1) - Mur en blocs de béton pleins d'épaisseur ≤ 20 cm avec isolation intérieure (10 cm) donnant sur l'extérieur", () => {
104
+ /** @type {Contexte} */
105
+ const ctx = {
106
+ effetJoule: false,
107
+ enumPeriodeConstructionId: '6',
108
+ zoneClimatiqueId: '3'
109
+ };
110
+ /** @type {MurDE} */
111
+ const de = {
112
+ enum_materiaux_structure_mur_id: '11',
113
+ enum_methode_saisie_u_id: '3',
114
+ enum_methode_saisie_u0_id: '2',
115
+ paroi_ancienne: false,
116
+ epaisseur_structure: 20,
117
+ enum_type_doublage_id: '2',
118
+ enum_type_isolation_id: '3',
119
+ epaisseur_isolation: 10
120
+ };
121
+
122
+ const di = service.execute(ctx, de);
123
+ expect(di.umur0).toBe(2.5);
124
+ expect(di.umur).toBeCloseTo(0.3448275862068969);
125
+ });
126
+
127
+ test('Mur 1 Nord - Mur en pierre de taille et moellons avec remplissage tout venant d'épaisseur 70 cm avec isolation intérieure (10 cm) donnant sur l'extérieur', () => {
128
+ /** @type {Contexte} */
129
+ const ctx = {
130
+ effetJoule: false,
131
+ enumPeriodeConstructionId: '6',
132
+ zoneClimatiqueId: '3'
133
+ };
134
+ /** @type {MurDE} */
135
+ const de = {
136
+ enum_materiaux_structure_mur_id: '3',
137
+ enum_methode_saisie_u_id: '3',
138
+ enum_methode_saisie_u0_id: '2',
139
+ paroi_ancienne: true,
140
+ epaisseur_structure: 70,
141
+ enum_type_doublage_id: '2',
142
+ enum_type_isolation_id: '3',
143
+ epaisseur_isolation: 10
144
+ };
145
+
146
+ const di = service.execute(ctx, de);
147
+ expect(di.umur0).toBe(1.45);
148
+ expect(di.umur).toBeCloseTo(0.31351351351351353);
149
+ });
150
+
151
+ test("(2387E0430619S) Mur 3 Nord, Est (p1) - Mur en placoplatre isolé par l'intérieur (environ 10 cm) avec isolation intérieure donnant sur des circulations avec ouverture directe sur l'extérieur", () => {
152
+ /** @type {Contexte} */
153
+ const ctx = {
154
+ effetJoule: false,
155
+ enumPeriodeConstructionId: '6',
156
+ zoneClimatiqueId: '3'
157
+ };
158
+ /** @type {MurDE} */
159
+ const de = {
160
+ enum_materiaux_structure_mur_id: '23',
161
+ enum_methode_saisie_u_id: '9',
162
+ enum_methode_saisie_u0_id: '5',
163
+ paroi_ancienne: false,
164
+ enum_type_doublage_id: '2',
165
+ enum_type_isolation_id: '3',
166
+ umur_saisi: 0.32
167
+ };
168
+
169
+ const di = service.execute(ctx, de);
170
+ expect(di.umur0).toBeUndefined();
171
+ expect(di.umur).toBeCloseTo(0.32);
172
+ });
173
+
174
+ test("Mur 1 Nord, Sud, Ouest (p1) - Mur en pierre de taille et moellons avec remplissage tout venant d'épaisseur 50 cm non isolé donnant sur l'extérieur", () => {
175
+ /** @type {Contexte} */
176
+ const ctx = {
177
+ effetJoule: false,
178
+ enumPeriodeConstructionId: '6',
179
+ zoneClimatiqueId: '3'
180
+ };
181
+ /** @type {MurDE} */
182
+ const de = {
183
+ enum_materiaux_structure_mur_id: '3',
184
+ enum_methode_saisie_u_id: '1',
185
+ enum_methode_saisie_u0_id: '2',
186
+ enduit_isolant_paroi_ancienne: true,
187
+ epaisseur_structure: 50,
188
+ enum_type_doublage_id: '2',
189
+ enum_type_isolation_id: '2'
190
+ };
191
+
192
+ const di = service.execute(ctx, de);
193
+ expect(di.umur0).toBeCloseTo(0.81545);
194
+ expect(di.umur).toBeCloseTo(0.81545);
195
+ });
196
+
197
+ test("Mur 8 Est - Mur en pierre de taille et moellons avec remplissage tout venant d'épaisseur 50 cm avec un doublage rapporté non isolé donnant sur l'extérieur", () => {
198
+ /** @type {Contexte} */
199
+ const ctx = {
200
+ effetJoule: false,
201
+ enumPeriodeConstructionId: '6',
202
+ zoneClimatiqueId: '3'
203
+ };
204
+ /** @type {MurDE} */
205
+ const de = {
206
+ enum_materiaux_structure_mur_id: '3',
207
+ enum_methode_saisie_u_id: '1',
208
+ enum_methode_saisie_u0_id: '2',
209
+ paroi_ancienne: true,
210
+ epaisseur_structure: 50,
211
+ enum_type_doublage_id: '3',
212
+ enum_type_isolation_id: '2'
213
+ };
214
+
215
+ const di = service.execute(ctx, de);
216
+ expect(di.umur0).toBeCloseTo(1.59664);
217
+ expect(di.umur).toBeCloseTo(1.59664);
218
+ });
219
+
220
+ test('(2287E0577966W) umur 0.7 au lieu de 0.8', () => {
221
+ /** @type {Contexte} */
222
+ const ctx = {
223
+ effetJoule: true,
224
+ enumPeriodeConstructionId: '1',
225
+ zoneClimatiqueId: '3'
226
+ };
227
+ /** @type {MurDE} */
228
+ const de = {
229
+ enum_materiaux_structure_mur_id: '12',
230
+ enum_methode_saisie_u_id: '7',
231
+ enum_methode_saisie_u0_id: '2',
232
+ paroi_ancienne: false,
233
+ epaisseur_structure: 25,
234
+ enum_type_doublage_id: '4',
235
+ enum_type_isolation_id: '1',
236
+ enum_periode_isolation_id: '5'
237
+ };
238
+
239
+ const di = service.execute(ctx, de);
240
+ expect(di.umur0).toBeCloseTo(1.55091);
241
+ expect(di.umur).toBeCloseTo(0.7); // 0.7 dans le DPE d'origine, comme si effet joule
242
+ });
243
+
244
+ test("(2387E0430619S) Mur 3 Nord, Est (p1) - Mur en placoplatre isolé par l'intérieur (environ 10 cm) avec isolation intérieure donnant sur des circulations avec ouverture directe sur l'extérieur", () => {
245
+ /** @type {Contexte} */
246
+ const ctx = {
247
+ effetJoule: true,
248
+ enumPeriodeConstructionId: '1',
249
+ zoneClimatiqueId: '3'
250
+ };
251
+ /** @type {MurDE} */
252
+ const de = {
253
+ enum_materiaux_structure_mur_id: '23',
254
+ enum_methode_saisie_u_id: '9',
255
+ enum_methode_saisie_u0_id: '5',
256
+ paroi_ancienne: false,
257
+ umur_saisi: 0.32,
258
+ epaisseur_structure: 25,
259
+ enum_type_doublage_id: '2',
260
+ enum_type_isolation_id: '3'
261
+ };
262
+
263
+ const di = service.execute(ctx, de);
264
+ expect(di.umur0).toBeUndefined(); // umur saisie directement
265
+ expect(di.umur).toBeCloseTo(0.32);
266
+ });
267
+
268
+ test('(2287E1724516Y) Mur 4 Nord, Sud (p1) - Mur en pan de bois sans remplissage tout venant d'épaisseur 18 cm avec isolation intérieure (R=2.5m².K/W) donnant sur l'extérieur', () => {
269
+ /** @type {Contexte} */
270
+ const ctx = {
271
+ effetJoule: false,
272
+ enumPeriodeConstructionId: '1',
273
+ zoneClimatiqueId: '3'
274
+ };
275
+ /** @type {MurDE} */
276
+ const de = {
277
+ enum_materiaux_structure_mur_id: '5',
278
+ enum_methode_saisie_u_id: '6',
279
+ enum_methode_saisie_u0_id: '2',
280
+ enduit_isolant_paroi_ancienne: true,
281
+ epaisseur_structure: 18,
282
+ resistance_isolation: 2.5,
283
+ enum_type_doublage_id: '2',
284
+ enum_type_isolation_id: '3'
285
+ };
286
+
287
+ const di = service.execute(ctx, de);
288
+ expect(di.umur0).toBeCloseTo(0.82983999999999991);
289
+ expect(di.umur).toBeCloseTo(0.26990177584075975);
290
+ });
291
+
292
+ test('(2187E0982013C) Mur Nord, Sud, Est, Ouest - Mur en pierre de taille et moellons avec remplissage tout venant d'épaisseur 50 cm non isolé donnant sur l'extérieur', () => {
293
+ /** @type {Contexte} */
294
+ const ctx = {
295
+ effetJoule: false,
296
+ enumPeriodeConstructionId: '1',
297
+ zoneClimatiqueId: '3'
298
+ };
299
+ /** @type {MurDE} */
300
+ const de = {
301
+ enum_type_adjacence_id: '1',
302
+ enum_materiaux_structure_mur_id: '3',
303
+ enum_methode_saisie_u_id: '1',
304
+ enum_methode_saisie_u0_id: '2',
305
+ enduit_isolant_paroi_ancienne: true,
306
+ epaisseur_structure: 18,
307
+ resistance_isolation: 2.5,
308
+ enum_type_doublage_id: '2',
309
+ enum_type_isolation_id: '2'
310
+ };
311
+
312
+ const di = service.execute(ctx, de);
313
+ expect(di.umur0).toBeCloseTo(0.81545);
314
+ expect(di.umur).toBeCloseTo(0.81545);
315
+ });
316
+ });
317
+
318
+ describe("Test d'intégration de mur", () => {
319
+ test.each(corpus)('vérification des DI des murs pour dpe %s', (ademeId) => {
320
+ let dpeRequest = getAdemeFileJson(ademeId);
321
+ dpeRequest = normalizerService.normalize(dpeRequest);
322
+
323
+ /** @type {Contexte} */
324
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
325
+
326
+ const murs = dpeRequest.logement.enveloppe.mur_collection?.mur || [];
327
+
328
+ murs.forEach((m) => {
329
+ // dans ces cas de figure, les données paroi_ancienne sont bien traitées
330
+ if (['2187E0982013C', '2287E2336469P', '2387E0045247S'].includes(ademeId)) {
331
+ m.donnee_entree.enduit_isolant_paroi_ancienne = m.donnee_entree.paroi_ancienne;
332
+ }
333
+ const di = service.execute(ctx, m.donnee_entree);
334
+
335
+ if (m.donnee_intermediaire.umur0) {
336
+ expect(di.umur0).toBeCloseTo(m.donnee_intermediaire.umur0, 2);
337
+ } else {
338
+ expect(di.umur0).toBeUndefined();
339
+ }
340
+ expect(di.umur).toBeCloseTo(m.donnee_intermediaire.umur, 2);
341
+ expect(di.b).toBeCloseTo(m.donnee_intermediaire.b, 2);
342
+ });
343
+ });
344
+ });
345
+ });
@@ -0,0 +1,169 @@
1
+ import { inject } from 'dioma';
2
+ import { PRECISION } from '../../constants.js';
3
+ import { DeperditionService } from '../deperdition.service.js';
4
+ import { TvStore } from '../../../../dpe/infrastructure/tv.store.js';
5
+
6
+ /**
7
+ * Calcul des déperditions de l’enveloppe GV
8
+ * Chapitre 3.2.2 Calcul des planchers bas
9
+ *
10
+ * Méthode de calcul 3CL-DPE 2021
11
+ * Octobre 2021
12
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
13
+ */
14
+ export class DeperditionPlancherBasService extends DeperditionService {
15
+ /**
16
+ * @param tvStore {TvStore}
17
+ */
18
+ constructor(tvStore = inject(TvStore)) {
19
+ super(tvStore);
20
+ }
21
+
22
+ /**
23
+ * @param ctx {Contexte}
24
+ * @param pbDE {PlancherBasDE}
25
+ * @param plancherBas {PlancherBas[]}
26
+ * @return {PlancherBasDI}
27
+ */
28
+ execute(ctx, pbDE, plancherBas) {
29
+ const upb0 = this.#upb0(pbDE);
30
+ const upb = this.#upb(pbDE, upb0, ctx);
31
+ const upb_final = this.#upbFinal(pbDE, upb, ctx, plancherBas);
32
+ const b = this.b({
33
+ enumTypeAdjacenceId: pbDE.enum_type_adjacence_id,
34
+ surfaceAiu: pbDE.surface_aiu,
35
+ surfaceAue: pbDE.surface_aue,
36
+ enumCfgIsolationLncId: pbDE.enum_cfg_isolation_lnc_id,
37
+ zoneClimatiqueId: ctx.zoneClimatiqueId
38
+ });
39
+
40
+ /** @type {PlancherBasDI} */
41
+ return { upb0, upb, upb_final, b };
42
+ }
43
+
44
+ /**
45
+ * @param pbDE {PlancherBasDE}
46
+ * @param upb0 {number}
47
+ * @param ctx {Contexte}
48
+ * @return {number|undefined}
49
+ */
50
+ #upb(pbDE, upb0, ctx) {
51
+ // On determine upb_nu (soit upb0 soit 2 comme valeur minimale forfaitaire)
52
+ const upbNu = Math.min(upb0, 2);
53
+
54
+ const enumPeriodeIsolationId = this.getEnumPeriodeIsolationId(
55
+ pbDE.enum_periode_isolation_id,
56
+ ctx
57
+ );
58
+
59
+ // Selon l'isolation, on applique un calcul au upb nu pour simuler son isolation
60
+ let upb;
61
+ switch (pbDE.enum_methode_saisie_u_id) {
62
+ case '1': // non isolé
63
+ upb = upbNu;
64
+ break;
65
+ case '2': // isolation inconnue (table forfaitaire)
66
+ case '7': // année d'isolation différente de l'année de construction
67
+ case '8': // année de construction saisie
68
+ upb = Math.min(
69
+ upbNu,
70
+ this.tvStore.getUpb(enumPeriodeIsolationId, ctx.zoneClimatiqueId, ctx.effetJoule)
71
+ );
72
+ break;
73
+ case '3': // epaisseur isolation saisie justifiée par mesure ou observation
74
+ case '4': // epaisseur isolation saisie (en cm)
75
+ upb = 1 / (1 / upbNu + (pbDE.epaisseur_isolation * 0.01) / 0.042);
76
+ break;
77
+ case '5':
78
+ case '6': // resistance isolation saisie
79
+ upb = 1 / (1 / upbNu + pbDE.resistance_isolation);
80
+ break;
81
+ default: // saisie direct de la valeur de u
82
+ upb = pbDE.upb_saisi;
83
+ break;
84
+ }
85
+
86
+ return Math.round(parseFloat(upb) * PRECISION) / PRECISION;
87
+ }
88
+
89
+ /**
90
+ * @param pbDE {PlancherBasDE}
91
+ * @return {number|undefined}
92
+ */
93
+ #upb0(pbDE) {
94
+ let upb0;
95
+ switch (pbDE.enum_methode_saisie_u0_id) {
96
+ case '1': // 'type de paroi inconnu (valeur par défaut)'
97
+ case '2': // 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire'
98
+ upb0 = this.tvStore.getUpb0(pbDE.enum_type_plancher_bas_id);
99
+ break;
100
+ case '5': // 'u0 non saisi car le u est saisi connu et justifié.'
101
+ return;
102
+ default: // Valeur saisie
103
+ return pbDE.upb0_saisi;
104
+ }
105
+
106
+ return upb0;
107
+ }
108
+
109
+ /**
110
+ * Pour les vides sanitaires, les sous-sol non chauffés et terre-plein, le calcul des déperditions se fait avec un coefficient
111
+ * Ue en remplacement de Upb.
112
+ *
113
+ * @param pbDE {PlancherBasDE}
114
+ * @param upb {number}
115
+ * @param ctx {Contexte}
116
+ * @param plancherBas {PlancherBas[]}
117
+ * @return {number|undefined}
118
+ */
119
+ #upbFinal(pbDE, upb, ctx, plancherBas) {
120
+ if (pbDE.calcul_ue === 1) {
121
+ return pbDE.ue;
122
+ }
123
+
124
+ /**
125
+ * 3 - vide sanitaire
126
+ * 5 - terre-plein
127
+ * 6 - sous-sol non chauffé
128
+ */
129
+ if (!['3', '5', '6'].includes(pbDE.enum_type_adjacence_id)) {
130
+ return upb;
131
+ }
132
+
133
+ /**
134
+ * La surface Ue est la surface de tous les planchers bas ayant le même type d'adjacence
135
+ * Le périmètre Ue est le périmètre de tous les planchers bas ayant le même type d'adjacence
136
+ */
137
+ const { surfaceUe, perimetreUe } = plancherBas
138
+ .filter(
139
+ (plancherBas) =>
140
+ pbDE.enum_type_adjacence_id === plancherBas.donnee_entree.enum_type_adjacence_id
141
+ )
142
+ .reduce(
143
+ (acc, plancherBas) => {
144
+ acc.surfaceUe +=
145
+ plancherBas.donnee_entree.surface_ue || plancherBas.donnee_entree.surface_paroi_opaque;
146
+ acc.perimetreUe += plancherBas.donnee_entree.perimetre_ue || 0;
147
+ return acc;
148
+ },
149
+ { surfaceUe: 0, perimetreUe: 0 }
150
+ );
151
+
152
+ const available2sp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20];
153
+ let dsp = perimetreUe ? Math.round((2 * surfaceUe) / perimetreUe) : 1;
154
+
155
+ // Recherche de la valeur la plus proche dans les valeurs disponibles
156
+ dsp = available2sp.reduce((prev, curr) => {
157
+ return Math.abs(curr - dsp) < Math.abs(prev - dsp) ? curr : prev;
158
+ });
159
+
160
+ const upbFinal = this.tvStore.getUeByUpd(
161
+ pbDE.enum_type_adjacence_id,
162
+ ctx.enumPeriodeConstructionId,
163
+ dsp,
164
+ upb
165
+ );
166
+
167
+ return Math.round(parseFloat(upbFinal) * PRECISION) / PRECISION;
168
+ }
169
+ }