@open3cl/engine 1.8.14 → 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.
- package/13.2_generateur_combustion_ch.js +164 -3
- package/13.2_generateur_combustion_ch.spec.js +286 -2
- package/README.md +23 -23
- package/engine.js +1 -1
- package/features/engine/domain/ch/emetteur-ch.service.js +164 -40
- package/features/engine/domain/ch/emetteur-ch.service.spec.js +240 -2
- package/features/engine/domain/ch/generateur-ch.service.js +15 -1
- package/features/engine/domain/ch/generateur-ch.service.spec.js +81 -0
- package/package.json +1 -1
|
@@ -62,7 +62,170 @@ export function tempDistributionChPourTempFonc(emChDe, emChDu) {
|
|
|
62
62
|
return tempDistribution;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Périodes d'installation des émetteurs (table temp_fonc_30 / temp_fonc_100), de la plus ancienne
|
|
67
|
+
* à la plus récente.
|
|
68
|
+
*/
|
|
69
|
+
export const PERIODES_INSTALLATION_EMETTEUR = ['avant 1981', 'entre 1981 et 2000', 'après 2000'];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tolérance (°C) pour considérer qu'une température de fonctionnement issue des tables reproduit
|
|
73
|
+
* celle stockée dans le DPE.
|
|
74
|
+
*/
|
|
75
|
+
export const TOLERANCE_TEMP_FONC = 0.01;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 13.2.1.5 Chaudières basse température et condensation
|
|
79
|
+
* « Si l'année d'installation des émetteurs est inconnue, prendre l'année de construction du
|
|
80
|
+
* bâtiment. »
|
|
81
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
|
|
82
|
+
*
|
|
83
|
+
* @param ac {number} année de construction du bâtiment
|
|
84
|
+
* @return {string} période d'installation des émetteurs
|
|
85
|
+
*/
|
|
86
|
+
export function periodeEmetteurAnneeConstruction(ac) {
|
|
87
|
+
if (ac < 1981) return PERIODES_INSTALLATION_EMETTEUR[0];
|
|
88
|
+
if (ac < 2000) return PERIODES_INSTALLATION_EMETTEUR[1];
|
|
89
|
+
return PERIODES_INSTALLATION_EMETTEUR[2];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function estNumerique(valeur) {
|
|
93
|
+
return (
|
|
94
|
+
valeur !== null && valeur !== undefined && valeur !== '' && Number.isFinite(Number(valeur))
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Lignes des tables temp_fonc_30 / temp_fonc_100 retenues pour un générateur (maximum sur les
|
|
100
|
+
* émetteurs), en affectant `periodeParDefaut` aux émetteurs dont la période d'installation n'est
|
|
101
|
+
* pas saisie. Les émetteurs sans réseau de distribution (enum_temp_distribution_ch_id = 1) sont
|
|
102
|
+
* exclus. Utilisé uniquement pour la déduction de période en mode bug_for_bug_compat.
|
|
103
|
+
*
|
|
104
|
+
* @param de {object} donnee_entree du générateur
|
|
105
|
+
* @param em_ch {object[]} émetteurs de l'installation
|
|
106
|
+
* @param periodeParDefaut {string} période affectée aux émetteurs sans période saisie
|
|
107
|
+
* @return {{row_30: object|null, row_100: object|null}}
|
|
108
|
+
*/
|
|
109
|
+
export function temp_fonc_pour_periode(de, em_ch, periodeParDefaut) {
|
|
110
|
+
let row_30 = null;
|
|
111
|
+
let row_100 = null;
|
|
112
|
+
for (const em of em_ch) {
|
|
113
|
+
const em_ch_de = em.donnee_entree;
|
|
114
|
+
const em_ch_du = em.donnee_utilisateur;
|
|
115
|
+
if (String(requestInputID(em_ch_de, em_ch_du, 'temp_distribution_ch')) === '1') continue;
|
|
116
|
+
const matcher = {
|
|
117
|
+
enum_type_generateur_ch_id: de.enum_type_generateur_ch_id,
|
|
118
|
+
enum_temp_distribution_ch_id: tempDistributionChPourTempFonc(em_ch_de, em_ch_du),
|
|
119
|
+
periode_emetteurs:
|
|
120
|
+
requestInput(em_ch_de, em_ch_du, 'periode_installation_emetteur') || periodeParDefaut
|
|
121
|
+
};
|
|
122
|
+
const r30 = tv('temp_fonc_30', matcher);
|
|
123
|
+
const r100 = tv('temp_fonc_100', matcher);
|
|
124
|
+
if (r30 && (!row_30 || Number(r30.temp_fonc_30) > Number(row_30.temp_fonc_30))) row_30 = r30;
|
|
125
|
+
if (r100 && (!row_100 || Number(r100.temp_fonc_100) > Number(row_100.temp_fonc_100))) {
|
|
126
|
+
row_100 = r100;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { row_30, row_100 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Mode bug_for_bug_compat uniquement (issue #220).
|
|
134
|
+
*
|
|
135
|
+
* Certains logiciels (ex. 4.1.1 / moteur 3cl-2024.6.1.0, LICIEL) calculent les températures de
|
|
136
|
+
* fonctionnement avec une période d'installation des émetteurs qu'ils n'exportent pas, mais
|
|
137
|
+
* stockent temp_fonc_30 / temp_fonc_100 dans les données intermédiaires du générateur. La période
|
|
138
|
+
* est alors retrouvée en testant chaque période candidate (appliquée à tous les émetteurs sans
|
|
139
|
+
* période saisie) et en ne retenant que celles qui reproduisent les DEUX valeurs du DPE.
|
|
140
|
+
*
|
|
141
|
+
* - une seule candidate : retenue ;
|
|
142
|
+
* - plusieurs : celle de l'année de construction si elle en fait partie, sinon la plus récente ;
|
|
143
|
+
* - aucune : null (repli sur l'année de construction, valeurs hors tables).
|
|
144
|
+
*
|
|
145
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
|
|
146
|
+
*
|
|
147
|
+
* @param tempFonc30Dpe {number} temp_fonc_30 d'origine du DPE
|
|
148
|
+
* @param tempFonc100Dpe {number} temp_fonc_100 d'origine du DPE
|
|
149
|
+
* @param de {object} donnee_entree du générateur
|
|
150
|
+
* @param em_ch {object[]} émetteurs de l'installation
|
|
151
|
+
* @param ac {number} année de construction
|
|
152
|
+
* @return {string|null} période déduite, ou null si aucune période ne reproduit le DPE
|
|
153
|
+
*/
|
|
154
|
+
export function periodeEmetteursDeduiteDuDpe(tempFonc30Dpe, tempFonc100Dpe, de, em_ch, ac) {
|
|
155
|
+
const candidates = PERIODES_INSTALLATION_EMETTEUR.filter((periode) => {
|
|
156
|
+
const { row_30, row_100 } = temp_fonc_pour_periode(de, em_ch, periode);
|
|
157
|
+
return (
|
|
158
|
+
row_30 &&
|
|
159
|
+
row_100 &&
|
|
160
|
+
Math.abs(Number(row_30.temp_fonc_30) - tempFonc30Dpe) <= TOLERANCE_TEMP_FONC &&
|
|
161
|
+
Math.abs(Number(row_100.temp_fonc_100) - tempFonc100Dpe) <= TOLERANCE_TEMP_FONC
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
if (candidates.length === 0) return null;
|
|
165
|
+
const periodeConstruction = periodeEmetteurAnneeConstruction(ac);
|
|
166
|
+
if (candidates.includes(periodeConstruction)) return periodeConstruction;
|
|
167
|
+
return candidates[candidates.length - 1];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Indique si la déduction de la période des émetteurs à partir des données intermédiaires du DPE
|
|
172
|
+
* doit être tentée : mode bug_for_bug_compat, au moins un émetteur (avec réseau de distribution)
|
|
173
|
+
* sans période saisie, et temp_fonc_30 ET temp_fonc_100 d'origine présentes et numériques.
|
|
174
|
+
*
|
|
175
|
+
* @param tempFonc30Dpe {*} temp_fonc_30 d'origine du DPE
|
|
176
|
+
* @param tempFonc100Dpe {*} temp_fonc_100 d'origine du DPE
|
|
177
|
+
* @param em_ch {object[]} émetteurs de l'installation
|
|
178
|
+
* @return {boolean}
|
|
179
|
+
*/
|
|
180
|
+
export function deductionPeriodeEmetteursActive(tempFonc30Dpe, tempFonc100Dpe, em_ch) {
|
|
181
|
+
if (!bug_for_bug_compat) return false;
|
|
182
|
+
if (!estNumerique(tempFonc30Dpe) || !estNumerique(tempFonc100Dpe)) return false;
|
|
183
|
+
return em_ch.some(
|
|
184
|
+
(em) =>
|
|
185
|
+
String(requestInputID(em.donnee_entree, em.donnee_utilisateur, 'temp_distribution_ch')) !==
|
|
186
|
+
'1' &&
|
|
187
|
+
!requestInput(em.donnee_entree, em.donnee_utilisateur, 'periode_installation_emetteur')
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Températures de fonctionnement à 30 % et 100 % de charge.
|
|
193
|
+
*
|
|
194
|
+
* `di` est la donnee_intermediaire du générateur issue du DPE d'entrée : à l'appel, temp_fonc_30 /
|
|
195
|
+
* temp_fonc_100 contiennent les valeurs d'origine du DPE (jamais une valeur recalculée par
|
|
196
|
+
* Open3CL). En mode bug_for_bug_compat, elles servent uniquement à retrouver la période
|
|
197
|
+
* d'installation des émetteurs non exportée (issue #220) ; les valeurs retenues proviennent
|
|
198
|
+
* toujours des tables (tv_temp_fonc_30_id / tv_temp_fonc_100_id renseignés).
|
|
199
|
+
*
|
|
200
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
|
|
201
|
+
*/
|
|
65
202
|
export function tv_temp_fonc_30_100(di, de, du, em_ch, ac) {
|
|
203
|
+
const tempFonc30Dpe = di.temp_fonc_30;
|
|
204
|
+
const tempFonc100Dpe = di.temp_fonc_100;
|
|
205
|
+
if (deductionPeriodeEmetteursActive(tempFonc30Dpe, tempFonc100Dpe, em_ch)) {
|
|
206
|
+
const periode = periodeEmetteursDeduiteDuDpe(
|
|
207
|
+
Number(tempFonc30Dpe),
|
|
208
|
+
Number(tempFonc100Dpe),
|
|
209
|
+
de,
|
|
210
|
+
em_ch,
|
|
211
|
+
ac
|
|
212
|
+
);
|
|
213
|
+
if (periode) {
|
|
214
|
+
const { row_30, row_100 } = temp_fonc_pour_periode(de, em_ch, periode);
|
|
215
|
+
console.warn(
|
|
216
|
+
`période d'installation des émetteurs déduite des données intermédiaires du DPE : ${periode} (générateur ${de.description}, temp_fonc_30 = ${row_30.temp_fonc_30}, temp_fonc_100 = ${row_100.temp_fonc_100})`
|
|
217
|
+
);
|
|
218
|
+
de.tv_temp_fonc_30_id = row_30.tv_temp_fonc_30_id;
|
|
219
|
+
di.temp_fonc_30 = Number(row_30.temp_fonc_30);
|
|
220
|
+
de.tv_temp_fonc_100_id = row_100.tv_temp_fonc_100_id;
|
|
221
|
+
di.temp_fonc_100 = Number(row_100.temp_fonc_100);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
console.warn(
|
|
225
|
+
`période d'installation des émetteurs non déductible pour le générateur ${de.description} : valeurs hors tables / donnée d'entrée incohérente (temp_fonc_30 = ${tempFonc30Dpe}, temp_fonc_100 = ${tempFonc100Dpe}). Repli sur l'année de construction.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
66
229
|
for (const em of em_ch) {
|
|
67
230
|
const em_ch_de = em.donnee_entree;
|
|
68
231
|
const em_ch_du = em.donnee_utilisateur;
|
|
@@ -73,9 +236,7 @@ export function tv_temp_fonc_30_100(di, de, du, em_ch, ac) {
|
|
|
73
236
|
};
|
|
74
237
|
|
|
75
238
|
if (!matcher.periode_emetteurs) {
|
|
76
|
-
|
|
77
|
-
else if (ac < 2000) matcher.periode_emetteurs = 'entre 1981 et 2000';
|
|
78
|
-
else matcher.periode_emetteurs = 'après 2000';
|
|
239
|
+
matcher.periode_emetteurs = periodeEmetteurAnneeConstruction(ac);
|
|
79
240
|
}
|
|
80
241
|
|
|
81
242
|
const row_30 = tv('temp_fonc_30', matcher);
|
|
@@ -38,8 +38,14 @@ vi.mock('./utils.js', () => ({
|
|
|
38
38
|
requestInputID: vi.fn((de, du, field) => de[`enum_${field}_id`])
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
-
const {
|
|
42
|
-
|
|
41
|
+
const {
|
|
42
|
+
tv_temp_fonc_30_100,
|
|
43
|
+
tempDistributionChPourTempFonc,
|
|
44
|
+
calc_generateur_combustion_ch,
|
|
45
|
+
periodeEmetteursDeduiteDuDpe,
|
|
46
|
+
periodeEmetteurAnneeConstruction,
|
|
47
|
+
PERIODES_INSTALLATION_EMETTEUR
|
|
48
|
+
} = await import('./13.2_generateur_combustion_ch.js');
|
|
43
49
|
const { tv } = await import('./utils.js');
|
|
44
50
|
|
|
45
51
|
beforeEach(() => {
|
|
@@ -194,6 +200,284 @@ describe('tv_temp_fonc_30_100 - températures de fonctionnement', () => {
|
|
|
194
200
|
);
|
|
195
201
|
});
|
|
196
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Issue #220 - période d'installation des émetteurs absente du DPE.
|
|
205
|
+
*
|
|
206
|
+
* Mode strict : « Si l'année d'installation des émetteurs est inconnue, prendre l'année de
|
|
207
|
+
* construction du bâtiment. » En mode bug_for_bug_compat uniquement, la période est retrouvée à
|
|
208
|
+
* partir des temp_fonc_30 / temp_fonc_100 d'origine stockées dans le DPE (règle de l'expert).
|
|
209
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
|
|
210
|
+
*/
|
|
211
|
+
describe('tv_temp_fonc_30_100 - période des émetteurs absente (issue #220)', () => {
|
|
212
|
+
/**
|
|
213
|
+
* Table de test (contrôlée, indépendante des tables réelles) pour une chaudière à condensation :
|
|
214
|
+
* [température de distribution][période] -> [id, temp_fonc_30, temp_fonc_100].
|
|
215
|
+
*/
|
|
216
|
+
const TABLE = {
|
|
217
|
+
2: {
|
|
218
|
+
'avant 1981': ['1', '32', '60'],
|
|
219
|
+
'entre 1981 et 2000': ['4', '24.5', '35'],
|
|
220
|
+
'après 2000': ['7', '24.5', '35']
|
|
221
|
+
},
|
|
222
|
+
3: {
|
|
223
|
+
'avant 1981': ['2', '38', '80'],
|
|
224
|
+
'entre 1981 et 2000': ['5', '35', '70'],
|
|
225
|
+
'après 2000': ['8', '32', '60']
|
|
226
|
+
},
|
|
227
|
+
4: {
|
|
228
|
+
'avant 1981': ['3', '38', '80'],
|
|
229
|
+
'entre 1981 et 2000': ['6', '35', '70'],
|
|
230
|
+
'après 2000': ['9', '35', '70']
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function tvTable(table, matcher) {
|
|
235
|
+
const ligne = TABLE[matcher.enum_temp_distribution_ch_id]?.[matcher.periode_emetteurs];
|
|
236
|
+
if (!ligne) return null;
|
|
237
|
+
return table === 'temp_fonc_30'
|
|
238
|
+
? { tv_temp_fonc_30_id: `30-${ligne[0]}`, temp_fonc_30: ligne[1] }
|
|
239
|
+
: { tv_temp_fonc_100_id: `100-${ligne[0]}`, temp_fonc_100: ligne[2] };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function emetteur(tempDistribution, periode) {
|
|
243
|
+
const de = { enum_temp_distribution_ch_id: tempDistribution };
|
|
244
|
+
if (periode) de.periode_installation_emetteur = periode;
|
|
245
|
+
return { donnee_entree: de, donnee_utilisateur: {} };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const genCondensation = () => ({ enum_type_generateur_ch_id: '97', description: 'chaudière' });
|
|
249
|
+
|
|
250
|
+
let warnSpy;
|
|
251
|
+
beforeEach(() => {
|
|
252
|
+
tv.mockImplementation(tvTable);
|
|
253
|
+
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
254
|
+
return () => warnSpy.mockRestore();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('T1 strict : période absente, construction 1900 -> « avant 1981 » (38/80), valeurs du DPE ignorées', () => {
|
|
258
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60 };
|
|
259
|
+
const de = genCondensation();
|
|
260
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3')], 1900);
|
|
261
|
+
// Valeurs DPE (32) < table (38) : la table l'emporte, aucune déduction en mode strict
|
|
262
|
+
expect(di.temp_fonc_30).toBe(38);
|
|
263
|
+
expect(di.temp_fonc_100).toBe(80);
|
|
264
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-2');
|
|
265
|
+
expect(de.tv_temp_fonc_100_id).toBe('100-2');
|
|
266
|
+
expect(tv).not.toHaveBeenCalledWith(
|
|
267
|
+
'temp_fonc_30',
|
|
268
|
+
expect.objectContaining({ periode_emetteurs: 'après 2000' })
|
|
269
|
+
);
|
|
270
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('T2 bug_for_bug_compat : DPE à 32/60 -> période « après 2000 » déduite, valeurs et ids issus des tables', () => {
|
|
274
|
+
state.bug = true;
|
|
275
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60 };
|
|
276
|
+
const de = genCondensation();
|
|
277
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3')], 1900);
|
|
278
|
+
expect(di.temp_fonc_30).toBe(32);
|
|
279
|
+
expect(di.temp_fonc_100).toBe(60);
|
|
280
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-8');
|
|
281
|
+
expect(de.tv_temp_fonc_100_id).toBe('100-8');
|
|
282
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
283
|
+
expect.stringContaining(
|
|
284
|
+
"période d'installation des émetteurs déduite des données intermédiaires du DPE : après 2000"
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('T2 bis bug_for_bug_compat : valeurs du DPE sous forme de chaînes acceptées', () => {
|
|
290
|
+
state.bug = true;
|
|
291
|
+
const di = { temp_fonc_30: '32', temp_fonc_100: '60' };
|
|
292
|
+
tv_temp_fonc_30_100(di, genCondensation(), {}, [emetteur('3')], 1900);
|
|
293
|
+
expect(di.temp_fonc_30).toBe(32);
|
|
294
|
+
expect(di.temp_fonc_100).toBe(60);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('T3 bug_for_bug_compat : période saisie « avant 1981 » jamais écrasée (38/80 conservé)', () => {
|
|
298
|
+
state.bug = true;
|
|
299
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60 };
|
|
300
|
+
const de = genCondensation();
|
|
301
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3', 'avant 1981')], 1900);
|
|
302
|
+
expect(di.temp_fonc_30).toBe(38);
|
|
303
|
+
expect(di.temp_fonc_100).toBe(80);
|
|
304
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-2');
|
|
305
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('T4 bug_for_bug_compat : distribution haute, construction 1990, DPE 35/70 -> 2 candidates, période de construction retenue', () => {
|
|
309
|
+
state.bug = true;
|
|
310
|
+
const di = { temp_fonc_30: 35, temp_fonc_100: 70 };
|
|
311
|
+
const de = genCondensation();
|
|
312
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('4')], 1990);
|
|
313
|
+
expect(di.temp_fonc_30).toBe(35);
|
|
314
|
+
expect(di.temp_fonc_100).toBe(70);
|
|
315
|
+
// « entre 1981 et 2000 » (ids 6) et non « après 2000 » (ids 9)
|
|
316
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-6');
|
|
317
|
+
expect(de.tv_temp_fonc_100_id).toBe('100-6');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('T5 bug_for_bug_compat : DPE 33/62 hors tables -> repli sur l’année de construction et avertissement', () => {
|
|
321
|
+
state.bug = true;
|
|
322
|
+
const di = { temp_fonc_30: 33, temp_fonc_100: 62 };
|
|
323
|
+
const de = genCondensation();
|
|
324
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3')], 1990);
|
|
325
|
+
// Repli strict : « entre 1981 et 2000 » -> 35/70 (> 33/62 donc retenu)
|
|
326
|
+
expect(di.temp_fonc_30).toBe(35);
|
|
327
|
+
expect(di.temp_fonc_100).toBe(70);
|
|
328
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-5');
|
|
329
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
330
|
+
expect.stringContaining("valeurs hors tables / donnée d'entrée incohérente")
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test.each([
|
|
335
|
+
['temp_fonc_100 absente', { temp_fonc_30: 32 }],
|
|
336
|
+
['temp_fonc_30 absente', { temp_fonc_100: 60 }],
|
|
337
|
+
['temp_fonc_30 non numérique', { temp_fonc_30: 'abc', temp_fonc_100: 60 }],
|
|
338
|
+
['temp_fonc_100 vide', { temp_fonc_30: 32, temp_fonc_100: '' }],
|
|
339
|
+
['temp_fonc_30 nulle', { temp_fonc_30: null, temp_fonc_100: 60 }]
|
|
340
|
+
])(
|
|
341
|
+
'T6 bug_for_bug_compat : %s -> aucune déduction (règle de l’année de construction)',
|
|
342
|
+
(_, di) => {
|
|
343
|
+
state.bug = true;
|
|
344
|
+
tv_temp_fonc_30_100(di, genCondensation(), {}, [emetteur('3')], 1900);
|
|
345
|
+
expect(tv).not.toHaveBeenCalledWith(
|
|
346
|
+
'temp_fonc_30',
|
|
347
|
+
expect.objectContaining({ periode_emetteurs: 'après 2000' })
|
|
348
|
+
);
|
|
349
|
+
expect(tv).not.toHaveBeenCalledWith(
|
|
350
|
+
'temp_fonc_30',
|
|
351
|
+
expect.objectContaining({ periode_emetteurs: 'entre 1981 et 2000' })
|
|
352
|
+
);
|
|
353
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
test('T7 bug_for_bug_compat : deux émetteurs sans période (moyenne + haute), DPE 35/70 -> même période pour tous, règle du maximum', () => {
|
|
358
|
+
state.bug = true;
|
|
359
|
+
const di = { temp_fonc_30: 35, temp_fonc_100: 70 };
|
|
360
|
+
const de = genCondensation();
|
|
361
|
+
/**
|
|
362
|
+
* avant 1981 : max(38, 38) / max(80, 80) = 38/80 -> rejetée
|
|
363
|
+
* entre 1981 et 2000 : max(35, 35) / max(70, 70) = 35/70 -> candidate
|
|
364
|
+
* après 2000 : max(32, 35) / max(60, 70) = 35/70 -> candidate
|
|
365
|
+
* Construction 1900 (« avant 1981 ») hors candidates -> la plus récente : « après 2000 ».
|
|
366
|
+
*/
|
|
367
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3'), emetteur('4')], 1900);
|
|
368
|
+
expect(di.temp_fonc_30).toBe(35);
|
|
369
|
+
expect(di.temp_fonc_100).toBe(70);
|
|
370
|
+
// Maximum atteint par l'émetteur « haute » en « après 2000 » (ids 9)
|
|
371
|
+
expect(de.tv_temp_fonc_30_id).toBe('30-9');
|
|
372
|
+
expect(de.tv_temp_fonc_100_id).toBe('100-9');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('bug_for_bug_compat : un émetteur saisi et un émetteur sans période -> seule la période absente est déduite', () => {
|
|
376
|
+
state.bug = true;
|
|
377
|
+
const di = { temp_fonc_30: 35, temp_fonc_100: 70 };
|
|
378
|
+
const de = genCondensation();
|
|
379
|
+
// Émetteur saisi « entre 1981 et 2000 » (moyenne : 35/70) + émetteur basse sans période
|
|
380
|
+
tv_temp_fonc_30_100(di, de, {}, [emetteur('3', 'entre 1981 et 2000'), emetteur('2')], 1900);
|
|
381
|
+
expect(di.temp_fonc_30).toBe(35);
|
|
382
|
+
expect(di.temp_fonc_100).toBe(70);
|
|
383
|
+
expect(tv).toHaveBeenCalledWith(
|
|
384
|
+
'temp_fonc_30',
|
|
385
|
+
expect.objectContaining({
|
|
386
|
+
enum_temp_distribution_ch_id: '3',
|
|
387
|
+
periode_emetteurs: 'entre 1981 et 2000'
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
expect(tv).not.toHaveBeenCalledWith(
|
|
391
|
+
'temp_fonc_30',
|
|
392
|
+
expect.objectContaining({
|
|
393
|
+
enum_temp_distribution_ch_id: '3',
|
|
394
|
+
periode_emetteurs: 'après 2000'
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('bug_for_bug_compat : émetteur sans réseau de distribution (id 1) exclu', () => {
|
|
400
|
+
state.bug = true;
|
|
401
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60 };
|
|
402
|
+
tv_temp_fonc_30_100(di, genCondensation(), {}, [emetteur('1'), emetteur('3')], 1900);
|
|
403
|
+
expect(di.temp_fonc_30).toBe(32);
|
|
404
|
+
expect(di.temp_fonc_100).toBe(60);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('bug_for_bug_compat : seul un émetteur sans réseau de distribution est sans période -> aucune déduction', () => {
|
|
408
|
+
state.bug = true;
|
|
409
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60 };
|
|
410
|
+
tv_temp_fonc_30_100(
|
|
411
|
+
di,
|
|
412
|
+
genCondensation(),
|
|
413
|
+
{},
|
|
414
|
+
[emetteur('1'), emetteur('3', 'avant 1981')],
|
|
415
|
+
1900
|
|
416
|
+
);
|
|
417
|
+
expect(di.temp_fonc_30).toBe(38);
|
|
418
|
+
expect(di.temp_fonc_100).toBe(80);
|
|
419
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('bug_for_bug_compat : une seule des deux valeurs reproduite -> période rejetée (tolérance 0,01 °C)', () => {
|
|
423
|
+
state.bug = true;
|
|
424
|
+
// 32/60.02 : « après 2000 » ne reproduit que temp_fonc_30 -> aucune candidate
|
|
425
|
+
const di = { temp_fonc_30: 32, temp_fonc_100: 60.02 };
|
|
426
|
+
tv_temp_fonc_30_100(di, genCondensation(), {}, [emetteur('3')], 1900);
|
|
427
|
+
expect(di.temp_fonc_30).toBe(38);
|
|
428
|
+
expect(di.temp_fonc_100).toBe(80);
|
|
429
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('valeurs hors tables'));
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('bug_for_bug_compat : écart inférieur à la tolérance accepté', () => {
|
|
433
|
+
state.bug = true;
|
|
434
|
+
const di = { temp_fonc_30: 32.005, temp_fonc_100: 59.995 };
|
|
435
|
+
tv_temp_fonc_30_100(di, genCondensation(), {}, [emetteur('3')], 1900);
|
|
436
|
+
expect(di.temp_fonc_30).toBe(32);
|
|
437
|
+
expect(di.temp_fonc_100).toBe(60);
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('periodeEmetteursDeduiteDuDpe', () => {
|
|
442
|
+
const de = { enum_type_generateur_ch_id: '97' };
|
|
443
|
+
const em = [{ donnee_entree: { enum_temp_distribution_ch_id: '3' }, donnee_utilisateur: {} }];
|
|
444
|
+
|
|
445
|
+
test('aucune ligne de table trouvée pour une période : période non candidate', () => {
|
|
446
|
+
tv.mockReturnValue(null);
|
|
447
|
+
expect(periodeEmetteursDeduiteDuDpe(32, 60, de, em, 1900)).toBeNull();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('ligne temp_fonc_100 absente : période non candidate', () => {
|
|
451
|
+
tv.mockImplementation((table) =>
|
|
452
|
+
table === 'temp_fonc_30' ? { tv_temp_fonc_30_id: '1', temp_fonc_30: '32' } : null
|
|
453
|
+
);
|
|
454
|
+
expect(periodeEmetteursDeduiteDuDpe(32, 60, de, em, 1900)).toBeNull();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('toutes les périodes candidates : période de construction retenue', () => {
|
|
458
|
+
tv.mockImplementation((table) =>
|
|
459
|
+
table === 'temp_fonc_30'
|
|
460
|
+
? { tv_temp_fonc_30_id: '1', temp_fonc_30: '32' }
|
|
461
|
+
: { tv_temp_fonc_100_id: '1', temp_fonc_100: '60' }
|
|
462
|
+
);
|
|
463
|
+
expect(periodeEmetteursDeduiteDuDpe(32, 60, de, em, 1900)).toBe(
|
|
464
|
+
PERIODES_INSTALLATION_EMETTEUR[0]
|
|
465
|
+
);
|
|
466
|
+
expect(periodeEmetteursDeduiteDuDpe(32, 60, de, em, 2010)).toBe('après 2000');
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe('periodeEmetteurAnneeConstruction', () => {
|
|
471
|
+
test.each([
|
|
472
|
+
[1980, 'avant 1981'],
|
|
473
|
+
[1981, 'entre 1981 et 2000'],
|
|
474
|
+
[1999, 'entre 1981 et 2000'],
|
|
475
|
+
[2000, 'après 2000']
|
|
476
|
+
])('année de construction %s : %s', (ac, periode) => {
|
|
477
|
+
expect(periodeEmetteurAnneeConstruction(ac)).toBe(periode);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
197
481
|
describe('tempDistributionChPourTempFonc', () => {
|
|
198
482
|
test('émetteur sans type d’émission : température déclarée renvoyée telle quelle', () => {
|
|
199
483
|
expect(tempDistributionChPourTempFonc({ enum_temp_distribution_ch_id: '3' }, {})).toBe('3');
|
package/README.md
CHANGED
|
@@ -370,57 +370,57 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
370
370
|
|
|
371
371
|
<!-- CORPUS:START -->
|
|
372
372
|
|
|
373
|
-
> **Version `1.8.
|
|
373
|
+
> **Version `1.8.14`** · à publier · branche `main` · généré le 2026-10-02
|
|
374
374
|
> Seuil de tolérance **5%**
|
|
375
375
|
|
|
376
376
|
<table>
|
|
377
377
|
<tr>
|
|
378
378
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
379
379
|
<td align="center"><strong>89 990</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
-
<td align="center"><strong>60
|
|
381
|
-
<td align="center"><strong>
|
|
380
|
+
<td align="center"><strong>60 314</strong><br/><sub>DPE conformes</sub></td>
|
|
381
|
+
<td align="center"><strong>67,02 %</strong><br/><sub>réussite globale</sub></td>
|
|
382
382
|
</tr>
|
|
383
383
|
</table>
|
|
384
384
|
|
|
385
385
|
| | Corpus | Réussite | | DPE conformes |
|
|
386
386
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
387
|
-
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,
|
|
387
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,36 %** | `█████████░░░░░░░░░░░` | 4 636 / 10 000 |
|
|
388
388
|
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,42 %** | `██████████████████░░` | 9 242 / 10 000 |
|
|
389
|
-
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,
|
|
390
|
-
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,
|
|
391
|
-
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **
|
|
392
|
-
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,
|
|
393
|
-
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,
|
|
394
|
-
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **49,
|
|
389
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,42 %** | `██████████████████░░` | 8 935 / 9 992 |
|
|
390
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,50 %** | `██████████████████░░` | 8 850 / 10 000 |
|
|
391
|
+
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **75,85 %** | `███████████████░░░░░` | 7 584 / 9 999 |
|
|
392
|
+
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,18 %** | `██████████████░░░░░░` | 7 118 / 10 000 |
|
|
393
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,67 %** | `█████████████░░░░░░░` | 6 266 / 9 999 |
|
|
394
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **49,44 %** | `██████████░░░░░░░░░░` | 4 944 / 10 000 |
|
|
395
395
|
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,39 %** | `█████░░░░░░░░░░░░░░░` | 2 739 / 10 000 |
|
|
396
396
|
|
|
397
397
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
398
398
|
|
|
399
399
|
#### Temps d’exécution
|
|
400
400
|
|
|
401
|
-
> Durée de l’appel à `calcul_3cl` par DPE, sur 89
|
|
401
|
+
> Durée de l’appel à `calcul_3cl` par DPE, sur 89 956 calculs.
|
|
402
402
|
> La copie défensive de l’entrée et la lecture du fichier sont exclues de la mesure.
|
|
403
403
|
|
|
404
404
|
<table>
|
|
405
405
|
<tr>
|
|
406
|
-
<td align="center"><strong>5,
|
|
407
|
-
<td align="center"><strong>4,
|
|
406
|
+
<td align="center"><strong>5,23 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>4,55 ms</strong><br/><sub>médiane</sub></td>
|
|
408
408
|
<td align="center"><strong>0,11 ms</strong><br/><sub>min</sub></td>
|
|
409
|
-
<td align="center"><strong>
|
|
409
|
+
<td align="center"><strong>622,7 ms</strong><br/><sub>max</sub></td>
|
|
410
410
|
</tr>
|
|
411
411
|
</table>
|
|
412
412
|
|
|
413
413
|
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
414
414
|
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
415
|
-
| **Généraliste** | 4,
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** | 3,
|
|
417
|
-
| **Logement individuel (2025)** |
|
|
418
|
-
| **Maison individuelle (2025)** |
|
|
419
|
-
| **Immeuble · chauffage individuel** | 6,
|
|
420
|
-
| **Appartement · chauffage collectif (2025)** | 4,
|
|
421
|
-
| **Immeuble · chauffage collectif** |
|
|
422
|
-
| **Immeuble · chauffage mixte** |
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** | 5,
|
|
415
|
+
| **Généraliste** | 4,22 ms | 3,76 ms | 0,22 ms | 34,7 ms | 7,72 ms | 11,0 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 3,84 ms | 3,52 ms | 1,45 ms | 30,9 ms | 6,58 ms | 8,40 ms |
|
|
417
|
+
| **Logement individuel (2025)** | 4,70 ms | 4,15 ms | 1,59 ms | 35,3 ms | 8,68 ms | 12,1 ms |
|
|
418
|
+
| **Maison individuelle (2025)** | 6,54 ms | 5,89 ms | 1,98 ms | 41,2 ms | 11,9 ms | 17,0 ms |
|
|
419
|
+
| **Immeuble · chauffage individuel** | 6,29 ms | 5,33 ms | 0,11 ms | 132,2 ms | 12,8 ms | 20,0 ms |
|
|
420
|
+
| **Appartement · chauffage collectif (2025)** | 4,41 ms | 4,00 ms | 1,48 ms | 33,8 ms | 7,37 ms | 9,64 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 6,38 ms | 5,37 ms | 0,20 ms | 105,0 ms | 13,4 ms | 24,1 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 4,95 ms | 4,03 ms | 0,20 ms | 622,7 ms | 10,3 ms | 16,3 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 5,76 ms | 4,91 ms | 1,65 ms | 48,0 ms | 11,0 ms | 16,8 ms |
|
|
424
424
|
|
|
425
425
|
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
426
426
|
dizaines de fois la médiane : c’est la médiane qui décrit le cas courant, et p95/p99 le cas
|
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.
|
|
36
|
+
const LIB_VERSION = '1.8.15';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { ChTvStore } from '../../../dpe/infrastructure/ch/chTv.store.js';
|
|
2
2
|
import { inject } from 'dioma';
|
|
3
|
+
import { bug_for_bug_compat } from '../../../../utils.js';
|
|
4
|
+
import { logger } from '../../../../core/util/logger/log-service.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Périodes d'installation des émetteurs (enum_periode_installation_emetteur_id), de la plus
|
|
8
|
+
* ancienne à la plus récente.
|
|
9
|
+
*/
|
|
10
|
+
export const PERIODES_INSTALLATION_EMETTEUR = [1, 2, 3];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tolérance (°C) pour considérer qu'une température de fonctionnement issue des tables reproduit
|
|
14
|
+
* celle stockée dans le DPE.
|
|
15
|
+
*/
|
|
16
|
+
export const TOLERANCE_TEMP_FONC = 0.01;
|
|
17
|
+
|
|
18
|
+
function estNumerique(valeur) {
|
|
19
|
+
return (
|
|
20
|
+
valeur !== null && valeur !== undefined && valeur !== '' && Number.isFinite(Number(valeur))
|
|
21
|
+
);
|
|
22
|
+
}
|
|
3
23
|
|
|
4
24
|
/**
|
|
5
25
|
* Calcul des données des émetteurs de chauffage
|
|
@@ -28,69 +48,173 @@ export class EmetteurChService {
|
|
|
28
48
|
*
|
|
29
49
|
* @param ctx {Contexte}
|
|
30
50
|
* @param emetteurChauffage {EmetteurChauffage}
|
|
51
|
+
* @param periodeParDefaut {number|undefined} période à retenir si elle n'est pas saisie (déduite
|
|
52
|
+
* des données intermédiaires du DPE en mode bug_for_bug_compat, cf. issue #220)
|
|
31
53
|
*/
|
|
32
|
-
periodeInstallationEmetteur(ctx, emetteurChauffage) {
|
|
54
|
+
periodeInstallationEmetteur(ctx, emetteurChauffage, periodeParDefaut = undefined) {
|
|
33
55
|
const periodeInstallationEmetteur = parseInt(
|
|
34
56
|
emetteurChauffage.donnee_entree.enum_periode_installation_emetteur_id
|
|
35
57
|
);
|
|
36
58
|
|
|
37
59
|
if (!periodeInstallationEmetteur) {
|
|
60
|
+
if (periodeParDefaut) {
|
|
61
|
+
return periodeParDefaut;
|
|
62
|
+
}
|
|
38
63
|
return ctx.anneeConstruction < 1981 ? 1 : ctx.anneeConstruction < 2000 ? 2 : 3;
|
|
39
64
|
}
|
|
40
65
|
|
|
41
66
|
return periodeInstallationEmetteur;
|
|
42
67
|
}
|
|
43
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Émetteurs pris en compte pour les températures de fonctionnement : ceux disposant d'un réseau
|
|
71
|
+
* de distribution (enum_temp_distribution_ch_id ≠ 1).
|
|
72
|
+
*
|
|
73
|
+
* @param emetteursChauffage {EmetteurChauffage[]}
|
|
74
|
+
* @return {EmetteurChauffage[]}
|
|
75
|
+
*/
|
|
76
|
+
#emetteursAvecDistribution(emetteursChauffage) {
|
|
77
|
+
return emetteursChauffage.filter(
|
|
78
|
+
(emetteurChauffage) =>
|
|
79
|
+
parseInt(emetteurChauffage.donnee_entree.enum_temp_distribution_ch_id) !== 1
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mode bug_for_bug_compat uniquement (issue #220).
|
|
85
|
+
*
|
|
86
|
+
* Certains logiciels calculent les températures de fonctionnement avec une période
|
|
87
|
+
* d'installation des émetteurs qu'ils n'exportent pas, mais stockent temp_fonc_30 /
|
|
88
|
+
* temp_fonc_100 dans les données intermédiaires du générateur. Chaque période candidate est
|
|
89
|
+
* affectée à tous les émetteurs sans période saisie ; seules celles qui reproduisent les DEUX
|
|
90
|
+
* valeurs du DPE (tolérance 0,01 °C) sont retenues :
|
|
91
|
+
* - une seule candidate : retenue ;
|
|
92
|
+
* - plusieurs : celle de l'année de construction si elle en fait partie, sinon la plus récente ;
|
|
93
|
+
* - aucune : undefined (repli sur l'année de construction).
|
|
94
|
+
*
|
|
95
|
+
* Aucune déduction si le mode bug_for_bug_compat est désactivé, si l'une des deux températures
|
|
96
|
+
* du DPE est absente ou non numérique, ou si tous les émetteurs ont une période saisie.
|
|
97
|
+
*
|
|
98
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.1.5
|
|
99
|
+
*
|
|
100
|
+
* @param ctx {Contexte}
|
|
101
|
+
* @param generateurChauffageDE {GenerateurChauffageDE}
|
|
102
|
+
* @param emetteursChauffage {EmetteurChauffage[]}
|
|
103
|
+
* @param temperaturesDpe {{temp_fonc_30: number, temp_fonc_100: number}|undefined} valeurs
|
|
104
|
+
* d'origine des données intermédiaires du générateur dans le DPE
|
|
105
|
+
* @return {number|undefined} période déduite (enum_periode_installation_emetteur_id)
|
|
106
|
+
*/
|
|
107
|
+
periodeInstallationEmetteurDeduite(
|
|
108
|
+
ctx,
|
|
109
|
+
generateurChauffageDE,
|
|
110
|
+
emetteursChauffage,
|
|
111
|
+
temperaturesDpe
|
|
112
|
+
) {
|
|
113
|
+
if (
|
|
114
|
+
!bug_for_bug_compat ||
|
|
115
|
+
!estNumerique(temperaturesDpe?.temp_fonc_30) ||
|
|
116
|
+
!estNumerique(temperaturesDpe?.temp_fonc_100)
|
|
117
|
+
) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const emetteursSansPeriode = this.#emetteursAvecDistribution(emetteursChauffage).filter(
|
|
122
|
+
(emetteurChauffage) =>
|
|
123
|
+
!parseInt(emetteurChauffage.donnee_entree.enum_periode_installation_emetteur_id)
|
|
124
|
+
);
|
|
125
|
+
if (emetteursSansPeriode.length === 0) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const tempFonc30Dpe = Number(temperaturesDpe.temp_fonc_30);
|
|
130
|
+
const tempFonc100Dpe = Number(temperaturesDpe.temp_fonc_100);
|
|
131
|
+
|
|
132
|
+
const candidates = PERIODES_INSTALLATION_EMETTEUR.filter((periode) => {
|
|
133
|
+
const { temp_fonc_30, temp_fonc_100 } = this.temperatureFonctionnement(
|
|
134
|
+
ctx,
|
|
135
|
+
generateurChauffageDE,
|
|
136
|
+
emetteursChauffage,
|
|
137
|
+
periode
|
|
138
|
+
);
|
|
139
|
+
return (
|
|
140
|
+
estNumerique(temp_fonc_30) &&
|
|
141
|
+
estNumerique(temp_fonc_100) &&
|
|
142
|
+
Math.abs(temp_fonc_30 - tempFonc30Dpe) <= TOLERANCE_TEMP_FONC &&
|
|
143
|
+
Math.abs(temp_fonc_100 - tempFonc100Dpe) <= TOLERANCE_TEMP_FONC
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (candidates.length === 0) {
|
|
148
|
+
logger.warn(
|
|
149
|
+
`période d'installation des émetteurs non déductible : valeurs hors tables / donnée d'entrée incohérente (temp_fonc_30 = ${tempFonc30Dpe}, temp_fonc_100 = ${tempFonc100Dpe}). Repli sur l'année de construction.`
|
|
150
|
+
);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const periodeConstruction = this.periodeInstallationEmetteur(ctx, { donnee_entree: {} });
|
|
155
|
+
const periode = candidates.includes(periodeConstruction)
|
|
156
|
+
? periodeConstruction
|
|
157
|
+
: candidates[candidates.length - 1];
|
|
158
|
+
|
|
159
|
+
logger.info(
|
|
160
|
+
`période d'installation des émetteurs déduite des données intermédiaires du DPE : ${periode} (temp_fonc_30 = ${tempFonc30Dpe}, temp_fonc_100 = ${tempFonc100Dpe})`
|
|
161
|
+
);
|
|
162
|
+
return periode;
|
|
163
|
+
}
|
|
164
|
+
|
|
44
165
|
/**
|
|
45
166
|
* Calcul des températures de fonctionnement à 30% ou 100%
|
|
46
167
|
*
|
|
47
168
|
* @param ctx {Contexte}
|
|
48
169
|
* @param generateurChauffageDE {GenerateurChauffageDE}
|
|
49
170
|
* @param emetteursChauffage {EmetteurChauffage[]}
|
|
171
|
+
* @param periodeParDefaut {number|undefined} période affectée aux émetteurs sans période saisie
|
|
172
|
+
* (à défaut : année de construction)
|
|
50
173
|
* @return {{temp_fonc_30: number, temp_fonc_100: number}}
|
|
51
174
|
*/
|
|
52
|
-
temperatureFonctionnement(
|
|
175
|
+
temperatureFonctionnement(
|
|
176
|
+
ctx,
|
|
177
|
+
generateurChauffageDE,
|
|
178
|
+
emetteursChauffage,
|
|
179
|
+
periodeParDefaut = undefined
|
|
180
|
+
) {
|
|
53
181
|
let temperatureFonctionnement30;
|
|
54
182
|
let temperatureFonctionnement100;
|
|
55
183
|
|
|
56
|
-
emetteursChauffage
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!temperatureFonctionnement100 || tempFonctionnement100 > temperatureFonctionnement100) {
|
|
91
|
-
temperatureFonctionnement100 = tempFonctionnement100;
|
|
92
|
-
}
|
|
93
|
-
});
|
|
184
|
+
this.#emetteursAvecDistribution(emetteursChauffage).forEach((emetteurChauffage) => {
|
|
185
|
+
// Récupération de la température de distribution
|
|
186
|
+
const temperatureDistribution = Number(
|
|
187
|
+
emetteurChauffage.donnee_entree.enum_temp_distribution_ch_id
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Récupération de la période d'installation de distribution
|
|
191
|
+
const periodeInstallationEmetteur = this.periodeInstallationEmetteur(
|
|
192
|
+
ctx,
|
|
193
|
+
emetteurChauffage,
|
|
194
|
+
periodeParDefaut
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const tempFonctionnement30 = this.#chTvStore.temperatureFonctionnement(
|
|
198
|
+
'30',
|
|
199
|
+
generateurChauffageDE.enum_type_generateur_ch_id,
|
|
200
|
+
temperatureDistribution,
|
|
201
|
+
periodeInstallationEmetteur
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const tempFonctionnement100 = this.#chTvStore.temperatureFonctionnement(
|
|
205
|
+
'100',
|
|
206
|
+
generateurChauffageDE.enum_type_generateur_ch_id,
|
|
207
|
+
temperatureDistribution,
|
|
208
|
+
periodeInstallationEmetteur
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (!temperatureFonctionnement30 || tempFonctionnement30 > temperatureFonctionnement30) {
|
|
212
|
+
temperatureFonctionnement30 = tempFonctionnement30;
|
|
213
|
+
}
|
|
214
|
+
if (!temperatureFonctionnement100 || tempFonctionnement100 > temperatureFonctionnement100) {
|
|
215
|
+
temperatureFonctionnement100 = tempFonctionnement100;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
94
218
|
|
|
95
219
|
return {
|
|
96
220
|
temp_fonc_30: temperatureFonctionnement30,
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
});
|