@open3cl/engine 1.8.58 → 1.8.60
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_rdim_ecs_individuelle_unique.js +62 -0
- package/11_rdim_ecs_individuelle_unique.spec.js +113 -0
- package/14_generateur_ecs.js +35 -0
- package/14_generateur_ecs.spec.js +93 -0
- package/README.md +21 -21
- package/engine.js +8 -1
- package/engine.spec.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DPE immeuble avec une installation ECS individuelle unique décrite pour un logement type.
|
|
3
|
+
*
|
|
4
|
+
* Certains logiciels (3.0.x, 2.0.x, 1.1.x) exportent l'installation ECS individuelle unique d'un
|
|
5
|
+
* immeuble avec `surface_habitable` = SH / N et `nombre_logement` = N, sans `rdim`. Le calcul de
|
|
6
|
+
* référence (Tribu) a été fait avec Rdim = N : le besoin est ramené à un logement
|
|
7
|
+
* (Becs = Becs_immeuble × Sh × Rdim / SH / Rdim = Becs_immeuble / N), les pertes de stockage sont
|
|
8
|
+
* celles d'un seul ballon (Vs non multiplié), puis la consommation est multipliée par Rdim.
|
|
9
|
+
*
|
|
10
|
+
* Sans rdim, Open3CL impute le besoin de tout l'immeuble à un seul ballon : conso_ecs −25 à −30 %.
|
|
11
|
+
* On reconstitue donc rdim = N, ce qui réutilise la chaîne existante (11_ecs.js : besoin / rdim,
|
|
12
|
+
* conso.js : conso × rdim).
|
|
13
|
+
*
|
|
14
|
+
* LICIEL (nombre_logement = 1, Rdim non exporté) n'est pas concerné.
|
|
15
|
+
*
|
|
16
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - § 11.6, § 11.6.2 (p. 74-75) et § 17.2.2.3
|
|
17
|
+
* @see Tribu : Calcul_batiment.cs l. 583-588 (Ratio = Sh·Rdim/SH), l. 602-604 (Vs non multiplié),
|
|
18
|
+
* l. 1654-1664 et l. 1052 (Cecs / Rdim puis × Rdim)
|
|
19
|
+
* @see https://github.com/Open3CL/engine/issues/288
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Tolérance relative sur Sh_ecs × N ≈ SH */
|
|
23
|
+
export const TOLERANCE_SURFACE_LOGEMENT_TYPE = 0.05;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Rdim implicite de l'installation ECS individuelle unique d'un immeuble, ou null si non concernée.
|
|
27
|
+
*
|
|
28
|
+
* @param th {string} type de bâtiment (maison, appartement, immeuble)
|
|
29
|
+
* @param instal_ecs {Array} installation_ecs
|
|
30
|
+
* @param surfaceHabitableImmeuble {number|string} surface habitable de l'immeuble (SH)
|
|
31
|
+
* @return {number|null}
|
|
32
|
+
*/
|
|
33
|
+
export function rdimImpliciteEcsIndividuelleUnique(th, instal_ecs, surfaceHabitableImmeuble) {
|
|
34
|
+
if (th !== 'immeuble' || !Array.isArray(instal_ecs) || instal_ecs.length !== 1) return null;
|
|
35
|
+
|
|
36
|
+
const de = instal_ecs[0].donnee_entree;
|
|
37
|
+
if (!de || String(de.enum_type_installation_id) !== '1' || de.rdim) return null;
|
|
38
|
+
|
|
39
|
+
const N = Number(de.nombre_logement);
|
|
40
|
+
const SH = Number(surfaceHabitableImmeuble);
|
|
41
|
+
const sh = Number(de.surface_habitable);
|
|
42
|
+
if (!(N > 1) || !(SH > 0) || !(sh > 0)) return null;
|
|
43
|
+
|
|
44
|
+
return Math.abs(sh * N - SH) <= TOLERANCE_SURFACE_LOGEMENT_TYPE * SH ? N : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Reconstitue le rdim de l'installation ECS individuelle unique d'un immeuble décrite pour un
|
|
49
|
+
* logement type. L'installation reçoit `rdim` = nombre_logement et le flag `rdim_reconstitue`.
|
|
50
|
+
*
|
|
51
|
+
* @param th {string} type de bâtiment
|
|
52
|
+
* @param instal_ecs {Array} installation_ecs
|
|
53
|
+
* @param surfaceHabitableImmeuble {number|string} surface habitable de l'immeuble (SH)
|
|
54
|
+
* @return {boolean} true si le rdim a été reconstitué
|
|
55
|
+
*/
|
|
56
|
+
export function reconstituerRdimEcsIndividuelleUnique(th, instal_ecs, surfaceHabitableImmeuble) {
|
|
57
|
+
const rdim = rdimImpliciteEcsIndividuelleUnique(th, instal_ecs, surfaceHabitableImmeuble);
|
|
58
|
+
if (rdim === null) return false;
|
|
59
|
+
instal_ecs[0].donnee_entree.rdim = rdim;
|
|
60
|
+
instal_ecs[0].donnee_entree.rdim_reconstitue = true;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
rdimImpliciteEcsIndividuelleUnique,
|
|
4
|
+
reconstituerRdimEcsIndividuelleUnique,
|
|
5
|
+
TOLERANCE_SURFACE_LOGEMENT_TYPE
|
|
6
|
+
} from './11_rdim_ecs_individuelle_unique.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - § 11.6, § 11.6.2 et § 17.2.2.3
|
|
10
|
+
* @see Tribu : Calcul_batiment.cs l. 583-588, l. 602-604, l. 1654-1664
|
|
11
|
+
* @see https://github.com/Open3CL/engine/issues/288
|
|
12
|
+
*/
|
|
13
|
+
describe("Rdim de l'installation ECS individuelle unique d'un immeuble", () => {
|
|
14
|
+
/** Installation ECS décrite pour un logement type : Sh = SH / N, nombre_logement = N */
|
|
15
|
+
const ecs = (de) => [
|
|
16
|
+
{
|
|
17
|
+
donnee_entree: {
|
|
18
|
+
enum_type_installation_id: '1',
|
|
19
|
+
surface_habitable: 64,
|
|
20
|
+
nombre_logement: 10,
|
|
21
|
+
...de
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
describe('rdimImpliciteEcsIndividuelleUnique', () => {
|
|
27
|
+
test('installation individuelle unique, Sh × N = SH, sans rdim : rdim = nombre_logement', () => {
|
|
28
|
+
// Cas type 2244E3131012H : N = 10
|
|
29
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 640)).toBe(10);
|
|
30
|
+
expect(
|
|
31
|
+
rdimImpliciteEcsIndividuelleUnique(
|
|
32
|
+
'immeuble',
|
|
33
|
+
ecs({ enum_type_installation_id: 1, surface_habitable: '64', nombre_logement: '10' }),
|
|
34
|
+
'640'
|
|
35
|
+
)
|
|
36
|
+
).toBe(10);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('tolérance de 5 % sur Sh × N ≈ SH', () => {
|
|
40
|
+
expect(TOLERANCE_SURFACE_LOGEMENT_TYPE).toBe(0.05);
|
|
41
|
+
// |640 − SH| ≤ 0,05 × SH ⇔ 609,52 ≤ SH ≤ 673,68
|
|
42
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 673)).toBe(10);
|
|
43
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 610)).toBe(10);
|
|
44
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 674)).toBeNull();
|
|
45
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 609)).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('installation décrite sur la surface de l’immeuble (Sh = SH) : non concernée', () => {
|
|
49
|
+
expect(
|
|
50
|
+
rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ surface_habitable: 640 }), 640)
|
|
51
|
+
).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('LICIEL : nombre_logement = 1 → non concerné', () => {
|
|
55
|
+
expect(
|
|
56
|
+
rdimImpliciteEcsIndividuelleUnique(
|
|
57
|
+
'immeuble',
|
|
58
|
+
ecs({ nombre_logement: 1, surface_habitable: 640 }),
|
|
59
|
+
640
|
|
60
|
+
)
|
|
61
|
+
).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('rdim exporté : on conserve la saisie', () => {
|
|
65
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ rdim: 4 }), 640)).toBeNull();
|
|
66
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ rdim: 1 }), 640)).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('installation collective : non concernée', () => {
|
|
70
|
+
expect(
|
|
71
|
+
rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ enum_type_installation_id: '2' }), 640)
|
|
72
|
+
).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('maison ou appartement : non concerné', () => {
|
|
76
|
+
expect(rdimImpliciteEcsIndividuelleUnique('maison', ecs(), 640)).toBeNull();
|
|
77
|
+
expect(rdimImpliciteEcsIndividuelleUnique('appartement', ecs(), 640)).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('plusieurs installations ECS : non concerné (#232)', () => {
|
|
81
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', [...ecs(), ...ecs()], 640)).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('données manquantes : non concerné', () => {
|
|
85
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', undefined, 640)).toBeNull();
|
|
86
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', [], 640)).toBeNull();
|
|
87
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', [{}], 640)).toBeNull();
|
|
88
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), undefined)).toBeNull();
|
|
89
|
+
expect(rdimImpliciteEcsIndividuelleUnique('immeuble', ecs(), 0)).toBeNull();
|
|
90
|
+
expect(
|
|
91
|
+
rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ surface_habitable: undefined }), 640)
|
|
92
|
+
).toBeNull();
|
|
93
|
+
expect(
|
|
94
|
+
rdimImpliciteEcsIndividuelleUnique('immeuble', ecs({ nombre_logement: undefined }), 640)
|
|
95
|
+
).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('reconstituerRdimEcsIndividuelleUnique', () => {
|
|
100
|
+
test('installation concernée : rdim et flag positionnés', () => {
|
|
101
|
+
const inst = ecs();
|
|
102
|
+
expect(reconstituerRdimEcsIndividuelleUnique('immeuble', inst, 640)).toBe(true);
|
|
103
|
+
expect(inst[0].donnee_entree).toMatchObject({ rdim: 10, rdim_reconstitue: true });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('installation non concernée : inchangée', () => {
|
|
107
|
+
const inst = ecs({ nombre_logement: 1 });
|
|
108
|
+
expect(reconstituerRdimEcsIndividuelleUnique('immeuble', inst, 640)).toBe(false);
|
|
109
|
+
expect(inst[0].donnee_entree.rdim).toBeUndefined();
|
|
110
|
+
expect(inst[0].donnee_entree.rdim_reconstitue).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
package/14_generateur_ecs.js
CHANGED
|
@@ -183,6 +183,34 @@ function type_generateur_ecs(di, de, du, usage_generateur) {
|
|
|
183
183
|
/** Rendement de génération d'une chaudière électrique (tv rendement_generation, id 30). */
|
|
184
184
|
export const RG_CHAUDIERE_ELECTRIQUE = 0.97;
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Chauffe-eau gaz / GPL à production instantanée 2001-2015 (types 66 et 113) : la méthode donne
|
|
188
|
+
* Pveil = 100 W, mais le moteur de référence (Tribu, Calcul_generateur.cs, Pveil1 = 0,01 kW pour
|
|
189
|
+
* Pn > 10 kW comme pour Pn ≤ 10 kW) applique 10 W. Les DPE exportent alors pveilleuse = 10 (W) ou
|
|
190
|
+
* 0,01 (kW). Ce cas n'est reproduit qu'en mode bug_for_bug_compat.
|
|
191
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §14.1.1
|
|
192
|
+
* @see https://github.com/Open3CL/engine/issues/289
|
|
193
|
+
*/
|
|
194
|
+
export const PVEIL_CHAUFFE_EAU_GAZ_INSTANTANE_TRIBU = 10;
|
|
195
|
+
const TYPES_CHAUFFE_EAU_GAZ_INSTANTANE_2001_2015 = ['66', '113'];
|
|
196
|
+
const PVEILLEUSES_EXPORTEES_TRIBU = [10, 0.01];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Indique si la puissance de veilleuse du générateur doit être ramenée à la valeur Tribu (10 W) :
|
|
200
|
+
* chauffe-eau gaz / GPL instantané 2001-2015, veilleuse de la méthode (100 W) retenue, et
|
|
201
|
+
* pveilleuse exportée par le DPE égale à 10 W ou 0,01 kW.
|
|
202
|
+
* @param typeGenerateurId {string|number} enum_type_generateur_ecs_id
|
|
203
|
+
* @param di {Donnee_intermediaire}
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
export function isPveilChauffeEauGazInstantaneTribu(typeGenerateurId, di) {
|
|
207
|
+
return (
|
|
208
|
+
TYPES_CHAUFFE_EAU_GAZ_INSTANTANE_2001_2015.includes(String(typeGenerateurId)) &&
|
|
209
|
+
di.pveil === 100 &&
|
|
210
|
+
PVEILLEUSES_EXPORTEES_TRIBU.includes(Number(di.pveilleuse))
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
186
214
|
export function rg_chauffe_eau_gaz(di, besoin_ecs) {
|
|
187
215
|
return (
|
|
188
216
|
1 /
|
|
@@ -405,6 +433,13 @@ export default function calc_gen_ecs(dpe, gen_ecs, ecs_di, ecs_de, GV, ca_id, zc
|
|
|
405
433
|
di.pveil = 0;
|
|
406
434
|
}
|
|
407
435
|
|
|
436
|
+
if (bug_for_bug_compat && isPveilChauffeEauGazInstantaneTribu(type_generateur_id, di)) {
|
|
437
|
+
console.warn(
|
|
438
|
+
`Correction pveil pour le générateur ECS ${de.description} : ${PVEIL_CHAUFFE_EAU_GAZ_INSTANTANE_TRIBU} W au lieu de ${di.pveil} W (moteur de référence)`
|
|
439
|
+
);
|
|
440
|
+
di.pveil = PVEIL_CHAUFFE_EAU_GAZ_INSTANTANE_TRIBU;
|
|
441
|
+
}
|
|
442
|
+
|
|
408
443
|
const type_generateur = enums.type_generateur_ecs[type_generateur_id];
|
|
409
444
|
if (
|
|
410
445
|
type_generateur.includes('chauffe-eau gaz') ||
|
|
@@ -24,6 +24,8 @@ vi.mock('./enums.js', () => ({
|
|
|
24
24
|
pac: 'pac double service',
|
|
25
25
|
autre: 'générateur non implémenté',
|
|
26
26
|
combautre: 'foyer fermé bois à combustion',
|
|
27
|
+
66: 'chauffe-eau gaz à production instantanée 2001-2015',
|
|
28
|
+
113: 'chauffe-eau gpl/propane/butane à production instantanée 2001-2015',
|
|
27
29
|
84: 'chaudière fioul (système collectif par défaut)'
|
|
28
30
|
},
|
|
29
31
|
type_generateur_ch: { chaud_ch: 'chaudière gaz condensation' },
|
|
@@ -73,6 +75,8 @@ const {
|
|
|
73
75
|
rgrs_chaudiere,
|
|
74
76
|
rg_accumulateur_gaz,
|
|
75
77
|
rgrsReseauUrbain,
|
|
78
|
+
isPveilChauffeEauGazInstantaneTribu,
|
|
79
|
+
PVEIL_CHAUFFE_EAU_GAZ_INSTANTANE_TRIBU,
|
|
76
80
|
RG_CHAUDIERE_ELECTRIQUE,
|
|
77
81
|
typeGenerateurPertesStockage
|
|
78
82
|
} = await import('./14_generateur_ecs.js');
|
|
@@ -146,6 +150,39 @@ describe('rgrsReseauUrbain - rendement d’un réseau de chaleur', () => {
|
|
|
146
150
|
});
|
|
147
151
|
});
|
|
148
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Veilleuse des chauffe-eau gaz / GPL instantanés 2001-2015 : 100 W selon la méthode, 10 W chez Tribu.
|
|
155
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §14.1.1
|
|
156
|
+
* @see https://github.com/Open3CL/engine/issues/289
|
|
157
|
+
*/
|
|
158
|
+
describe('isPveilChauffeEauGazInstantaneTribu - veilleuse 10 W du moteur de référence', () => {
|
|
159
|
+
test('valeur Tribu : 10 W', () => {
|
|
160
|
+
expect(PVEIL_CHAUFFE_EAU_GAZ_INSTANTANE_TRIBU).toBe(10);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('types 66 (gaz) et 113 (GPL), pveil 100 W, pveilleuse exportée 10 W ou 0,01 kW : concerné', () => {
|
|
164
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 100, pveilleuse: 10 })).toBe(true);
|
|
165
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 100, pveilleuse: 0.01 })).toBe(true);
|
|
166
|
+
expect(isPveilChauffeEauGazInstantaneTribu(113, { pveil: 100, pveilleuse: '10' })).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('autres chauffe-eau gaz instantanés (1990-2000, après 2015) : non concernés', () => {
|
|
170
|
+
expect(isPveilChauffeEauGazInstantaneTribu('65', { pveil: 100, pveilleuse: 10 })).toBe(false);
|
|
171
|
+
expect(isPveilChauffeEauGazInstantaneTribu('67', { pveil: 100, pveilleuse: 10 })).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('pveilleuse exportée conforme à la méthode (100 W / 0,1 kW) ou absente : non concerné', () => {
|
|
175
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 100, pveilleuse: 100 })).toBe(false);
|
|
176
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 100, pveilleuse: 0.1 })).toBe(false);
|
|
177
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 100 })).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('pveil retenue différente de 100 W (sans veilleuse, ou déjà 10 W) : non concerné', () => {
|
|
181
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 0, pveilleuse: 10 })).toBe(false);
|
|
182
|
+
expect(isPveilChauffeEauGazInstantaneTribu('66', { pveil: 10, pveilleuse: 10 })).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
149
186
|
/**
|
|
150
187
|
* calc_gen_ecs : sélection de la branche de rendement selon le type d'énergie / générateur.
|
|
151
188
|
*/
|
|
@@ -271,6 +308,62 @@ describe('calc_gen_ecs - consommation par générateur', () => {
|
|
|
271
308
|
expect(g.donnee_intermediaire.conso_ecs).toBeCloseTo(123.81056790123455, 9);
|
|
272
309
|
});
|
|
273
310
|
|
|
311
|
+
describe('chauffe-eau gaz instantané 2001-2015 : veilleuse (#289)', () => {
|
|
312
|
+
function chauffeEau(type, pveilleuse) {
|
|
313
|
+
vi.mocked(tvColumnIDs).mockImplementation((table) =>
|
|
314
|
+
table === 'generateur_combustion' ? [type] : []
|
|
315
|
+
);
|
|
316
|
+
vi.mocked(tv_generateur_combustion).mockImplementation((dpe, di) => {
|
|
317
|
+
di.rpn = 0.84;
|
|
318
|
+
di.qp0 = 0;
|
|
319
|
+
di.pveil = 100;
|
|
320
|
+
di.pveilleuse = pveilleuse;
|
|
321
|
+
di.pn = 21000;
|
|
322
|
+
});
|
|
323
|
+
return gen({
|
|
324
|
+
type_energie: 'gaz naturel',
|
|
325
|
+
enum_type_generateur_ecs_id: type,
|
|
326
|
+
enum_methode_saisie_carac_sys_id: '1',
|
|
327
|
+
presence_veilleuse: 1
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
test('bug_for_bug_compat, pveilleuse exportée 10 W : Pveil = 10 W (Tribu)', () => {
|
|
332
|
+
state.bug = true;
|
|
333
|
+
const g = chauffeEau('66', 10);
|
|
334
|
+
calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
|
|
335
|
+
|
|
336
|
+
expect(g.donnee_intermediaire.pveil).toBe(10);
|
|
337
|
+
// valeur de référence de régression : 1 / (1 / 0.84 + 6970 * 10 / (100 * 1000))
|
|
338
|
+
expect(g.donnee_intermediaire.rendement_generation).toBeCloseTo(0.5298080076696016, 9);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('bug_for_bug_compat, GPL (113), pveilleuse exportée 0,01 kW : Pveil = 10 W', () => {
|
|
342
|
+
state.bug = true;
|
|
343
|
+
const g = chauffeEau('113', 0.01);
|
|
344
|
+
calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
|
|
345
|
+
|
|
346
|
+
expect(g.donnee_intermediaire.pveil).toBe(10);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('hors bug_for_bug_compat : Pveil de la méthode (100 W) conservée', () => {
|
|
350
|
+
const g = chauffeEau('66', 10);
|
|
351
|
+
calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
|
|
352
|
+
|
|
353
|
+
expect(g.donnee_intermediaire.pveil).toBe(100);
|
|
354
|
+
// valeur de référence de régression : 1 / (1 / 0.84 + 6970 * 100 / (100 * 1000))
|
|
355
|
+
expect(g.donnee_intermediaire.rendement_generation).toBeCloseTo(0.12254186847172784, 9);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('bug_for_bug_compat, pveilleuse exportée 100 W : Pveil de la méthode conservée', () => {
|
|
359
|
+
state.bug = true;
|
|
360
|
+
const g = chauffeEau('66', 100);
|
|
361
|
+
calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
|
|
362
|
+
|
|
363
|
+
expect(g.donnee_intermediaire.pveil).toBe(100);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
274
367
|
test('installation solaire : la couverture solaire réduit la consommation', () => {
|
|
275
368
|
ecs_de.enum_type_installation_solaire_id = '1';
|
|
276
369
|
ecs_de.fecs_saisi = 0.5;
|
package/README.md
CHANGED
|
@@ -375,29 +375,29 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
375
375
|
|
|
376
376
|
<!-- CORPUS:START -->
|
|
377
377
|
|
|
378
|
-
> **Version `1.8.
|
|
378
|
+
> **Version `1.8.59`** · branche `main` · généré le 2026-10-14
|
|
379
379
|
> Seuil de tolérance **5%**
|
|
380
380
|
|
|
381
381
|
<table>
|
|
382
382
|
<tr>
|
|
383
383
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
384
384
|
<td align="center"><strong>89 418</strong><br/><sub>DPE analysés</sub></td>
|
|
385
|
-
<td align="center"><strong>
|
|
386
|
-
<td align="center"><strong>
|
|
385
|
+
<td align="center"><strong>70 263</strong><br/><sub>DPE conformes</sub></td>
|
|
386
|
+
<td align="center"><strong>78,58 %</strong><br/><sub>réussite globale</sub></td>
|
|
387
387
|
</tr>
|
|
388
388
|
</table>
|
|
389
389
|
|
|
390
390
|
| | Corpus | Réussite | | DPE conformes |
|
|
391
391
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
392
|
-
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **47,
|
|
392
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **47,67 %** | `██████████░░░░░░░░░░` | 4 767 / 9 999 |
|
|
393
393
|
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,90 %** | `███████████████████░` | 9 290 / 10 000 |
|
|
394
394
|
| 🟢 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **91,72 %** | `██████████████████░░` | 9 172 / 10 000 |
|
|
395
395
|
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **90,47 %** | `██████████████████░░` | 9 040 / 9 992 |
|
|
396
396
|
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,90 %** | `██████████████████░░` | 8 889 / 9 999 |
|
|
397
|
-
| 🟢 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **85,
|
|
398
|
-
|
|
|
399
|
-
|
|
|
400
|
-
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **
|
|
397
|
+
| 🟢 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **85,07 %** | `█████████████████░░░` | 8 349 / 9 814 |
|
|
398
|
+
| 🟢 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **85,03 %** | `█████████████████░░░` | 8 501 / 9 998 |
|
|
399
|
+
| 🟡 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **69,07 %** | `██████████████░░░░░░` | 6 642 / 9 616 |
|
|
400
|
+
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **56,13 %** | `███████████░░░░░░░░░` | 5 613 / 10 000 |
|
|
401
401
|
|
|
402
402
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
403
403
|
|
|
@@ -408,24 +408,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
408
408
|
|
|
409
409
|
<table>
|
|
410
410
|
<tr>
|
|
411
|
-
<td align="center"><strong>5,
|
|
412
|
-
<td align="center"><strong>4,
|
|
413
|
-
<td align="center"><strong>0,
|
|
414
|
-
<td align="center"><strong>
|
|
411
|
+
<td align="center"><strong>5,52 ms</strong><br/><sub>moyenne</sub></td>
|
|
412
|
+
<td align="center"><strong>4,86 ms</strong><br/><sub>médiane</sub></td>
|
|
413
|
+
<td align="center"><strong>0,11 ms</strong><br/><sub>min</sub></td>
|
|
414
|
+
<td align="center"><strong>690,4 ms</strong><br/><sub>max</sub></td>
|
|
415
415
|
</tr>
|
|
416
416
|
</table>
|
|
417
417
|
|
|
418
418
|
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
419
419
|
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
420
|
-
| **Généraliste** | 4,
|
|
421
|
-
| **Appartement · chauffage individuel (2025)** |
|
|
422
|
-
| **Appartement · chauffage collectif (2025)** | 4,
|
|
423
|
-
| **Logement individuel (2025)** | 5,
|
|
424
|
-
| **Maison individuelle (2025)** | 6,
|
|
425
|
-
| **Immeuble · chauffage collectif** | 5,
|
|
426
|
-
| **Immeuble · chauffage individuel** |
|
|
427
|
-
| **Immeuble · chauffage mixte** | 5,
|
|
428
|
-
| **Individuel généré depuis l'immeuble (2026)** | 6,
|
|
420
|
+
| **Généraliste** | 4,67 ms | 4,22 ms | 0,22 ms | 39,6 ms | 8,37 ms | 11,6 ms |
|
|
421
|
+
| **Appartement · chauffage individuel (2025)** | 4,17 ms | 3,85 ms | 1,58 ms | 34,2 ms | 7,08 ms | 9,10 ms |
|
|
422
|
+
| **Appartement · chauffage collectif (2025)** | 4,75 ms | 4,46 ms | 1,67 ms | 37,7 ms | 7,67 ms | 10,1 ms |
|
|
423
|
+
| **Logement individuel (2025)** | 5,23 ms | 4,68 ms | 1,63 ms | 41,1 ms | 9,60 ms | 13,1 ms |
|
|
424
|
+
| **Maison individuelle (2025)** | 6,41 ms | 5,76 ms | 2,10 ms | 54,5 ms | 11,6 ms | 14,9 ms |
|
|
425
|
+
| **Immeuble · chauffage collectif** | 5,67 ms | 4,91 ms | 0,20 ms | 113,1 ms | 10,2 ms | 17,2 ms |
|
|
426
|
+
| **Immeuble · chauffage individuel** | 6,68 ms | 5,68 ms | 0,11 ms | 117,5 ms | 13,8 ms | 21,0 ms |
|
|
427
|
+
| **Immeuble · chauffage mixte** | 5,53 ms | 4,50 ms | 0,19 ms | 690,4 ms | 11,5 ms | 18,4 ms |
|
|
428
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,59 ms | 5,66 ms | 1,77 ms | 58,0 ms | 12,4 ms | 21,2 ms |
|
|
429
429
|
|
|
430
430
|
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
431
431
|
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,8 +33,9 @@ import getFicheTechnique from './ficheTechnique.js';
|
|
|
33
33
|
import { ProductionENR, ratioProrataPvLogement } from './16.2_production_enr.js';
|
|
34
34
|
import DpeSanitizerService from './dpe-sanitizer.service.js';
|
|
35
35
|
import { reconstituerRdimDpeGenereImmeuble } from './17.2_rdim_dpe_genere_immeuble.js';
|
|
36
|
+
import { reconstituerRdimEcsIndividuelleUnique } from './11_rdim_ecs_individuelle_unique.js';
|
|
36
37
|
|
|
37
|
-
const LIB_VERSION = '1.8.
|
|
38
|
+
const LIB_VERSION = '1.8.60';
|
|
38
39
|
|
|
39
40
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
40
41
|
|
|
@@ -291,6 +292,12 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
291
292
|
const ilpa = logement.meteo.batiment_materiaux_anciens === 1 ? '1' : '0';
|
|
292
293
|
|
|
293
294
|
const ecs = logement.installation_ecs_collection.installation_ecs || [];
|
|
295
|
+
/**
|
|
296
|
+
* Immeuble avec une installation ECS individuelle unique décrite pour un logement type
|
|
297
|
+
* (Sh_ecs × nombre_logement ≈ SH, sans rdim) : rdim = nombre_logement.
|
|
298
|
+
* @see https://github.com/Open3CL/engine/issues/288
|
|
299
|
+
*/
|
|
300
|
+
reconstituerRdimEcsIndividuelleUnique(th, ecs, cg.surface_habitable_immeuble);
|
|
294
301
|
const Nb_lgt = cg.nombre_appartement || 1;
|
|
295
302
|
|
|
296
303
|
/**
|
package/engine.spec.js
CHANGED
|
@@ -73,6 +73,9 @@ vi.mock('./ficheTechnique.js', () => ({ default: vi.fn() }));
|
|
|
73
73
|
vi.mock('./17.2_rdim_dpe_genere_immeuble.js', () => ({
|
|
74
74
|
reconstituerRdimDpeGenereImmeuble: vi.fn()
|
|
75
75
|
}));
|
|
76
|
+
vi.mock('./11_rdim_ecs_individuelle_unique.js', () => ({
|
|
77
|
+
reconstituerRdimEcsIndividuelleUnique: vi.fn()
|
|
78
|
+
}));
|
|
76
79
|
|
|
77
80
|
// Les instances Inertie / ProductionENR sont créées au chargement du module.
|
|
78
81
|
// On expose des méthodes mockées récupérables dans les tests.
|
|
@@ -116,6 +119,8 @@ const calc_confort_ete = (await import('./2021_04_13_confort_ete.js')).default;
|
|
|
116
119
|
const calc_qualite_isolation = (await import('./2021_04_13_qualite_isolation.js')).default;
|
|
117
120
|
const getFicheTechnique = (await import('./ficheTechnique.js')).default;
|
|
118
121
|
const { reconstituerRdimDpeGenereImmeuble } = await import('./17.2_rdim_dpe_genere_immeuble.js');
|
|
122
|
+
const { reconstituerRdimEcsIndividuelleUnique } =
|
|
123
|
+
await import('./11_rdim_ecs_individuelle_unique.js');
|
|
119
124
|
|
|
120
125
|
/** Objet conso renvoyé par le mock de `calc_conso` (mêmes réf pour les 3 appels). */
|
|
121
126
|
let consoResult;
|
|
@@ -1198,6 +1203,27 @@ describe('calcul_3cl - rdim reconstitué des DPE générés depuis un DPE immeub
|
|
|
1198
1203
|
});
|
|
1199
1204
|
});
|
|
1200
1205
|
|
|
1206
|
+
describe('calcul_3cl - rdim de l’installation ECS individuelle unique d’un immeuble (#288)', () => {
|
|
1207
|
+
test('reconstitution appelée avec le type de bâtiment, les installations ECS et SH, avant le calcul ECS', () => {
|
|
1208
|
+
const dpe = makeDpe();
|
|
1209
|
+
dpe.logement.caracteristique_generale.surface_habitable_immeuble = 640;
|
|
1210
|
+
// la reconstitution doit précéder le calcul de l'ECS (besoin / rdim)
|
|
1211
|
+
vi.mocked(reconstituerRdimEcsIndividuelleUnique).mockImplementationOnce(() => {
|
|
1212
|
+
expect(calc_ecs).not.toHaveBeenCalled();
|
|
1213
|
+
return false;
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
calcul_3cl(dpe, { sanitize: false });
|
|
1217
|
+
|
|
1218
|
+
expect(reconstituerRdimEcsIndividuelleUnique).toHaveBeenCalledWith(
|
|
1219
|
+
'maison',
|
|
1220
|
+
dpe.logement.installation_ecs_collection.installation_ecs,
|
|
1221
|
+
640
|
|
1222
|
+
);
|
|
1223
|
+
expect(calc_ecs).toHaveBeenCalled();
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1201
1227
|
describe('get_conso_coeff_1_7_2027', () => {
|
|
1202
1228
|
test('projette la conso EP 2027 pour une maison', () => {
|
|
1203
1229
|
vi.mocked(classe_bilan_dpe).mockReturnValue('C');
|