@open3cl/engine 1.8.3 → 1.8.4

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.
package/9_emetteur_ch.js CHANGED
@@ -13,10 +13,18 @@ export function rendement_emission(em, rg = 1) {
13
13
  function tv_rendement_distribution_ch(di, de) {
14
14
  let row;
15
15
 
16
- row = tvsStore.getRendementDistributionCh(
17
- de.enum_type_emission_distribution_id,
18
- de.reseau_distribution_isole
19
- );
16
+ // Pour les 'Autres équipements' (id=41), si le DPE original a déjà un tv_rendement_distribution_ch_id,
17
+ // on conserve cette valeur plutôt que de prendre la première ligne de la table (bug).
18
+ if (de.enum_type_emission_distribution_id === '41' && de.tv_rendement_distribution_ch_id) {
19
+ row = tvsStore.getRendementDistributionChById(de.tv_rendement_distribution_ch_id);
20
+ }
21
+
22
+ if (!row) {
23
+ row = tvsStore.getRendementDistributionCh(
24
+ de.enum_type_emission_distribution_id,
25
+ de.reseau_distribution_isole
26
+ );
27
+ }
20
28
 
21
29
  if (!row && de.tv_rendement_distribution_ch_id) {
22
30
  // Find rendement distribution by id if it exists
@@ -1,340 +1,427 @@
1
1
  import { beforeEach, describe, expect, test, vi } from 'vitest';
2
2
 
3
3
  /**
4
- * Dépendances mockées pour isoler `9_emetteur_ch.js` :
5
- * - `tv` : accès générique aux tables (rendements d'émission / de régulation /
6
- * intermittence). On pilote la ligne retournée selon la table interrogée ;
7
- * - `bug_for_bug_compat` : désactivé pour rester sur le comportement nominal ;
8
- * - `TvsStore` : store des rendements de distribution, remplacé par une classe
9
- * factice dont on contrôle les méthodes via `vi.hoisted`.
4
+ * Dépendances mockées :
5
+ * - `utils` : utilitaires (tv, bug_for_bug_compat)
6
+ * - `TvsStore` : service d'accès aux tables de valeurs forfaitaires pour les émetteurs de chaleur.
7
+ *
8
+ * `bug_for_bug_compat` est exposé via un getter afin de pouvoir basculer sa valeur par test.
10
9
  */
11
- const { getRendementDistributionCh, getRendementDistributionChById, utilState } = vi.hoisted(
12
- () => ({
13
- getRendementDistributionCh: vi.fn(),
14
- getRendementDistributionChById: vi.fn(),
15
- utilState: { bug: false }
16
- })
17
- );
10
+ const utilsState = vi.hoisted(() => ({ bugForBugCompat: false }));
11
+
12
+ const mockTvsStore = vi.hoisted(() => ({
13
+ getRendementDistributionCh: vi.fn(),
14
+ getRendementDistributionChById: vi.fn()
15
+ }));
18
16
 
19
17
  vi.mock('./utils.js', () => ({
20
18
  set_bug_for_bug_compat: vi.fn(),
21
19
  tv: vi.fn(),
22
20
  get bug_for_bug_compat() {
23
- return utilState.bug;
21
+ return utilsState.bugForBugCompat;
24
22
  }
25
23
  }));
26
24
 
27
25
  vi.mock('./core/tv/infrastructure/tvs.store.js', () => ({
28
- TvsStore: vi.fn(() => ({
29
- getRendementDistributionCh,
30
- getRendementDistributionChById
31
- }))
26
+ TvsStore: vi.fn(() => mockTvsStore)
32
27
  }));
33
28
 
34
29
  const { rendement_emission, calc_emetteur_ch } = await import('./9_emetteur_ch.js');
35
30
  const { tv } = await import('./utils.js');
36
31
 
37
32
  beforeEach(() => {
33
+ mockTvsStore.getRendementDistributionCh.mockReset();
34
+ mockTvsStore.getRendementDistributionChById.mockReset();
38
35
  vi.mocked(tv).mockReset();
39
- getRendementDistributionCh.mockReset();
40
- getRendementDistributionChById.mockReset();
41
- utilState.bug = false;
36
+ utilsState.bugForBugCompat = false;
37
+ vi.spyOn(console, 'error').mockImplementation(() => {});
42
38
  });
43
39
 
44
40
  /**
45
- * 9. Rendements des émetteurs de chauffage
46
- * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §9
41
+ * Configure le mock `tv` pour renvoyer une ligne par nom de table.
42
+ * Pour `intermittence`, `rows.intermittence` peut être une fonction (matcher) => row.
47
43
  */
48
- describe('rendement_emission - produit des rendements', () => {
49
- const emetteur = {
50
- donnee_intermediaire: {
51
- rendement_emission: 0.95,
52
- rendement_distribution: 0.9,
53
- rendement_regulation: 0.9
54
- }
44
+ function mockTv(rows = {}) {
45
+ vi.mocked(tv).mockImplementation((table, matcher) => {
46
+ const row = rows[table];
47
+ return typeof row === 'function' ? row(matcher) : row;
48
+ });
49
+ }
50
+
51
+ /** Retourne les matchers passés à `tv` pour une table donnée. */
52
+ function tvMatchers(table) {
53
+ return vi
54
+ .mocked(tv)
55
+ .mock.calls.filter(([name]) => name === table)
56
+ .map(([, matcher]) => matcher);
57
+ }
58
+
59
+ /**
60
+ * Fabrique un émetteur de chaleur avec les données d'entrée utiles au calcul.
61
+ */
62
+ function emetteur({
63
+ typeEmissionDistributionId = '1',
64
+ networkIsolated = false,
65
+ tvRendementDistributionChId = null
66
+ }) {
67
+ return {
68
+ donnee_entree: {
69
+ enum_type_emission_distribution_id: typeEmissionDistributionId,
70
+ reseau_distribution_isole: networkIsolated,
71
+ tv_rendement_distribution_ch_id: tvRendementDistributionChId
72
+ },
73
+ donnee_intermediaire: {}
55
74
  };
75
+ }
56
76
 
57
- test('rendement global = rg * re * rd * rr (rg = 1 par défaut)', () => {
58
- expect(rendement_emission(emetteur)).toBeCloseTo(0.7695, 12);
59
- });
77
+ /**
78
+ * Tests pour le calcul du rendement de distribution pour chauffage (9_emetteur_ch.js)
79
+ * Focus sur la correction du bug #170 : conservation du tv_rendement_distribution_ch_id
80
+ * pour type_emission_distribution=41 ('Autres équipements').
81
+ * @see : https://github.com/Open3CL/engine/issues/170
82
+ */
83
+ describe('tv_rendement_distribution_ch - rendement de distribution CH', () => {
84
+ test("type_emission=41 AVEC tv_rendement_distribution_ch_id=6 : conserve l'ID original", () => {
85
+ // Arrange
86
+ const em = emetteur({
87
+ typeEmissionDistributionId: '41',
88
+ tvRendementDistributionChId: 6
89
+ });
60
90
 
61
- test('le rendement de génération (rg) est appliqué en facteur', () => {
62
- expect(rendement_emission(emetteur, 0.8)).toBeCloseTo(0.6156, 12);
91
+ // Mock : getRendementDistributionChById(6) retourne rd=0.91
92
+ mockTvsStore.getRendementDistributionChById.mockReturnValue({
93
+ rd: '0.91',
94
+ tv_rendement_distribution_ch_id: '6'
95
+ });
96
+
97
+ // Act
98
+ calc_emetteur_ch(em, {}, '1', '1');
99
+
100
+ // Assert
101
+ // Vérification que getRendementDistributionChById a été appelé en premier (court-circuit du bug)
102
+ expect(mockTvsStore.getRendementDistributionChById).toHaveBeenCalledWith(6);
103
+ expect(em.donnee_intermediaire.rendement_distribution).toBeCloseTo(0.91, 10);
104
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBe(6);
63
105
  });
64
- });
65
106
 
66
- describe('calc_emetteur_ch - renseignement des données intermédiaires', () => {
67
- /** Pilote la fonction `tv` selon la table interrogée. */
68
- function stubTv() {
69
- vi.mocked(tv).mockImplementation((table) => {
70
- if (table === 'rendement_emission') {
71
- return { re: '0.95', tv_rendement_emission_id: '11' };
72
- }
73
- if (table === 'rendement_regulation') {
74
- return { rr: '0.99', tv_rendement_regulation_id: '22' };
75
- }
76
- if (table === 'intermittence') {
77
- return { i0: '0.85', tv_intermittence_id: '33' };
78
- }
79
- return null;
107
+ test('type_emission=41 SANS tv_rendement_distribution_ch_id : utilise getRendementDistributionCh', () => {
108
+ // Arrange
109
+ const em = emetteur({
110
+ typeEmissionDistributionId: '41',
111
+ tvRendementDistributionChId: null
80
112
  });
81
- }
82
113
 
83
- test("agrège les rendements (distribution, émission, régulation) et l'intermittence", () => {
84
- stubTv();
85
- getRendementDistributionCh.mockReturnValue({
86
- rd: '0.92',
87
- tv_rendement_distribution_ch_id: '44'
114
+ // Mock : getRendementDistributionCh retourne rd=0.85
115
+ mockTvsStore.getRendementDistributionCh.mockReturnValue({
116
+ rd: '0.85',
117
+ tv_rendement_distribution_ch_id: '3'
88
118
  });
89
119
 
90
- const em_ch = {
91
- donnee_entree: {
92
- enum_type_emission_distribution_id: '10',
93
- reseau_distribution_isole: 1
94
- }
95
- };
96
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
120
+ // Act
121
+ calc_emetteur_ch(em, {}, '1', '1');
97
122
 
98
- expect(em_ch.donnee_intermediaire).toEqual({
99
- rendement_distribution: 0.92,
100
- rendement_emission: 0.95,
101
- rendement_regulation: 0.99,
102
- i0: 0.85
103
- });
104
- // les identifiants de table sont réécrits en nombres sur la donnée d'entrée
105
- expect(em_ch.donnee_entree.tv_rendement_distribution_ch_id).toBe(44);
106
- expect(em_ch.donnee_entree.tv_rendement_emission_id).toBe(11);
107
- expect(em_ch.donnee_entree.tv_rendement_regulation_id).toBe(22);
108
- expect(em_ch.donnee_entree.tv_intermittence_id).toBe(33);
109
- expect(em_ch.donnee_utilisateur).toEqual({});
123
+ // Assert
124
+ // Vérification que getRendementDistributionCh est appelé (fallthrough)
125
+ expect(mockTvsStore.getRendementDistributionCh).toHaveBeenCalledWith('41', false);
126
+ expect(em.donnee_intermediaire.rendement_distribution).toBeCloseTo(0.85, 10);
127
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBe(3);
110
128
  });
111
129
 
112
- test('rendement de distribution : repli sur la recherche par identifiant si aucune ligne directe', () => {
113
- stubTv();
114
- getRendementDistributionCh.mockReturnValue(undefined);
115
- getRendementDistributionChById.mockReturnValue({
116
- rd: '0.88',
117
- tv_rendement_distribution_ch_id: '55'
130
+ test('type_emission=12 (pas 41) : utilise getRendementDistributionCh normalement', () => {
131
+ // Arrange
132
+ const em = emetteur({
133
+ typeEmissionDistributionId: '12',
134
+ tvRendementDistributionChId: 5
135
+ });
136
+
137
+ // Mock : getRendementDistributionCh retourne rd=0.90
138
+ mockTvsStore.getRendementDistributionCh.mockReturnValue({
139
+ rd: '0.90',
140
+ tv_rendement_distribution_ch_id: '5'
118
141
  });
119
142
 
120
- const em_ch = {
121
- donnee_entree: {
122
- enum_type_emission_distribution_id: '10',
123
- tv_rendement_distribution_ch_id: 55
143
+ // Act
144
+ calc_emetteur_ch(em, {}, '1', '1');
145
+
146
+ // Assert
147
+ // Vérification que getRendementDistributionCh est appelé directement (pas de court-circuit)
148
+ expect(mockTvsStore.getRendementDistributionCh).toHaveBeenCalledWith('12', false);
149
+ expect(em.donnee_intermediaire.rendement_distribution).toBeCloseTo(0.9, 10);
150
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBe(5);
151
+ });
152
+
153
+ test('rendement_emission - calcul du rendement total', () => {
154
+ // Arrange
155
+ const em = {
156
+ donnee_intermediaire: {
157
+ rendement_emission: 0.85,
158
+ rendement_distribution: 0.9,
159
+ rendement_regulation: 0.95
124
160
  }
125
161
  };
126
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
127
162
 
128
- expect(getRendementDistributionChById).toHaveBeenCalledWith(55);
129
- expect(em_ch.donnee_intermediaire.rendement_distribution).toBe(0.88);
130
- });
163
+ // Act
164
+ const result = rendement_emission(em);
131
165
 
132
- test("intermittence : la fiche technique de comptage force la présence d'un comptage individuel", () => {
133
- stubTv();
134
- getRendementDistributionCh.mockReturnValue({
135
- rd: '0.92',
136
- tv_rendement_distribution_ch_id: '44'
137
- });
166
+ // Assert
167
+ // rg * re * rd * rr = 1 * 0.85 * 0.9 * 0.95
168
+ expect(result).toBeCloseTo(0.72675, 10);
169
+ });
138
170
 
139
- const em_ch = {
140
- donnee_entree: {
141
- enum_type_emission_distribution_id: '10',
142
- enum_type_chauffage_id: '1',
143
- enum_equipement_intermittence_id: '1',
144
- enum_type_regulation_id: '1'
171
+ test('rendement_emission - avec coefficient de régulation différent de 1', () => {
172
+ // Arrange
173
+ const em = {
174
+ donnee_intermediaire: {
175
+ rendement_emission: 0.8,
176
+ rendement_distribution: 0.85,
177
+ rendement_regulation: 0.9
145
178
  }
146
179
  };
147
- // map_id === '1' => la classe d'inertie entre dans le matcher
148
- calc_emetteur_ch(
149
- em_ch,
150
- {
151
- enum_type_installation_id: '1',
152
- ficheTechniqueComptage: { valeur: '1' }
153
- },
154
- '1',
155
- '7'
156
- );
157
180
 
158
- const matcher = vi.mocked(tv).mock.calls.find((c) => c[0] === 'intermittence')[1];
159
- expect(matcher.comptage_individuel).toBe('Présence');
160
- expect(matcher.enum_methode_application_dpe_log_id).toBe('1');
161
- expect(matcher.enum_classe_inertie_id).toBe('7');
181
+ // Act
182
+ const result = rendement_emission(em, 0.95);
183
+
184
+ // Assert
185
+ // rg * re * rd * rr = 0.95 * 0.8 * 0.85 * 0.9
186
+ expect(result).toBeCloseTo(0.5814, 10);
162
187
  });
188
+ });
163
189
 
164
- test('intermittence : absence de comptage individuel hors fiche technique', () => {
165
- stubTv();
166
- getRendementDistributionCh.mockReturnValue({
167
- rd: '0.92',
168
- tv_rendement_distribution_ch_id: '44'
190
+ describe('tv_rendement_distribution_ch - cas de repli', () => {
191
+ test('type_emission=41 AVEC id inconnu par id : repli sur getRendementDistributionCh', () => {
192
+ const em = emetteur({ typeEmissionDistributionId: '41', tvRendementDistributionChId: 99 });
193
+ mockTvsStore.getRendementDistributionChById.mockReturnValue(undefined);
194
+ mockTvsStore.getRendementDistributionCh.mockReturnValue({
195
+ rd: '0.87',
196
+ tv_rendement_distribution_ch_id: '4'
169
197
  });
170
198
 
171
- const em_ch = {
172
- donnee_entree: { enum_type_emission_distribution_id: '10' }
173
- };
174
- // map_id !== '1' => pas de classe d'inertie dans le matcher
175
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
199
+ calc_emetteur_ch(em, {}, '1', '1');
176
200
 
177
- const matcher = vi.mocked(tv).mock.calls.find((c) => c[0] === 'intermittence')[1];
178
- expect(matcher.comptage_individuel).toBe('Absence');
179
- expect(matcher.enum_classe_inertie_id).toBeUndefined();
201
+ expect(mockTvsStore.getRendementDistributionChById).toHaveBeenCalledWith(99);
202
+ expect(mockTvsStore.getRendementDistributionCh).toHaveBeenCalledWith('41', false);
203
+ expect(em.donnee_intermediaire.rendement_distribution).toBeCloseTo(0.87, 10);
204
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBe(4);
180
205
  });
181
206
 
182
- test('aucune ligne trouvée pour l’ensemble des rendements : messages d’erreur', () => {
183
- const err = vi.spyOn(console, 'error').mockImplementation(() => {});
184
- // toutes les tables renvoient null et le store ne fournit rien
185
- vi.mocked(tv).mockReturnValue(null);
186
- getRendementDistributionCh.mockReturnValue(undefined);
187
- getRendementDistributionChById.mockReturnValue(undefined);
207
+ test('aucune ligne par critères mais id présent : recherche par id', () => {
208
+ const em = emetteur({
209
+ typeEmissionDistributionId: '12',
210
+ networkIsolated: true,
211
+ tvRendementDistributionChId: 7
212
+ });
213
+ mockTvsStore.getRendementDistributionCh.mockReturnValue(undefined);
214
+ mockTvsStore.getRendementDistributionChById.mockReturnValue({
215
+ rd: '0.93',
216
+ tv_rendement_distribution_ch_id: '7'
217
+ });
188
218
 
189
- const em_ch = {
190
- donnee_entree: { enum_type_emission_distribution_id: '10' }
191
- };
192
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
219
+ calc_emetteur_ch(em, {}, '1', '1');
193
220
 
194
- // distribution + émission + régulation + intermittence => 4 erreurs
195
- expect(err).toHaveBeenCalledTimes(4);
196
- expect(em_ch.donnee_intermediaire).toEqual({});
197
- err.mockRestore();
221
+ expect(mockTvsStore.getRendementDistributionCh).toHaveBeenCalledWith('12', true);
222
+ expect(mockTvsStore.getRendementDistributionChById).toHaveBeenCalledWith(7);
223
+ expect(em.donnee_intermediaire.rendement_distribution).toBeCloseTo(0.93, 10);
224
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBe(7);
198
225
  });
199
226
 
200
- test("fiche technique de comptage à 'oui' : présence détectée", () => {
201
- stubTv();
202
- getRendementDistributionCh.mockReturnValue({
203
- rd: '0.92',
204
- tv_rendement_distribution_ch_id: '44'
205
- });
227
+ test('aucune ligne trouvée et pas d’id : erreur et rendement non renseigné', () => {
228
+ const em = emetteur({ typeEmissionDistributionId: '12' });
229
+ mockTvsStore.getRendementDistributionCh.mockReturnValue(undefined);
206
230
 
207
- const em_ch = { donnee_entree: { enum_type_emission_distribution_id: '10' } };
208
- calc_emetteur_ch(
209
- em_ch,
210
- { enum_type_installation_id: '1', ficheTechniqueComptage: { valeur: 'oui' } },
211
- '2',
212
- '3'
213
- );
231
+ calc_emetteur_ch(em, {}, '1', '1');
214
232
 
215
- const matcher = vi.mocked(tv).mock.calls.find((c) => c[0] === 'intermittence')[1];
216
- expect(matcher.comptage_individuel).toBe('Présence');
233
+ expect(mockTvsStore.getRendementDistributionChById).not.toHaveBeenCalled();
234
+ expect(em.donnee_intermediaire.rendement_distribution).toBeUndefined();
235
+ expect(em.donnee_entree.tv_rendement_distribution_ch_id).toBeNull();
236
+ expect(console.error).toHaveBeenCalledWith(
237
+ '!! pas de valeur forfaitaire trouvée pour rendement_distribution_ch !!'
238
+ );
217
239
  });
240
+ });
218
241
 
219
- test('fiche technique de comptage vide : absence de comptage', () => {
220
- stubTv();
221
- getRendementDistributionCh.mockReturnValue({
222
- rd: '0.92',
223
- tv_rendement_distribution_ch_id: '44'
242
+ describe('tv_rendement_emission / tv_rendement_regulation', () => {
243
+ test('lignes trouvées : renseigne les rendements et les ids de table', () => {
244
+ const em = emetteur({ typeEmissionDistributionId: '12' });
245
+ mockTv({
246
+ rendement_emission: { re: '0.95', tv_rendement_emission_id: '2' },
247
+ rendement_regulation: { rr: '0.9', tv_rendement_regulation_id: '3' }
224
248
  });
225
249
 
226
- const em_ch = { donnee_entree: { enum_type_emission_distribution_id: '10' } };
227
- calc_emetteur_ch(
228
- em_ch,
229
- { enum_type_installation_id: '1', ficheTechniqueComptage: { valeur: '' } },
230
- '2',
231
- '3'
232
- );
233
-
234
- const matcher = vi.mocked(tv).mock.calls.find((c) => c[0] === 'intermittence')[1];
235
- expect(matcher.comptage_individuel).toBe('Absence');
250
+ calc_emetteur_ch(em, {}, '1', '1');
251
+
252
+ expect(tvMatchers('rendement_emission')).toEqual([
253
+ { enum_type_emission_distribution_id: '12' }
254
+ ]);
255
+ expect(tvMatchers('rendement_regulation')).toEqual([
256
+ { enum_type_emission_distribution_id: '12' }
257
+ ]);
258
+ expect(em.donnee_intermediaire.rendement_emission).toBeCloseTo(0.95, 10);
259
+ expect(em.donnee_intermediaire.rendement_regulation).toBeCloseTo(0.9, 10);
260
+ expect(em.donnee_entree.tv_rendement_emission_id).toBe(2);
261
+ expect(em.donnee_entree.tv_rendement_regulation_id).toBe(3);
236
262
  });
237
263
 
238
- test('intermittence introuvable : message d’erreur dédié', () => {
239
- const err = vi.spyOn(console, 'error').mockImplementation(() => {});
240
- vi.mocked(tv).mockImplementation((table) => {
241
- if (table === 'rendement_emission') return { re: '0.95', tv_rendement_emission_id: '11' };
242
- if (table === 'rendement_regulation') return { rr: '0.99', tv_rendement_regulation_id: '22' };
243
- // intermittence => null
244
- return null;
245
- });
246
- getRendementDistributionCh.mockReturnValue({
247
- rd: '0.92',
248
- tv_rendement_distribution_ch_id: '44'
249
- });
264
+ test('aucune ligne trouvée : erreurs et rendements non renseignés', () => {
265
+ const em = emetteur({ typeEmissionDistributionId: '12' });
266
+ mockTv({});
250
267
 
251
- const em_ch = { donnee_entree: { enum_type_emission_distribution_id: '10' } };
252
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
268
+ calc_emetteur_ch(em, {}, '1', '1');
253
269
 
254
- expect(err).toHaveBeenCalled();
255
- expect(em_ch.donnee_intermediaire.i0).toBeUndefined();
256
- err.mockRestore();
270
+ expect(em.donnee_intermediaire.rendement_emission).toBeUndefined();
271
+ expect(em.donnee_intermediaire.rendement_regulation).toBeUndefined();
272
+ expect(console.error).toHaveBeenCalledWith(
273
+ '!! pas de valeur forfaitaire trouvée pour rendement_emission !!'
274
+ );
275
+ expect(console.error).toHaveBeenCalledWith(
276
+ '!! pas de valeur forfaitaire trouvée pour rendement_regulation !!'
277
+ );
257
278
  });
258
279
  });
259
280
 
260
- /**
261
- * Mode "bug for bug" : en l'absence de fiche technique, la présence d'un
262
- * comptage individuel est déduite de la ligne d'intermittence de référence.
263
- */
264
- describe('calc_emetteur_ch - déduction du comptage en mode "bug for bug"', () => {
265
- /** Pilote `tv` avec une ligne d'intermittence paramétrable. */
266
- function stubTvIntermittence(comptageRow) {
267
- vi.mocked(tv).mockImplementation((table, matcher) => {
268
- if (table === 'rendement_emission') return { re: '0.95', tv_rendement_emission_id: '11' };
269
- if (table === 'rendement_regulation') return { rr: '0.99', tv_rendement_regulation_id: '22' };
270
- if (table === 'intermittence') {
271
- // 1er appel : matcher basé sur tv_intermittence_id (déduction du comptage)
272
- if (matcher.tv_intermittence_id !== undefined && !matcher.enum_type_installation_id) {
273
- return comptageRow;
274
- }
275
- return { i0: '0.85', tv_intermittence_id: '33' };
276
- }
277
- return null;
278
- });
279
- getRendementDistributionCh.mockReturnValue({
280
- rd: '0.92',
281
- tv_rendement_distribution_ch_id: '44'
281
+ describe('tv_intermittence - intermittence', () => {
282
+ /**
283
+ * Fabrique un émetteur avec les données d'entrée nécessaires au matcher d'intermittence.
284
+ */
285
+ function emetteurIntermittence(tvIntermittenceId = 10) {
286
+ const em = emetteur({ typeEmissionDistributionId: '12' });
287
+ Object.assign(em.donnee_entree, {
288
+ enum_type_chauffage_id: '1',
289
+ enum_equipement_intermittence_id: '2',
290
+ enum_type_regulation_id: '3',
291
+ tv_intermittence_id: tvIntermittenceId
282
292
  });
293
+ return em;
283
294
  }
284
295
 
285
- const emetteur = () => ({
286
- donnee_entree: { enum_type_emission_distribution_id: '10', tv_intermittence_id: 5 }
296
+ /** Ligne de résultat d'intermittence renvoyée par le matcher final (sans tv_intermittence_id). */
297
+ const intermittenceRow = (row) => (matcher) =>
298
+ 'tv_intermittence_id' in matcher ? row.lookup : row.result;
299
+
300
+ test('maison individuelle (map_id=1) : matcher complet avec classe d’inertie', () => {
301
+ const em = emetteurIntermittence();
302
+ mockTv({ intermittence: { i0: '0.86', tv_intermittence_id: '42' } });
303
+
304
+ calc_emetteur_ch(em, { enum_type_installation_id: '1' }, '1', '3');
305
+
306
+ expect(tvMatchers('intermittence')).toEqual([
307
+ {
308
+ enum_methode_application_dpe_log_id: '1',
309
+ enum_type_installation_id: '1',
310
+ enum_type_chauffage_id: '1',
311
+ enum_equipement_intermittence_id: '2',
312
+ enum_type_regulation_id: '3',
313
+ enum_type_emission_distribution_id: '12',
314
+ comptage_individuel: 'Absence',
315
+ enum_classe_inertie_id: '3'
316
+ }
317
+ ]);
318
+ expect(em.donnee_intermediaire.i0).toBeCloseTo(0.86, 10);
319
+ expect(em.donnee_entree.tv_intermittence_id).toBe(42);
320
+ });
321
+
322
+ test('autre méthode (map_id≠1) : pas de classe d’inertie dans le matcher', () => {
323
+ const em = emetteurIntermittence();
324
+ mockTv({ intermittence: { i0: '0.9', tv_intermittence_id: '43' } });
325
+
326
+ calc_emetteur_ch(em, { enum_type_installation_id: '2' }, '5', '3');
327
+
328
+ const [matcher] = tvMatchers('intermittence');
329
+ expect(matcher).not.toHaveProperty('enum_classe_inertie_id');
330
+ expect(matcher.enum_methode_application_dpe_log_id).toBe('5');
331
+ expect(em.donnee_entree.tv_intermittence_id).toBe(43);
332
+ });
333
+
334
+ test.each([
335
+ ['1', 'Présence'],
336
+ ['oui', 'Présence'],
337
+ ['OUI', 'Présence'],
338
+ ['non', 'Absence'],
339
+ ['0', 'Absence'],
340
+ [null, 'Absence']
341
+ ])('fiche technique comptage valeur=%s → comptage %s', (valeur, attendu) => {
342
+ utilsState.bugForBugCompat = true;
343
+ const em = emetteurIntermittence();
344
+ mockTv({ intermittence: { i0: '0.9', tv_intermittence_id: '1' } });
345
+
346
+ calc_emetteur_ch(
347
+ em,
348
+ { enum_type_installation_id: '1', ficheTechniqueComptage: { valeur } },
349
+ '1',
350
+ '1'
351
+ );
352
+
353
+ // La fiche technique prime : pas de recherche par tv_intermittence_id même en bug_for_bug_compat
354
+ const matchers = tvMatchers('intermittence');
355
+ expect(matchers).toHaveLength(1);
356
+ expect(matchers[0].comptage_individuel).toBe(attendu);
287
357
  });
288
358
 
289
- test('ligne de référence "Présence" => comptage individuel présent', () => {
290
- utilState.bug = true;
291
- stubTvIntermittence({ comptage_individuel: 'Présence d’un comptage' });
359
+ test('bug_for_bug_compat sans fiche : comptage déduit de tv_intermittence_id (Présence)', () => {
360
+ utilsState.bugForBugCompat = true;
361
+ const em = emetteurIntermittence(10);
362
+ mockTv({
363
+ intermittence: intermittenceRow({
364
+ lookup: { comptage_individuel: 'Présence de comptage' },
365
+ result: { i0: '0.95', tv_intermittence_id: '11' }
366
+ })
367
+ });
292
368
 
293
- const em_ch = emetteur();
294
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
369
+ calc_emetteur_ch(em, { enum_type_installation_id: '2' }, '1', '1');
295
370
 
296
- const matcher = vi
297
- .mocked(tv)
298
- .mock.calls.find((c) => c[0] === 'intermittence' && c[1].enum_type_installation_id)[1];
299
- expect(matcher.comptage_individuel).toBe('Présence');
371
+ const matchers = tvMatchers('intermittence');
372
+ expect(matchers[0]).toEqual({ tv_intermittence_id: 10 });
373
+ expect(matchers[1].comptage_individuel).toBe('Présence');
374
+ expect(em.donnee_intermediaire.i0).toBeCloseTo(0.95, 10);
375
+ expect(em.donnee_entree.tv_intermittence_id).toBe(11);
300
376
  });
301
377
 
302
- test('ligne de référence sans "Présence" => absence de comptage', () => {
303
- utilState.bug = true;
304
- stubTvIntermittence({ comptage_individuel: 'Absence de comptage' });
378
+ test.each([
379
+ ['ligne sans comptage_individuel', {}],
380
+ ['aucune ligne', undefined]
381
+ ])('bug_for_bug_compat sans fiche, %s : comptage Absence', (_, lookup) => {
382
+ utilsState.bugForBugCompat = true;
383
+ const em = emetteurIntermittence(10);
384
+ mockTv({
385
+ intermittence: intermittenceRow({
386
+ lookup,
387
+ result: { i0: '0.9', tv_intermittence_id: '12' }
388
+ })
389
+ });
305
390
 
306
- const em_ch = emetteur();
307
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
391
+ calc_emetteur_ch(em, { enum_type_installation_id: '2' }, '1', '1');
308
392
 
309
- const matcher = vi
310
- .mocked(tv)
311
- .mock.calls.find((c) => c[0] === 'intermittence' && c[1].enum_type_installation_id)[1];
312
- expect(matcher.comptage_individuel).toBe('Absence');
393
+ const matchers = tvMatchers('intermittence');
394
+ expect(matchers).toHaveLength(2);
395
+ expect(matchers[1].comptage_individuel).toBe('Absence');
313
396
  });
314
397
 
315
- test('ligne de référence sans colonne comptage => absence de comptage', () => {
316
- utilState.bug = true;
317
- stubTvIntermittence({ i0: '0.5' });
398
+ test('aucune ligne trouvée : erreur et i0 non renseigné', () => {
399
+ const em = emetteurIntermittence(10);
400
+ mockTv({});
318
401
 
319
- const em_ch = emetteur();
320
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
402
+ calc_emetteur_ch(em, { enum_type_installation_id: '1' }, '1', '1');
321
403
 
322
- const matcher = vi
323
- .mocked(tv)
324
- .mock.calls.find((c) => c[0] === 'intermittence' && c[1].enum_type_installation_id)[1];
325
- expect(matcher.comptage_individuel).toBe('Absence');
404
+ expect(em.donnee_intermediaire.i0).toBeUndefined();
405
+ expect(em.donnee_entree.tv_intermittence_id).toBe(10);
406
+ expect(console.error).toHaveBeenCalledWith(
407
+ '!! pas de valeur forfaitaire trouvée pour intermittence !!'
408
+ );
326
409
  });
410
+ });
327
411
 
328
- test('aucune ligne de référence trouvée => absence de comptage', () => {
329
- utilState.bug = true;
330
- stubTvIntermittence(null);
412
+ describe('calc_emetteur_ch - résultat', () => {
413
+ test('initialise donnee_utilisateur et remplace donnee_intermediaire', () => {
414
+ const em = emetteur({ typeEmissionDistributionId: '12' });
415
+ em.donnee_intermediaire = { ancien: true };
416
+ mockTvsStore.getRendementDistributionCh.mockReturnValue({
417
+ rd: '0.9',
418
+ tv_rendement_distribution_ch_id: '1'
419
+ });
420
+ mockTv({});
331
421
 
332
- const em_ch = emetteur();
333
- calc_emetteur_ch(em_ch, { enum_type_installation_id: '1' }, '2', '3');
422
+ calc_emetteur_ch(em, {}, '1', '1');
334
423
 
335
- const matcher = vi
336
- .mocked(tv)
337
- .mock.calls.find((c) => c[0] === 'intermittence' && c[1].enum_type_installation_id)[1];
338
- expect(matcher.comptage_individuel).toBe('Absence');
424
+ expect(em.donnee_intermediaire).not.toHaveProperty('ancien');
425
+ expect(em.donnee_utilisateur).toEqual({});
339
426
  });
340
427
  });
package/engine.js CHANGED
@@ -33,7 +33,7 @@ import getFicheTechnique from './ficheTechnique.js';
33
33
  import { ProductionENR } from './16.2_production_enr.js';
34
34
  import DpeSanitizerService from './dpe-sanitizer.service.js';
35
35
 
36
- const LIB_VERSION = '1.8.3';
36
+ const LIB_VERSION = '1.8.4';
37
37
 
38
38
  const dpeSanitizerService = new DpeSanitizerService();
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.8.3",
3
+ "version": "1.8.4",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {