@open3cl/engine 1.8.13 → 1.8.15

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.
@@ -1,6 +1,27 @@
1
1
  import { beforeEach, describe, expect, test, vi } from 'vitest';
2
- import { ChTvStore } from '../../../dpe/infrastructure/ch/chTv.store.js';
3
- import { EmetteurChService } from './emetteur-ch.service.js';
2
+
3
+ /**
4
+ * Dépendances mockées :
5
+ * - `bug_for_bug_compat` (utils.js) : piloté par le test ;
6
+ * - `logger` : traces de déduction de la période des émetteurs (issue #220).
7
+ */
8
+ const state = vi.hoisted(() => ({ bug: false }));
9
+
10
+ vi.mock('../../../../utils.js', async (importOriginal) => ({
11
+ ...(await importOriginal()),
12
+ get bug_for_bug_compat() {
13
+ return state.bug;
14
+ }
15
+ }));
16
+
17
+ vi.mock('../../../../core/util/logger/log-service.js', () => ({
18
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }
19
+ }));
20
+
21
+ const { ChTvStore } = await import('../../../dpe/infrastructure/ch/chTv.store.js');
22
+ const { EmetteurChService, PERIODES_INSTALLATION_EMETTEUR } =
23
+ await import('./emetteur-ch.service.js');
24
+ const { logger } = await import('../../../../core/util/logger/log-service.js');
4
25
 
5
26
  /** @type {EmetteurChService} **/
6
27
  let service;
@@ -10,6 +31,7 @@ let chTvStore;
10
31
 
11
32
  describe('Calcul des caractéristiques des générateurs de chauffage', () => {
12
33
  beforeEach(() => {
34
+ state.bug = false;
13
35
  chTvStore = new ChTvStore();
14
36
  service = new EmetteurChService(chTvStore);
15
37
  });
@@ -37,6 +59,19 @@ describe('Calcul des caractéristiques des générateurs de chauffage', () => {
37
59
  ).toBe(3);
38
60
  });
39
61
 
62
+ test("Période par défaut (déduite) utilisée uniquement si la période n'est pas saisie", () => {
63
+ expect(
64
+ service.periodeInstallationEmetteur({ anneeConstruction: 1850 }, { donnee_entree: {} }, 3)
65
+ ).toBe(3);
66
+ expect(
67
+ service.periodeInstallationEmetteur(
68
+ { anneeConstruction: 1850 },
69
+ { donnee_entree: { enum_periode_installation_emetteur_id: 1 } },
70
+ 3
71
+ )
72
+ ).toBe(1);
73
+ });
74
+
40
75
  test('Détermination des températures de fonctionnement à 30 et 100% de charge', () => {
41
76
  vi.spyOn(chTvStore, 'temperatureFonctionnement')
42
77
  .mockReturnValueOnce(25)
@@ -76,3 +111,206 @@ describe('Calcul des caractéristiques des générateurs de chauffage', () => {
76
111
  expect(chTvStore.temperatureFonctionnement).toHaveBeenCalledWith('100', 80, 2, 2);
77
112
  });
78
113
  });
114
+
115
+ /**
116
+ * Issue #220 - période d'installation des émetteurs absente du DPE.
117
+ * Mode strict : année de construction. Mode bug_for_bug_compat : période retrouvée à partir des
118
+ * temp_fonc_30 / temp_fonc_100 d'origine du DPE.
119
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
120
+ */
121
+ describe('Déduction de la période des émetteurs depuis les données du DPE (issue #220)', () => {
122
+ /**
123
+ * Table de test contrôlée (chaudière à condensation) :
124
+ * [température de distribution][période] -> [temp_fonc_30, temp_fonc_100].
125
+ */
126
+ const TABLE = {
127
+ 2: { 1: [32, 60], 2: [24.5, 35], 3: [24.5, 35] },
128
+ 3: { 1: [38, 80], 2: [35, 70], 3: [32, 60] },
129
+ 4: { 1: [38, 80], 2: [35, 70], 3: [35, 70] }
130
+ };
131
+
132
+ const generateurDE = { enum_type_generateur_ch_id: '97' };
133
+
134
+ function emetteur(tempDistribution, periode) {
135
+ const de = { enum_temp_distribution_ch_id: tempDistribution };
136
+ if (periode) de.enum_periode_installation_emetteur_id = periode;
137
+ return { donnee_entree: de };
138
+ }
139
+
140
+ beforeEach(() => {
141
+ state.bug = true;
142
+ vi.mocked(logger.info).mockReset();
143
+ vi.mocked(logger.warn).mockReset();
144
+ chTvStore = new ChTvStore();
145
+ service = new EmetteurChService(chTvStore);
146
+ vi.spyOn(chTvStore, 'temperatureFonctionnement').mockImplementation(
147
+ (pourcentage, _type, tempDistribution, periode) =>
148
+ TABLE[tempDistribution]?.[periode]?.[pourcentage === '30' ? 0 : 1]
149
+ );
150
+ });
151
+
152
+ test('périodes candidates ordonnées de la plus ancienne à la plus récente', () => {
153
+ expect(PERIODES_INSTALLATION_EMETTEUR).toStrictEqual([1, 2, 3]);
154
+ });
155
+
156
+ test('T1 strict : bug_for_bug_compat désactivé -> aucune déduction (année de construction)', () => {
157
+ state.bug = false;
158
+ const ctx = { anneeConstruction: 1900 };
159
+ const periode = service.periodeInstallationEmetteurDeduite(ctx, generateurDE, [emetteur(3)], {
160
+ temp_fonc_30: 32,
161
+ temp_fonc_100: 60
162
+ });
163
+ expect(periode).toBeUndefined();
164
+ expect(chTvStore.temperatureFonctionnement).not.toHaveBeenCalled();
165
+ expect(
166
+ service.temperatureFonctionnement(ctx, generateurDE, [emetteur(3)], periode)
167
+ ).toStrictEqual({ temp_fonc_30: 38, temp_fonc_100: 80 });
168
+ });
169
+
170
+ test('T2 bug_for_bug_compat : DPE 32/60, construction 1900 -> période « après 2000 » (3)', () => {
171
+ const ctx = { anneeConstruction: 1900 };
172
+ const periode = service.periodeInstallationEmetteurDeduite(ctx, generateurDE, [emetteur(3)], {
173
+ temp_fonc_30: 32,
174
+ temp_fonc_100: 60
175
+ });
176
+ expect(periode).toBe(3);
177
+ expect(
178
+ service.temperatureFonctionnement(ctx, generateurDE, [emetteur(3)], periode)
179
+ ).toStrictEqual({ temp_fonc_30: 32, temp_fonc_100: 60 });
180
+ expect(logger.info).toHaveBeenCalledWith(
181
+ expect.stringContaining(
182
+ "période d'installation des émetteurs déduite des données intermédiaires du DPE"
183
+ )
184
+ );
185
+ });
186
+
187
+ test('T2 bis bug_for_bug_compat : valeurs du DPE sous forme de chaînes acceptées', () => {
188
+ expect(
189
+ service.periodeInstallationEmetteurDeduite(
190
+ { anneeConstruction: 1900 },
191
+ generateurDE,
192
+ [emetteur(3)],
193
+ { temp_fonc_30: '32', temp_fonc_100: '60' }
194
+ )
195
+ ).toBe(3);
196
+ });
197
+
198
+ test('T3 bug_for_bug_compat : période saisie -> aucune déduction, période saisie conservée', () => {
199
+ const ctx = { anneeConstruction: 1900 };
200
+ const emetteurs = [emetteur(3, 1)];
201
+ const periode = service.periodeInstallationEmetteurDeduite(ctx, generateurDE, emetteurs, {
202
+ temp_fonc_30: 32,
203
+ temp_fonc_100: 60
204
+ });
205
+ expect(periode).toBeUndefined();
206
+ expect(service.temperatureFonctionnement(ctx, generateurDE, emetteurs, 3)).toStrictEqual({
207
+ temp_fonc_30: 38,
208
+ temp_fonc_100: 80
209
+ });
210
+ });
211
+
212
+ test('T4 bug_for_bug_compat : distribution haute, construction 1990, DPE 35/70 -> période de construction (2)', () => {
213
+ expect(
214
+ service.periodeInstallationEmetteurDeduite(
215
+ { anneeConstruction: 1990 },
216
+ generateurDE,
217
+ [emetteur(4)],
218
+ { temp_fonc_30: 35, temp_fonc_100: 70 }
219
+ )
220
+ ).toBe(2);
221
+ });
222
+
223
+ test('T5 bug_for_bug_compat : DPE 33/62 hors tables -> aucune candidate, avertissement', () => {
224
+ expect(
225
+ service.periodeInstallationEmetteurDeduite(
226
+ { anneeConstruction: 1990 },
227
+ generateurDE,
228
+ [emetteur(3)],
229
+ { temp_fonc_30: 33, temp_fonc_100: 62 }
230
+ )
231
+ ).toBeUndefined();
232
+ expect(logger.warn).toHaveBeenCalledWith(
233
+ expect.stringContaining("valeurs hors tables / donnée d'entrée incohérente")
234
+ );
235
+ });
236
+
237
+ test.each([
238
+ ['données intermédiaires absentes', undefined],
239
+ ['temp_fonc_100 absente', { temp_fonc_30: 32 }],
240
+ ['temp_fonc_30 absente', { temp_fonc_100: 60 }],
241
+ ['temp_fonc_30 non numérique', { temp_fonc_30: 'abc', temp_fonc_100: 60 }],
242
+ ['temp_fonc_100 vide', { temp_fonc_30: 32, temp_fonc_100: '' }],
243
+ ['temp_fonc_30 nulle', { temp_fonc_30: null, temp_fonc_100: 60 }]
244
+ ])('T6 bug_for_bug_compat : %s -> aucune déduction', (_, di) => {
245
+ expect(
246
+ service.periodeInstallationEmetteurDeduite(
247
+ { anneeConstruction: 1900 },
248
+ generateurDE,
249
+ [emetteur(3)],
250
+ di
251
+ )
252
+ ).toBeUndefined();
253
+ expect(chTvStore.temperatureFonctionnement).not.toHaveBeenCalled();
254
+ });
255
+
256
+ test('T7 bug_for_bug_compat : deux émetteurs sans période (moyenne + haute), DPE 35/70 -> règle du maximum, période la plus récente', () => {
257
+ /**
258
+ * 1 : max(38, 38) / max(80, 80) -> rejetée ; 2 : 35/70 -> candidate ;
259
+ * 3 : max(32, 35) / max(60, 70) = 35/70 -> candidate.
260
+ * Construction 1900 (période 1) hors candidates -> la plus récente : 3.
261
+ */
262
+ const ctx = { anneeConstruction: 1900 };
263
+ const emetteurs = [emetteur(3), emetteur(4)];
264
+ const periode = service.periodeInstallationEmetteurDeduite(ctx, generateurDE, emetteurs, {
265
+ temp_fonc_30: 35,
266
+ temp_fonc_100: 70
267
+ });
268
+ expect(periode).toBe(3);
269
+ expect(service.temperatureFonctionnement(ctx, generateurDE, emetteurs, periode)).toStrictEqual({
270
+ temp_fonc_30: 35,
271
+ temp_fonc_100: 70
272
+ });
273
+ });
274
+
275
+ test('bug_for_bug_compat : seul un émetteur sans réseau de distribution est sans période -> aucune déduction', () => {
276
+ expect(
277
+ service.periodeInstallationEmetteurDeduite(
278
+ { anneeConstruction: 1900 },
279
+ generateurDE,
280
+ [emetteur(1), emetteur(3, 1)],
281
+ { temp_fonc_30: 32, temp_fonc_100: 60 }
282
+ )
283
+ ).toBeUndefined();
284
+ });
285
+
286
+ test('bug_for_bug_compat : aucune valeur de table trouvée -> aucune candidate', () => {
287
+ vi.mocked(chTvStore.temperatureFonctionnement).mockReturnValue(undefined);
288
+ expect(
289
+ service.periodeInstallationEmetteurDeduite(
290
+ { anneeConstruction: 1900 },
291
+ generateurDE,
292
+ [emetteur(3)],
293
+ { temp_fonc_30: 32, temp_fonc_100: 60 }
294
+ )
295
+ ).toBeUndefined();
296
+ });
297
+
298
+ test('bug_for_bug_compat : une seule des deux valeurs reproduite -> période rejetée (tolérance 0,01 °C)', () => {
299
+ expect(
300
+ service.periodeInstallationEmetteurDeduite(
301
+ { anneeConstruction: 1900 },
302
+ generateurDE,
303
+ [emetteur(3)],
304
+ { temp_fonc_30: 32, temp_fonc_100: 60.02 }
305
+ )
306
+ ).toBeUndefined();
307
+ expect(
308
+ service.periodeInstallationEmetteurDeduite(
309
+ { anneeConstruction: 1900 },
310
+ generateurDE,
311
+ [emetteur(3)],
312
+ { temp_fonc_30: 32.005, temp_fonc_100: 59.995 }
313
+ )
314
+ ).toBe(3);
315
+ });
316
+ });
@@ -309,10 +309,24 @@ export class GenerateurChService {
309
309
  temp_fonc_100: generateurChauffageDI.temp_fonc_100
310
310
  };
311
311
  } else {
312
+ /**
313
+ * Mode bug_for_bug_compat (issue #220) : période d'installation des émetteurs non exportée
314
+ * retrouvée à partir des temp_fonc_30 / temp_fonc_100 d'origine du DPE (données
315
+ * intermédiaires du générateur, non encore recalculées à ce stade). Sans déduction possible,
316
+ * la règle §13.2.1.5 s'applique (année de construction).
317
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
318
+ */
319
+ const periodeDeduite = this.#emetteurChService.periodeInstallationEmetteurDeduite(
320
+ ctx,
321
+ generateurChauffageDE,
322
+ emetteursChauffage,
323
+ generateurChauffageDI
324
+ );
312
325
  return this.#emetteurChService.temperatureFonctionnement(
313
326
  ctx,
314
327
  generateurChauffage.donnee_entree,
315
- emetteursChauffage
328
+ emetteursChauffage,
329
+ periodeDeduite
316
330
  );
317
331
  }
318
332
  }
@@ -0,0 +1,81 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { GenerateurChService } from './generateur-ch.service.js';
3
+ import { EmetteurChService } from './emetteur-ch.service.js';
4
+ import { ChTvStore } from '../../../dpe/infrastructure/ch/chTv.store.js';
5
+ import { TvStore } from '../../../dpe/infrastructure/tv.store.js';
6
+
7
+ /** @type {GenerateurChService} **/
8
+ let service;
9
+
10
+ /** @type {EmetteurChService} **/
11
+ let emetteurChService;
12
+
13
+ /**
14
+ * Températures de fonctionnement du générateur (délégation au service émetteurs).
15
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
16
+ */
17
+ describe('Températures de fonctionnement des générateurs de chauffage', () => {
18
+ const ctx = { anneeConstruction: 1900 };
19
+ const emetteurs = [{ donnee_entree: { enum_temp_distribution_ch_id: 3 } }];
20
+
21
+ beforeEach(() => {
22
+ emetteurChService = new EmetteurChService(new ChTvStore());
23
+ service = new GenerateurChService(new ChTvStore(), new TvStore(), emetteurChService);
24
+ vi.spyOn(emetteurChService, 'temperatureFonctionnement').mockReturnValue({
25
+ temp_fonc_30: 32,
26
+ temp_fonc_100: 60
27
+ });
28
+ vi.spyOn(emetteurChService, 'periodeInstallationEmetteurDeduite').mockReturnValue(3);
29
+ });
30
+
31
+ test('caractéristiques saisies (méthode 5) avec temp_fonc_30 et temp_fonc_100 : valeurs conservées', () => {
32
+ const generateur = {
33
+ donnee_entree: { enum_methode_saisie_carac_sys_id: 5, enum_type_generateur_ch_id: '97' },
34
+ donnee_intermediaire: { temp_fonc_30: 41, temp_fonc_100: 72 }
35
+ };
36
+ expect(service.temperatureFonctionnement(ctx, generateur, emetteurs)).toStrictEqual({
37
+ temp_fonc_30: 41,
38
+ temp_fonc_100: 72
39
+ });
40
+ expect(emetteurChService.periodeInstallationEmetteurDeduite).not.toHaveBeenCalled();
41
+ expect(emetteurChService.temperatureFonctionnement).not.toHaveBeenCalled();
42
+ });
43
+
44
+ test('valeurs forfaitaires : période déduite des données intermédiaires du DPE transmise au calcul (issue #220)', () => {
45
+ const generateurDE = { enum_methode_saisie_carac_sys_id: 1, enum_type_generateur_ch_id: '97' };
46
+ const generateurDI = { temp_fonc_30: 32, temp_fonc_100: 60 };
47
+ const generateur = { donnee_entree: generateurDE, donnee_intermediaire: generateurDI };
48
+
49
+ expect(service.temperatureFonctionnement(ctx, generateur, emetteurs)).toStrictEqual({
50
+ temp_fonc_30: 32,
51
+ temp_fonc_100: 60
52
+ });
53
+ // Les valeurs d'origine du DPE (di du générateur) servent à la déduction
54
+ expect(emetteurChService.periodeInstallationEmetteurDeduite).toHaveBeenCalledWith(
55
+ ctx,
56
+ generateurDE,
57
+ emetteurs,
58
+ generateurDI
59
+ );
60
+ expect(emetteurChService.temperatureFonctionnement).toHaveBeenCalledWith(
61
+ ctx,
62
+ generateurDE,
63
+ emetteurs,
64
+ 3
65
+ );
66
+ });
67
+
68
+ test('méthode 5 sans temp_fonc_100 : calcul forfaitaire depuis les émetteurs', () => {
69
+ vi.mocked(emetteurChService.periodeInstallationEmetteurDeduite).mockReturnValue(undefined);
70
+ const generateurDE = { enum_methode_saisie_carac_sys_id: 5, enum_type_generateur_ch_id: '97' };
71
+ const generateur = { donnee_entree: generateurDE, donnee_intermediaire: { temp_fonc_30: 32 } };
72
+
73
+ service.temperatureFonctionnement(ctx, generateur, emetteurs);
74
+ expect(emetteurChService.temperatureFonctionnement).toHaveBeenCalledWith(
75
+ ctx,
76
+ generateurDE,
77
+ emetteurs,
78
+ undefined
79
+ );
80
+ });
81
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.8.13",
3
+ "version": "1.8.15",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {