@open3cl/engine 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/11_ecs.js +5 -1
- package/11_ecs.spec.js +1 -1
- package/13.2_generateur_combustion_ch.js +34 -2
- package/13.2_generateur_combustion_ch.spec.js +148 -15
- package/README.md +26 -26
- package/engine.js +1 -1
- package/package.json +6 -3
- package/utils.spec.js +28 -1
package/11_ecs.js
CHANGED
|
@@ -13,7 +13,11 @@ function tv_rendement_distribution_ecs(di, de, du, pvc) {
|
|
|
13
13
|
let configuration_logement;
|
|
14
14
|
if (type_installation.includes('individuelle')) {
|
|
15
15
|
if (pvc === 1) {
|
|
16
|
-
|
|
16
|
+
// Motif ancré « ^…+…$ » : compatible avec les deux matchers de tv().
|
|
17
|
+
// - tvMatchLegacy échappe le « + » d'un motif ancré avant le .match() regex ;
|
|
18
|
+
// - tvMatchOptimized retire les ancres puis compare la chaîne littérale.
|
|
19
|
+
// L'ancien « [+] » (échappement regex) ne matchait jamais en version optimisée (#206).
|
|
20
|
+
configuration_logement = '^production volume habitable + pièces alimentées contiguës$';
|
|
17
21
|
} else configuration_logement = 'production hors volume habitable';
|
|
18
22
|
} else if (type_installation.includes('collective')) {
|
|
19
23
|
let type_reseau_collectif;
|
package/11_ecs.spec.js
CHANGED
|
@@ -169,7 +169,7 @@ describe('calc_ecs - rendement de distribution', () => {
|
|
|
169
169
|
calc_ecs({}, ecs, 100, 200, 1, 'ca1', 'h1a', 'maison', false, null, null, false);
|
|
170
170
|
|
|
171
171
|
expect(tv).toHaveBeenCalledWith('rendement_distribution_ecs', {
|
|
172
|
-
configuration_logement: 'production volume habitable
|
|
172
|
+
configuration_logement: '^production volume habitable + pièces alimentées contiguës$'
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
|
|
@@ -32,13 +32,43 @@ const K = {
|
|
|
32
32
|
'réseau de froid urbain': undefined
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Planchers / plafonds chauffants sur réseau d'eau chaude « basse ou moyenne température
|
|
37
|
+
* (inf 65°c) » (enum_type_emission_distribution_id 12, 14, 16, 18).
|
|
38
|
+
*/
|
|
39
|
+
export const EMISSION_PLANCHER_PLAFOND_CHAUFFANT_BT = ['12', '14', '16', '18'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Température de distribution à utiliser pour les tables temp_fonc_30 / temp_fonc_100.
|
|
43
|
+
*
|
|
44
|
+
* Un plancher (ou plafond) chauffant à eau < 65 °C fonctionne en basse température : comme le
|
|
45
|
+
* moteur de référence CSTB (Tribu), qui déduit la ligne « basse » du type d'émetteur plancher
|
|
46
|
+
* chauffant, on retient « basse » (id 2) lorsque le DPE déclare « moyenne » (id 3) pour ces
|
|
47
|
+
* émetteurs. Les logiciels du marché (ex. LICIEL) calculent ainsi mais exportent « moyenne ».
|
|
48
|
+
*
|
|
49
|
+
* @param emChDe {object} donnee_entree de l'émetteur
|
|
50
|
+
* @param emChDu {object} donnee_utilisateur de l'émetteur
|
|
51
|
+
* @return {string|undefined}
|
|
52
|
+
*/
|
|
53
|
+
export function tempDistributionChPourTempFonc(emChDe, emChDu) {
|
|
54
|
+
const tempDistribution = requestInputID(emChDe, emChDu, 'temp_distribution_ch');
|
|
55
|
+
const typeEmission = String(emChDe?.enum_type_emission_distribution_id ?? '');
|
|
56
|
+
if (
|
|
57
|
+
String(tempDistribution) === '3' &&
|
|
58
|
+
EMISSION_PLANCHER_PLAFOND_CHAUFFANT_BT.includes(typeEmission)
|
|
59
|
+
) {
|
|
60
|
+
return '2';
|
|
61
|
+
}
|
|
62
|
+
return tempDistribution;
|
|
63
|
+
}
|
|
64
|
+
|
|
35
65
|
export function tv_temp_fonc_30_100(di, de, du, em_ch, ac) {
|
|
36
66
|
for (const em of em_ch) {
|
|
37
67
|
const em_ch_de = em.donnee_entree;
|
|
38
68
|
const em_ch_du = em.donnee_utilisateur;
|
|
39
69
|
const matcher = {
|
|
40
70
|
enum_type_generateur_ch_id: de.enum_type_generateur_ch_id,
|
|
41
|
-
enum_temp_distribution_ch_id:
|
|
71
|
+
enum_temp_distribution_ch_id: tempDistributionChPourTempFonc(em_ch_de, em_ch_du),
|
|
42
72
|
periode_emetteurs: requestInput(em_ch_de, em_ch_du, 'periode_installation_emetteur')
|
|
43
73
|
};
|
|
44
74
|
|
|
@@ -180,7 +210,9 @@ export function calc_generateur_combustion_ch(dpe, di, de, du) {
|
|
|
180
210
|
|
|
181
211
|
// Pveil and QP0 are in kW
|
|
182
212
|
const Pveil = di.pveil / 1000;
|
|
183
|
-
|
|
213
|
+
// rg est d'abord calculé sur PCS (puis × k pour revenir en PCI) : QP0 doit donc être exprimé
|
|
214
|
+
// sur PCS, comme dans QPx() (qp0 × k). Cf. issue #205.
|
|
215
|
+
const QP0 = (di.qp0 * k) / 1000;
|
|
184
216
|
|
|
185
217
|
const rg_pcs = Pmfou / (Pmcons + 0.45 * QP0 + Pveil);
|
|
186
218
|
const rg_pcs_dep = Pmfou_dep / (Pmcons_dep + 0.45 * QP0 + Pveil);
|
|
@@ -38,7 +38,7 @@ vi.mock('./utils.js', () => ({
|
|
|
38
38
|
requestInputID: vi.fn((de, du, field) => de[`enum_${field}_id`])
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
-
const { tv_temp_fonc_30_100, calc_generateur_combustion_ch } =
|
|
41
|
+
const { tv_temp_fonc_30_100, tempDistributionChPourTempFonc, calc_generateur_combustion_ch } =
|
|
42
42
|
await import('./13.2_generateur_combustion_ch.js');
|
|
43
43
|
const { tv } = await import('./utils.js');
|
|
44
44
|
|
|
@@ -138,6 +138,75 @@ describe('tv_temp_fonc_30_100 - températures de fonctionnement', () => {
|
|
|
138
138
|
expect(errSpy).toHaveBeenCalledTimes(2);
|
|
139
139
|
errSpy.mockRestore();
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Plancher / plafond chauffant à eau < 65 °C : la ligne « basse » est retenue même si le DPE
|
|
144
|
+
* déclare « moyenne » (comportement du moteur de référence CSTB / Tribu).
|
|
145
|
+
*/
|
|
146
|
+
test.each(['12', '14', '16', '18'])(
|
|
147
|
+
'plancher/plafond chauffant BT (émission %s) déclaré « moyenne » : ligne « basse » utilisée',
|
|
148
|
+
(typeEmission) => {
|
|
149
|
+
tv.mockReturnValue({ tv_temp_fonc_30_id: '1', temp_fonc_30: '24.5' });
|
|
150
|
+
tv_temp_fonc_30_100(
|
|
151
|
+
{},
|
|
152
|
+
{ enum_type_generateur_ch_id: '97' },
|
|
153
|
+
{},
|
|
154
|
+
[emetteur({ enum_type_emission_distribution_id: typeEmission })],
|
|
155
|
+
2018
|
|
156
|
+
);
|
|
157
|
+
expect(tv).toHaveBeenCalledWith(
|
|
158
|
+
'temp_fonc_30',
|
|
159
|
+
expect.objectContaining({ enum_temp_distribution_ch_id: '2' })
|
|
160
|
+
);
|
|
161
|
+
expect(tv).toHaveBeenCalledWith(
|
|
162
|
+
'temp_fonc_100',
|
|
163
|
+
expect.objectContaining({ enum_temp_distribution_ch_id: '2' })
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
test.each([
|
|
169
|
+
['radiateur (35), moyenne', '35', '3', '3'],
|
|
170
|
+
['plancher chauffant HT (13), moyenne', '13', '3', '3'],
|
|
171
|
+
['plancher chauffant BT (14), haute', '14', '4', '4'],
|
|
172
|
+
['plancher chauffant BT (14), basse', '14', '2', '2']
|
|
173
|
+
])(
|
|
174
|
+
'%s : température de distribution déclarée conservée',
|
|
175
|
+
(_, typeEmission, declaree, attendue) => {
|
|
176
|
+
tv.mockReturnValue({ tv_temp_fonc_30_id: '1', temp_fonc_30: '40' });
|
|
177
|
+
tv_temp_fonc_30_100(
|
|
178
|
+
{},
|
|
179
|
+
{ enum_type_generateur_ch_id: '97' },
|
|
180
|
+
{},
|
|
181
|
+
[
|
|
182
|
+
emetteur({
|
|
183
|
+
enum_type_emission_distribution_id: typeEmission,
|
|
184
|
+
enum_temp_distribution_ch_id: declaree
|
|
185
|
+
})
|
|
186
|
+
],
|
|
187
|
+
2018
|
|
188
|
+
);
|
|
189
|
+
expect(tv).toHaveBeenCalledWith(
|
|
190
|
+
'temp_fonc_30',
|
|
191
|
+
expect.objectContaining({ enum_temp_distribution_ch_id: attendue })
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('tempDistributionChPourTempFonc', () => {
|
|
198
|
+
test('émetteur sans type d’émission : température déclarée renvoyée telle quelle', () => {
|
|
199
|
+
expect(tempDistributionChPourTempFonc({ enum_temp_distribution_ch_id: '3' }, {})).toBe('3');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('type d’émission numérique (14) : reconnu comme plancher chauffant BT', () => {
|
|
203
|
+
expect(
|
|
204
|
+
tempDistributionChPourTempFonc(
|
|
205
|
+
{ enum_temp_distribution_ch_id: 3, enum_type_emission_distribution_id: 14 },
|
|
206
|
+
{}
|
|
207
|
+
)
|
|
208
|
+
).toBe('2');
|
|
209
|
+
});
|
|
141
210
|
});
|
|
142
211
|
|
|
143
212
|
/**
|
|
@@ -156,9 +225,9 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
156
225
|
};
|
|
157
226
|
calc_generateur_combustion_ch({}, di, de, { cdimref: 0.8, cdimrefDep: 0.6 });
|
|
158
227
|
// valeurs de référence de régression (module réel, enums réels équivalents)
|
|
159
|
-
expect(di.rg).toBeCloseTo(0.
|
|
160
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
161
|
-
expect(di.rendement_generation).toBeCloseTo(0.
|
|
228
|
+
expect(di.rg).toBeCloseTo(0.8575549045559866, 9);
|
|
229
|
+
expect(di.rg_dep).toBeCloseTo(0.8700736982536121, 9);
|
|
230
|
+
expect(di.rendement_generation).toBeCloseTo(0.8575549045559866, 9);
|
|
162
231
|
});
|
|
163
232
|
|
|
164
233
|
test('chaudière bois : branche de calcul dédiée (QP50 / QP100)', () => {
|
|
@@ -171,8 +240,8 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
171
240
|
};
|
|
172
241
|
calc_generateur_combustion_ch({}, di, de, { cdimref: 0.9, cdimrefDep: 0.7 });
|
|
173
242
|
// valeurs de référence de régression
|
|
174
|
-
expect(di.rg).toBeCloseTo(0.
|
|
175
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
243
|
+
expect(di.rg).toBeCloseTo(0.7040089707689774, 9);
|
|
244
|
+
expect(di.rg_dep).toBeCloseTo(0.713512984577247, 9);
|
|
176
245
|
});
|
|
177
246
|
|
|
178
247
|
test('chaudière à condensation avec régulation : température de fonctionnement à 30 % utilisée', () => {
|
|
@@ -186,8 +255,8 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
186
255
|
};
|
|
187
256
|
calc_generateur_combustion_ch({}, di, de, { cdimref: 0.8, cdimrefDep: 0.6 });
|
|
188
257
|
// valeurs de référence de régression
|
|
189
|
-
expect(di.rg).toBeCloseTo(0.
|
|
190
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
258
|
+
expect(di.rg).toBeCloseTo(0.9275325681176758, 9);
|
|
259
|
+
expect(di.rg_dep).toBeCloseTo(0.9340267431264844, 9);
|
|
191
260
|
});
|
|
192
261
|
|
|
193
262
|
test('radiateur à gaz : branche de calcul dédiée', () => {
|
|
@@ -201,8 +270,8 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
201
270
|
};
|
|
202
271
|
calc_generateur_combustion_ch({}, di, de, { cdimref: 0.8, cdimrefDep: 0.6 });
|
|
203
272
|
// valeurs de référence de régression
|
|
204
|
-
expect(di.rg).toBeCloseTo(0.
|
|
205
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
273
|
+
expect(di.rg).toBeCloseTo(0.8842491989709215, 9);
|
|
274
|
+
expect(di.rg_dep).toBeCloseTo(0.8862325713702337, 9);
|
|
206
275
|
});
|
|
207
276
|
|
|
208
277
|
test('générateur à air chaud : branche de calcul dédiée', () => {
|
|
@@ -231,8 +300,8 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
231
300
|
};
|
|
232
301
|
calc_generateur_combustion_ch({}, di, de, { cdimref: 0.8, cdimrefDep: 0.6 });
|
|
233
302
|
// valeurs de référence de régression
|
|
234
|
-
expect(di.rg).toBeCloseTo(0.
|
|
235
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
303
|
+
expect(di.rg).toBeCloseTo(0.8505103134051144, 9);
|
|
304
|
+
expect(di.rg_dep).toBeCloseTo(0.8649097139284332, 9);
|
|
236
305
|
});
|
|
237
306
|
|
|
238
307
|
test('type de générateur inconnu : aucun rendement calculé (avertissement)', () => {
|
|
@@ -262,8 +331,8 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
262
331
|
};
|
|
263
332
|
calc_generateur_combustion_ch({}, di, de, {});
|
|
264
333
|
// Cdimref = 0 -> x/0 = Infinity -> min(1, ...) = 1 pour tous les x, nominal = dépensier
|
|
265
|
-
expect(di.rg).toBeCloseTo(0.
|
|
266
|
-
expect(di.rg_dep).toBeCloseTo(0.
|
|
334
|
+
expect(di.rg).toBeCloseTo(0.8963697027040485, 9);
|
|
335
|
+
expect(di.rg_dep).toBeCloseTo(0.8963697027040485, 9);
|
|
267
336
|
});
|
|
268
337
|
|
|
269
338
|
test('bug_for_bug_compat : correction de qp0 exprimé en kW (< 1) vers des W', () => {
|
|
@@ -281,7 +350,7 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
281
350
|
// qp0 < 1 -> multiplié par 1000 => 200 (passage kW -> W)
|
|
282
351
|
expect(di.qp0).toBe(200);
|
|
283
352
|
// valeur de référence de régression (chaudière standard, pn = 20000, qp0 corrigé à 200)
|
|
284
|
-
expect(di.rg).toBeCloseTo(0.
|
|
353
|
+
expect(di.rg).toBeCloseTo(0.8557877790761849, 9);
|
|
285
354
|
warnSpy.mockRestore();
|
|
286
355
|
});
|
|
287
356
|
|
|
@@ -320,4 +389,68 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
320
389
|
// Avec régulation (tf plus basse) le rendement est plus élevé
|
|
321
390
|
expect(diAvec.rg).toBeGreaterThan(diSans.rg);
|
|
322
391
|
});
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Référence externe (et non régression) : autotest État CSTB `APP2-0-1`, moteur
|
|
395
|
+
* Moteur_DPE.dll v2025.11.1.0 (Tribu). Chaudière gaz à condensation 2001-2015, Pn = 23 kW,
|
|
396
|
+
* valeurs par défaut. Les grandeurs intermédiaires ci-dessous sont celles de la sortie Tribu
|
|
397
|
+
* (Rpn, Rpint, Qp0, Tfonc30/100, Cdimref = 0.05 / TchFinal(Ch_5)).
|
|
398
|
+
* Avant correctif (QP0 non converti sur PCS) : rg = 0.8597041838253313. Cf. issue #205.
|
|
399
|
+
*/
|
|
400
|
+
test('autotest État APP2-0-1 : rg identique à Tribu (QP0 exprimé sur PCS)', () => {
|
|
401
|
+
const di = {
|
|
402
|
+
pn: 23000,
|
|
403
|
+
rpn: 0.9236172783601759,
|
|
404
|
+
rpint: 0.983617278360176,
|
|
405
|
+
qp0: 230,
|
|
406
|
+
pveil: 0,
|
|
407
|
+
temp_fonc_30: 38,
|
|
408
|
+
temp_fonc_100: 80
|
|
409
|
+
};
|
|
410
|
+
const de = {
|
|
411
|
+
enum_type_generateur_ch_id: '96',
|
|
412
|
+
enum_type_energie_id: '2',
|
|
413
|
+
type_energie: 'gaz naturel',
|
|
414
|
+
presence_regulation_combustion: true,
|
|
415
|
+
description: 'Chaudière gaz condensation'
|
|
416
|
+
};
|
|
417
|
+
const cdimref = 0.05 / 0.00603495141488493;
|
|
418
|
+
calc_generateur_combustion_ch({}, di, de, { cdimref, cdimrefDep: cdimref });
|
|
419
|
+
// Rg Tribu (sortie APP2-0-1_Sortie.xml) : 0.8511886946001722
|
|
420
|
+
expect(di.rg).toBeCloseTo(0.8511886946001722, 12);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('les pertes à charge nulle QP0 réduisent le rendement de génération', () => {
|
|
424
|
+
const di = () => ({
|
|
425
|
+
pn: 23000,
|
|
426
|
+
rpn: 0.92,
|
|
427
|
+
rpint: 0.98,
|
|
428
|
+
qp0: 230,
|
|
429
|
+
pveil: 0,
|
|
430
|
+
temp_fonc_30: 38,
|
|
431
|
+
temp_fonc_100: 80
|
|
432
|
+
});
|
|
433
|
+
const de = {
|
|
434
|
+
enum_type_generateur_ch_id: '96',
|
|
435
|
+
enum_type_energie_id: '2',
|
|
436
|
+
presence_regulation_combustion: true,
|
|
437
|
+
description: 't'
|
|
438
|
+
};
|
|
439
|
+
const avecPertes = di();
|
|
440
|
+
calc_generateur_combustion_ch(
|
|
441
|
+
{},
|
|
442
|
+
avecPertes,
|
|
443
|
+
{ ...de, type_energie: 'gaz naturel' },
|
|
444
|
+
{ cdimref: 8 }
|
|
445
|
+
);
|
|
446
|
+
const sansPertes = { ...di(), qp0: 0 };
|
|
447
|
+
calc_generateur_combustion_ch(
|
|
448
|
+
{},
|
|
449
|
+
sansPertes,
|
|
450
|
+
{ ...de, type_energie: 'gaz naturel' },
|
|
451
|
+
{ cdimref: 8 }
|
|
452
|
+
);
|
|
453
|
+
// le terme 0.45 × QP0 réduit bien le rendement
|
|
454
|
+
expect(avecPertes.rg).toBeLessThan(sansPertes.rg);
|
|
455
|
+
});
|
|
323
456
|
});
|
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.
|
|
373
|
+
> **Version `1.8.1`** · à publier · branche `main` · généré le 2026-10-01
|
|
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
|
-
<td align="center"><strong>89
|
|
380
|
-
<td align="center"><strong>59
|
|
381
|
-
<td align="center"><strong>66,
|
|
379
|
+
<td align="center"><strong>89 990</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
+
<td align="center"><strong>59 630</strong><br/><sub>DPE conformes</sub></td>
|
|
381
|
+
<td align="center"><strong>66,26 %</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> | **
|
|
388
|
-
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,
|
|
389
|
-
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,
|
|
390
|
-
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **
|
|
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> | **69,
|
|
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> | **48,
|
|
395
|
-
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,
|
|
387
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,11 %** | `█████████░░░░░░░░░░░` | 4 611 / 10 000 |
|
|
388
|
+
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,69 %** | `██████████████████░░` | 9 169 / 10 000 |
|
|
389
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,19 %** | `██████████████████░░` | 8 812 / 9 992 |
|
|
390
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,02 %** | `██████████████████░░` | 8 802 / 10 000 |
|
|
391
|
+
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **74,08 %** | `███████████████░░░░░` | 7 407 / 9 999 |
|
|
392
|
+
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **69,67 %** | `██████████████░░░░░░` | 6 967 / 10 000 |
|
|
393
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,42 %** | `████████████░░░░░░░░` | 6 241 / 9 999 |
|
|
394
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **48,82 %** | `██████████░░░░░░░░░░` | 4 882 / 10 000 |
|
|
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 950 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>
|
|
407
|
-
<td align="center"><strong>
|
|
406
|
+
<td align="center"><strong>4,88 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>4,26 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>891,4 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** |
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** | 3,
|
|
417
|
-
| **Logement individuel (2025)** |
|
|
418
|
-
| **Maison individuelle (2025)** |
|
|
419
|
-
| **Immeuble · chauffage individuel** |
|
|
420
|
-
| **Appartement · chauffage collectif (2025)** | 3,
|
|
421
|
-
| **Immeuble · chauffage collectif** |
|
|
422
|
-
| **Immeuble · chauffage mixte** |
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** | 6,
|
|
415
|
+
| **Généraliste** | 3,65 ms | 3,28 ms | 0,21 ms | 29,7 ms | 6,67 ms | 8,87 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 3,54 ms | 3,23 ms | 1,09 ms | 24,6 ms | 6,11 ms | 8,02 ms |
|
|
417
|
+
| **Logement individuel (2025)** | 4,89 ms | 4,37 ms | 1,41 ms | 39,4 ms | 9,35 ms | 12,7 ms |
|
|
418
|
+
| **Maison individuelle (2025)** | 5,30 ms | 4,73 ms | 1,69 ms | 39,6 ms | 9,54 ms | 13,0 ms |
|
|
419
|
+
| **Immeuble · chauffage individuel** | 5,64 ms | 4,78 ms | 0,11 ms | 76,2 ms | 11,4 ms | 17,8 ms |
|
|
420
|
+
| **Appartement · chauffage collectif (2025)** | 3,92 ms | 3,56 ms | 1,18 ms | 32,4 ms | 6,59 ms | 9,44 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 5,56 ms | 4,75 ms | 0,13 ms | 140,4 ms | 10,7 ms | 18,7 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 5,41 ms | 4,41 ms | 0,19 ms | 891,4 ms | 11,0 ms | 18,0 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,04 ms | 5,26 ms | 1,65 ms | 64,0 ms | 11,5 ms | 17,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.2';
|
|
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
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "Open Source 3CL-DPE engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
"prepare": "husky install",
|
|
29
29
|
"assets:sync": "node scripts/synchronize_assets.js",
|
|
30
30
|
"version:update": "node scripts/update_version.js",
|
|
31
|
-
"reports:charts": "node scripts/generate_corpus_history_chart.js",
|
|
32
31
|
"reports:preview": "npm run http-server dist/reports/corpus -o",
|
|
33
|
-
"reports:readme": "node scripts/generate_corpus_readme.js
|
|
32
|
+
"reports:readme": "node scripts/generate_corpus_readme.js",
|
|
33
|
+
"reports:stamp": "node scripts/stamp_corpus_release.js",
|
|
34
|
+
"reports:version": "node scripts/corpus_version.js"
|
|
34
35
|
},
|
|
35
36
|
"repository": {
|
|
36
37
|
"type": "git",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"@commitlint/cz-commitlint": "^19.2.0",
|
|
54
55
|
"@duckdb/node-api": "^1.3.2-alpha.25",
|
|
55
56
|
"@semantic-release/changelog": "^6.0.3",
|
|
57
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
56
58
|
"@semantic-release/exec": "^7.1.0",
|
|
57
59
|
"@semantic-release/git": "^10.0.1",
|
|
58
60
|
"@semantic-release/github": "^12.0.2",
|
|
@@ -71,6 +73,7 @@
|
|
|
71
73
|
"fast-csv": "^5.0.2",
|
|
72
74
|
"http-server": "^14.1.1",
|
|
73
75
|
"husky": "^9.0.11",
|
|
76
|
+
"js-yaml": "^4.3.2",
|
|
74
77
|
"jscpd": "^3.5.10",
|
|
75
78
|
"piscina": "^5.1.2",
|
|
76
79
|
"prettier": "^3.2.5",
|
package/utils.spec.js
CHANGED
|
@@ -23,7 +23,12 @@ vi.mock('./tv.js', () => ({
|
|
|
23
23
|
{ code: 'abc', val: 'r4' }
|
|
24
24
|
],
|
|
25
25
|
// Table dédiée à tvMatchOptimized (version optimisée).
|
|
26
|
-
table_opt: [{ a: 'brique', b: '1|2', c: '≥50', d: 'isolation par exterieur', e: 'zone 3' }]
|
|
26
|
+
table_opt: [{ a: 'brique', b: '1|2', c: '≥50', d: 'isolation par exterieur', e: 'zone 3' }],
|
|
27
|
+
// Table dédiée à la compatibilité des deux matchers sur un libellé contenant « + » (#206).
|
|
28
|
+
table_plus: [
|
|
29
|
+
{ conf: 'production volume habitable + pièces alimentées non contiguës', val: 'p0' },
|
|
30
|
+
{ conf: 'production volume habitable + pièces alimentées contiguës', val: 'p1' }
|
|
31
|
+
]
|
|
27
32
|
}
|
|
28
33
|
}));
|
|
29
34
|
|
|
@@ -431,6 +436,28 @@ describe('Utils unit tests', () => {
|
|
|
431
436
|
});
|
|
432
437
|
});
|
|
433
438
|
|
|
439
|
+
// -------------------------------------------------------------------------------------------
|
|
440
|
+
// Libellé contenant un « + » littéral (ex. rendement_distribution_ecs, cf. issue #206) : le
|
|
441
|
+
// motif ancré « ^…+…$ » doit sélectionner la même ligne dans les deux versions du matcher.
|
|
442
|
+
describe('tv / libellé avec « + » : compatibilité des deux matchers', () => {
|
|
443
|
+
const motifAncre = '^production volume habitable + pièces alimentées contiguës$';
|
|
444
|
+
|
|
445
|
+
test.each([
|
|
446
|
+
['historique', unset_tv_match_optimized_version],
|
|
447
|
+
['optimisée', set_tv_match_optimized_version]
|
|
448
|
+
])('version %s : le motif ancré sélectionne la bonne ligne', (_, setVersion) => {
|
|
449
|
+
setVersion();
|
|
450
|
+
expect(tv('table_plus', { conf: motifAncre }).val).toBe('p1');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test('version optimisée : l’échappement regex « [+] » ne matche aucune ligne', () => {
|
|
454
|
+
set_tv_match_optimized_version();
|
|
455
|
+
expect(
|
|
456
|
+
tv('table_plus', { conf: 'production volume habitable [+] pièces alimentées contiguës' })
|
|
457
|
+
).toBeNull();
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
434
461
|
// -------------------------------------------------------------------------------------------
|
|
435
462
|
describe('tv / cache', () => {
|
|
436
463
|
// Règle : `tv()` mémoïse chaque couple (table, matcher), borné par une éviction FIFO.
|