@open3cl/engine 1.8.57 → 1.8.59
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/17.2_rdim_dpe_genere_immeuble.js +73 -0
- package/17.2_rdim_dpe_genere_immeuble.spec.js +100 -0
- package/9_chauffage.js +3 -1
- package/9_chauffage.spec.js +9 -0
- package/README.md +55 -48
- package/conso.js +8 -3
- package/conso.spec.js +60 -0
- package/engine.js +28 -6
- package/engine.spec.js +126 -0
- package/package.json +2 -2
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DPE appartement généré à partir d'un DPE immeuble (méthodes 10 à 13, 33-34, 38-40).
|
|
3
|
+
*
|
|
4
|
+
* Certains logiciels (LICIEL / BBS_Slama) exportent une installation individuelle décrite sur la
|
|
5
|
+
* surface de l'immeuble entier (Sh_i ≈ SH immeuble) avec un `rdim` égal à 1, alors que le calcul
|
|
6
|
+
* (Tribu) a été fait avec un Rdim implicite égal au nombre de logements desservis. La consommation
|
|
7
|
+
* sortie ADEME vaut alors : clé × Σ C_i × (na × Sh_i / SH).
|
|
8
|
+
*
|
|
9
|
+
* En mode `bug_for_bug_compat`, on reconstitue ce rdim (`rdim = na × Sh_i / SH`), on calcule
|
|
10
|
+
* l'installation à l'échelle de l'immeuble, puis on applique la clé de répartition exportée :
|
|
11
|
+
* - chauffage individuel, gestion homogène : coef_IFC = 1 → Cch_ap = Clé_ap × Cch (et Caux_ch) ;
|
|
12
|
+
* - ECS : Cecs_ap = Cecs × Becs_ap / Becs (chaîne existante cle_ecs × rdim).
|
|
13
|
+
*
|
|
14
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - § 17.2.2.2.2 (p. 115) et § 17.2.2.3.1 (p. 116)
|
|
15
|
+
* @see Tribu : Calcul_installation.cs l. 62 (Rdim lu en entrée), Calcul_batiment.cs l. 836-958
|
|
16
|
+
* (conso = Cch1 × Rdim) et l. 1560 (Cdimref = Pn × Rdim)
|
|
17
|
+
* @see https://github.com/Open3CL/engine/issues/287
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Ratio na × Sh_i / SH au-delà duquel l'installation est considérée comme « immeuble » */
|
|
21
|
+
export const SEUIL_RDIM_IMPLICITE = 1.5;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Rdim implicite d'une installation individuelle exportée avec rdim = 1, ou null si l'installation
|
|
25
|
+
* n'est pas concernée.
|
|
26
|
+
*
|
|
27
|
+
* @param de {object} donnee_entree de l'installation (chauffage ou ECS)
|
|
28
|
+
* @param surface {number|string} surface desservie par l'installation (Sh_i)
|
|
29
|
+
* @param nombreAppartement {number|string} nombre de logements de l'immeuble (na)
|
|
30
|
+
* @param surfaceHabitableImmeuble {number|string} surface habitable de l'immeuble (SH)
|
|
31
|
+
* @return {number|null}
|
|
32
|
+
*/
|
|
33
|
+
export function rdimImplicite(de, surface, nombreAppartement, surfaceHabitableImmeuble) {
|
|
34
|
+
if (!de || de.enum_type_installation_id !== '1') return null;
|
|
35
|
+
if ((Number(de.rdim) || 1) !== 1) return null;
|
|
36
|
+
|
|
37
|
+
const na = Number(nombreAppartement);
|
|
38
|
+
const SH = Number(surfaceHabitableImmeuble);
|
|
39
|
+
const Shi = Number(surface);
|
|
40
|
+
if (!(na > 1) || !(SH > 0) || !(Shi > 0)) return null;
|
|
41
|
+
|
|
42
|
+
const rdim = (na * Shi) / SH;
|
|
43
|
+
return rdim > SEUIL_RDIM_IMPLICITE ? rdim : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reconstitue le rdim des installations individuelles d'un DPE appartement généré depuis un DPE
|
|
48
|
+
* immeuble. Les installations concernées reçoivent `rdim` reconstitué et le flag `rdim_reconstitue`.
|
|
49
|
+
* Chauffage et ECS sont traités indépendamment.
|
|
50
|
+
*
|
|
51
|
+
* @param cg {object} caracteristique_generale
|
|
52
|
+
* @param instal_ch {Array} installation_chauffage
|
|
53
|
+
* @param instal_ecs {Array} installation_ecs
|
|
54
|
+
* @return {{ch: boolean, ecs: boolean}} installations reconstituées
|
|
55
|
+
*/
|
|
56
|
+
export function reconstituerRdimDpeGenereImmeuble(cg, instal_ch, instal_ecs) {
|
|
57
|
+
const na = cg?.nombre_appartement;
|
|
58
|
+
const SH = cg?.surface_habitable_immeuble;
|
|
59
|
+
|
|
60
|
+
const appliquer = (installations, champSurface) =>
|
|
61
|
+
(installations || []).reduce((found, inst) => {
|
|
62
|
+
const de = inst.donnee_entree;
|
|
63
|
+
const rdim = rdimImplicite(de, de?.[champSurface], na, SH);
|
|
64
|
+
if (rdim === null) return found;
|
|
65
|
+
de.rdim = rdim;
|
|
66
|
+
de.rdim_reconstitue = true;
|
|
67
|
+
return true;
|
|
68
|
+
}, false);
|
|
69
|
+
|
|
70
|
+
const ch = appliquer(instal_ch, 'surface_chauffee');
|
|
71
|
+
const ecs = appliquer(instal_ecs, 'surface_habitable');
|
|
72
|
+
return { ch, ecs };
|
|
73
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
rdimImplicite,
|
|
4
|
+
reconstituerRdimDpeGenereImmeuble,
|
|
5
|
+
SEUIL_RDIM_IMPLICITE
|
|
6
|
+
} from './17.2_rdim_dpe_genere_immeuble.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - § 17.2.2.2.2 et § 17.2.2.3.1
|
|
10
|
+
* @see https://github.com/Open3CL/engine/issues/287
|
|
11
|
+
*/
|
|
12
|
+
describe('Reconstitution du rdim des DPE appartement générés depuis un DPE immeuble', () => {
|
|
13
|
+
describe('rdimImplicite', () => {
|
|
14
|
+
test('installation individuelle décrite sur la surface de l’immeuble : rdim = na × Sh_i / SH', () => {
|
|
15
|
+
// Cas 2694E0022738J : na = 62, Sh_i = SH = 4 094 m² → rdim = 62
|
|
16
|
+
expect(rdimImplicite({ enum_type_installation_id: '1', rdim: 1 }, 4094, 62, 4094)).toBe(62);
|
|
17
|
+
expect(rdimImplicite({ enum_type_installation_id: '1' }, 2047, '62', '4094')).toBe(31);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('installation collective : non concernée', () => {
|
|
21
|
+
expect(rdimImplicite({ enum_type_installation_id: '2', rdim: 1 }, 4094, 62, 4094)).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('rdim exporté différent de 1 : on conserve la saisie', () => {
|
|
25
|
+
expect(rdimImplicite({ enum_type_installation_id: '1', rdim: 4 }, 4094, 62, 4094)).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('ratio na × Sh_i / SH inférieur ou égal au seuil : installation d’un seul logement', () => {
|
|
29
|
+
expect(SEUIL_RDIM_IMPLICITE).toBe(1.5);
|
|
30
|
+
// 10 × 60 / 600 = 1 → logement seul
|
|
31
|
+
expect(rdimImplicite({ enum_type_installation_id: '1' }, 60, 10, 600)).toBeNull();
|
|
32
|
+
// 10 × 90 / 600 = 1,5 → seuil non dépassé
|
|
33
|
+
expect(rdimImplicite({ enum_type_installation_id: '1' }, 90, 10, 600)).toBeNull();
|
|
34
|
+
// 10 × 91 / 600 > 1,5
|
|
35
|
+
expect(rdimImplicite({ enum_type_installation_id: '1' }, 91, 10, 600)).toBeCloseTo(
|
|
36
|
+
1.516666667,
|
|
37
|
+
9
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('données manquantes ou un seul logement : non concerné', () => {
|
|
42
|
+
const de = { enum_type_installation_id: '1' };
|
|
43
|
+
expect(rdimImplicite(undefined, 100, 10, 100)).toBeNull();
|
|
44
|
+
expect(rdimImplicite(de, 100, 1, 100)).toBeNull();
|
|
45
|
+
expect(rdimImplicite(de, 100, undefined, 100)).toBeNull();
|
|
46
|
+
expect(rdimImplicite(de, 100, 10, 0)).toBeNull();
|
|
47
|
+
expect(rdimImplicite(de, undefined, 10, 100)).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('reconstituerRdimDpeGenereImmeuble', () => {
|
|
52
|
+
const cg = { nombre_appartement: 62, surface_habitable_immeuble: 4094 };
|
|
53
|
+
const ch = (surface, type = '1') => ({
|
|
54
|
+
donnee_entree: { enum_type_installation_id: type, rdim: 1, surface_chauffee: surface }
|
|
55
|
+
});
|
|
56
|
+
const ecs = (surface, type = '1') => ({
|
|
57
|
+
donnee_entree: { enum_type_installation_id: type, rdim: 1, surface_habitable: surface }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('chauffage et ECS décrits à l’échelle de l’immeuble : rdim reconstitué sur les deux', () => {
|
|
61
|
+
const instCh = [ch(4094)];
|
|
62
|
+
const instEcs = [ecs(4094)];
|
|
63
|
+
expect(reconstituerRdimDpeGenereImmeuble(cg, instCh, instEcs)).toEqual({
|
|
64
|
+
ch: true,
|
|
65
|
+
ecs: true
|
|
66
|
+
});
|
|
67
|
+
expect(instCh[0].donnee_entree).toMatchObject({ rdim: 62, rdim_reconstitue: true });
|
|
68
|
+
expect(instEcs[0].donnee_entree).toMatchObject({ rdim: 62, rdim_reconstitue: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('ECS seule à l’échelle de l’immeuble : rdim reconstitué sur l’ECS uniquement', () => {
|
|
72
|
+
const instCh = [ch(63.8)];
|
|
73
|
+
const instEcs = [ecs(4094)];
|
|
74
|
+
expect(reconstituerRdimDpeGenereImmeuble(cg, instCh, instEcs)).toEqual({
|
|
75
|
+
ch: false,
|
|
76
|
+
ecs: true
|
|
77
|
+
});
|
|
78
|
+
expect(instCh[0].donnee_entree.rdim_reconstitue).toBeUndefined();
|
|
79
|
+
expect(instEcs[0].donnee_entree).toMatchObject({ rdim: 62, rdim_reconstitue: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('seules les installations concernées sont modifiées', () => {
|
|
83
|
+
const instCh = [ch(4094), ch(4094, '2'), ch(63.8)];
|
|
84
|
+
expect(reconstituerRdimDpeGenereImmeuble(cg, instCh, undefined)).toEqual({
|
|
85
|
+
ch: true,
|
|
86
|
+
ecs: false
|
|
87
|
+
});
|
|
88
|
+
expect(instCh[0].donnee_entree.rdim_reconstitue).toBe(true);
|
|
89
|
+
expect(instCh[1].donnee_entree.rdim_reconstitue).toBeUndefined();
|
|
90
|
+
expect(instCh[2].donnee_entree.rdim_reconstitue).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('collections absentes', () => {
|
|
94
|
+
expect(reconstituerRdimDpeGenereImmeuble(undefined, undefined, undefined)).toEqual({
|
|
95
|
+
ch: false,
|
|
96
|
+
ecs: false
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
package/9_chauffage.js
CHANGED
|
@@ -71,7 +71,9 @@ export default function calc_chauffage(
|
|
|
71
71
|
genChDe.cle_repartition_ch = de.cle_repartition_ch || 1;
|
|
72
72
|
genChDe.enum_type_installation_id = de.enum_type_installation_id;
|
|
73
73
|
genChDe.surface_chauffee = de.surface_chauffee || Sh;
|
|
74
|
-
genChDe.rdim_installation_ch =
|
|
74
|
+
genChDe.rdim_installation_ch =
|
|
75
|
+
isImmeuble(map_id) || de.rdim_reconstitue ? Number(de.rdim) || 1 : 1;
|
|
76
|
+
genChDe.rdim_reconstitue = de.rdim_reconstitue;
|
|
75
77
|
genChDe.nombre_niveau_installation_ch = de.nombre_niveau_installation_ch || 1;
|
|
76
78
|
genChDe.fch = Fch || 0.5;
|
|
77
79
|
|
package/9_chauffage.spec.js
CHANGED
|
@@ -278,6 +278,15 @@ describe('calc_chauffage - rdim de l’installation transmis au générateur', (
|
|
|
278
278
|
appel(ch, { map_id: 2 });
|
|
279
279
|
expect(rdimGen(ch)).toBe(1);
|
|
280
280
|
});
|
|
281
|
+
|
|
282
|
+
test('DPE appartement issu d’un DPE immeuble avec rdim reconstitué (issue #287) : rdim repris', () => {
|
|
283
|
+
const ch = installation([generateur()], [emetteur('1')], { rdim: 62, rdim_reconstitue: true });
|
|
284
|
+
appel(ch, { map_id: 10 });
|
|
285
|
+
expect(rdimGen(ch)).toBe(62);
|
|
286
|
+
expect(
|
|
287
|
+
ch.generateur_chauffage_collection.generateur_chauffage[0].donnee_entree.rdim_reconstitue
|
|
288
|
+
).toBe(true);
|
|
289
|
+
});
|
|
281
290
|
});
|
|
282
291
|
|
|
283
292
|
describe('isImmeuble', () => {
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Implémentation open source du moteur Open3CL de l'ADEME.
|
|
|
23
23
|
·
|
|
24
24
|
<a href="https://github.com/Open3CL/engine/issues/new?labels=enhancement&template=feature-request---.md">Créer une feature</a>
|
|
25
25
|
·
|
|
26
|
-
<a href="https://open3cl.github.io/engine/
|
|
26
|
+
<a href="https://open3cl.github.io/engine/">🏠 Démo 3D en ligne</a>
|
|
27
27
|
</p>
|
|
28
28
|
</div>
|
|
29
29
|
|
|
@@ -33,6 +33,21 @@ Implémentation open source du moteur Open3CL de l'ADEME.
|
|
|
33
33
|
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
|
+
<br />
|
|
37
|
+
|
|
38
|
+
<div align="center">
|
|
39
|
+
|
|
40
|
+
### 🏠 Voir un DPE en 3D
|
|
41
|
+
|
|
42
|
+
**[➡️ Ouvrir la démo 3D en ligne](https://open3cl.github.io/engine/)**
|
|
43
|
+
|
|
44
|
+
Chargez le DPE d'un logement : Open3CL le recalcule dans le navigateur et en reconstruit la maquette 3D.
|
|
45
|
+
Chaque paroi, fenêtre et équipement s'affiche avec ses déperditions — consultables et modifiables en direct.
|
|
46
|
+
|
|
47
|
+
[open3cl_3D_demo_resized.webm](https://github.com/user-attachments/assets/534a7f78-a493-41b7-9f73-128076349d84)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
36
51
|
<details>
|
|
37
52
|
<summary>📚 Sommaire</summary>
|
|
38
53
|
<ol>
|
|
@@ -92,7 +107,7 @@ consommations, émissions de gaz à effet de serre, coûts et étiquettes.
|
|
|
92
107
|
| 🔍 **Vérifiable** | ~90 000 DPE réels rejoués à chaque version, résultats publiés (voir [Résultats corpus](#résultats-corpus)) |
|
|
93
108
|
| 🧩 **Sans dépendance** | Pure JavaScript (ESM), aucun service externe, fonctionne en Node.js comme dans un navigateur |
|
|
94
109
|
| 📦 **Prête à intégrer** | Entrée XML ADEME ou objet JSON, sortie JSON complète |
|
|
95
|
-
| 🆓 **Libre** | Licence
|
|
110
|
+
| 🆓 **Libre** | Licence MIT, développée au grand jour |
|
|
96
111
|
|
|
97
112
|
### Ce que fait le moteur
|
|
98
113
|
|
|
@@ -139,11 +154,11 @@ flowchart LR
|
|
|
139
154
|
|
|
140
155
|
### L'écosystème Open3CL
|
|
141
156
|
|
|
142
|
-
| Ressource
|
|
143
|
-
|
|
|
144
|
-
| [
|
|
145
|
-
| [
|
|
146
|
-
| [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine)
|
|
157
|
+
| Ressource | Description |
|
|
158
|
+
| :--------------------------------------------------------------- | :------------------------------------------------------------------------- |
|
|
159
|
+
| [Démo 3D](https://open3cl.github.io/engine/) | Visualisation 3D des déperditions d'un DPE, recalculées dans le navigateur |
|
|
160
|
+
| [open3cl.fr](https://open3cl.fr) | Le site du projet |
|
|
161
|
+
| [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine) | Le paquet npm |
|
|
147
162
|
|
|
148
163
|
<p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
|
|
149
164
|
|
|
@@ -318,24 +333,14 @@ d'environnement, quotas de l'API ADEME, structure des rapports.
|
|
|
318
333
|
### Le rapport interactif
|
|
319
334
|
|
|
320
335
|
Chaque exécution produit un tableau de bord HTML : jauge de réussite, ratio par contrôle, liste des DPE au-dessus du
|
|
321
|
-
seuil avec le **détail des propriétés en écart**, et comparaison entre deux branches.
|
|
336
|
+
seuil avec le **détail des propriétés en écart**, et comparaison entre deux branches. Il est écrit dans
|
|
337
|
+
`dist/reports/corpus` et se consulte en local :
|
|
322
338
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
</a>
|
|
327
|
-
<br/>
|
|
328
|
-
<sub>👉 <a href="https://open3cl.github.io/engine/reports/corpus"><strong>Ouvrir le rapport interactif</strong></a></sub>
|
|
329
|
-
</div>
|
|
339
|
+
```sh
|
|
340
|
+
npm run reports:preview # sert dist/reports/corpus et ouvre le navigateur
|
|
341
|
+
```
|
|
330
342
|
|
|
331
|
-
|
|
332
|
-
> GitHub neutralise les scripts et les `iframe` dans les fichiers markdown : le rapport ne peut donc pas être
|
|
333
|
-
> **intégré**
|
|
334
|
-
> tel quel dans ce README. Il est affiché ici sous forme d'aperçu cliquable, et reste consultable en ligne ou en local :
|
|
335
|
-
>
|
|
336
|
-
> ```sh
|
|
337
|
-
> npm run reports:preview # sert dist/reports/corpus et ouvre le navigateur
|
|
338
|
-
> ```
|
|
343
|
+
Les chiffres de la dernière exécution sont repris ci-dessous, dans [Résultats corpus](#résultats-corpus).
|
|
339
344
|
|
|
340
345
|
### Lancer un corpus
|
|
341
346
|
|
|
@@ -370,21 +375,21 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
370
375
|
|
|
371
376
|
<!-- CORPUS:START -->
|
|
372
377
|
|
|
373
|
-
> **Version `1.8.
|
|
378
|
+
> **Version `1.8.58`** · branche `main` · généré le 2026-10-14
|
|
374
379
|
> Seuil de tolérance **5%**
|
|
375
380
|
|
|
376
381
|
<table>
|
|
377
382
|
<tr>
|
|
378
383
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
379
384
|
<td align="center"><strong>89 418</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
-
<td align="center"><strong>
|
|
381
|
-
<td align="center"><strong>
|
|
385
|
+
<td align="center"><strong>69 329</strong><br/><sub>DPE conformes</sub></td>
|
|
386
|
+
<td align="center"><strong>77,53 %</strong><br/><sub>réussite globale</sub></td>
|
|
382
387
|
</tr>
|
|
383
388
|
</table>
|
|
384
389
|
|
|
385
390
|
| | Corpus | Réussite | | DPE conformes |
|
|
386
391
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
387
|
-
| 🔴 | **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 |
|
|
388
393
|
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,90 %** | `███████████████████░` | 9 290 / 10 000 |
|
|
389
394
|
| 🟢 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **91,72 %** | `██████████████████░░` | 9 172 / 10 000 |
|
|
390
395
|
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **90,47 %** | `██████████████████░░` | 9 040 / 9 992 |
|
|
@@ -392,7 +397,7 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
392
397
|
| 🟢 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **85,04 %** | `█████████████████░░░` | 8 346 / 9 814 |
|
|
393
398
|
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,58 %** | `█████████████████░░░` | 8 456 / 9 998 |
|
|
394
399
|
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **59,86 %** | `████████████░░░░░░░░` | 5 756 / 9 616 |
|
|
395
|
-
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **
|
|
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 |
|
|
396
401
|
|
|
397
402
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
398
403
|
|
|
@@ -403,24 +408,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
403
408
|
|
|
404
409
|
<table>
|
|
405
410
|
<tr>
|
|
406
|
-
<td align="center"><strong>5,
|
|
407
|
-
<td align="center"><strong>4,
|
|
408
|
-
<td align="center"><strong>0,
|
|
409
|
-
<td align="center"><strong>
|
|
411
|
+
<td align="center"><strong>5,46 ms</strong><br/><sub>moyenne</sub></td>
|
|
412
|
+
<td align="center"><strong>4,80 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>655,4 ms</strong><br/><sub>max</sub></td>
|
|
410
415
|
</tr>
|
|
411
416
|
</table>
|
|
412
417
|
|
|
413
418
|
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
414
419
|
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
415
|
-
| **Généraliste** |
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** |
|
|
417
|
-
| **Appartement · chauffage collectif (2025)** | 4,
|
|
418
|
-
| **Logement individuel (2025)** |
|
|
419
|
-
| **Maison individuelle (2025)** |
|
|
420
|
-
| **Immeuble · chauffage collectif** |
|
|
421
|
-
| **Immeuble · chauffage individuel** |
|
|
422
|
-
| **Immeuble · chauffage mixte** |
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** | 6,
|
|
420
|
+
| **Généraliste** | 4,56 ms | 4,11 ms | 0,22 ms | 28,4 ms | 8,39 ms | 11,5 ms |
|
|
421
|
+
| **Appartement · chauffage individuel (2025)** | 4,13 ms | 3,83 ms | 1,53 ms | 33,1 ms | 6,90 ms | 8,83 ms |
|
|
422
|
+
| **Appartement · chauffage collectif (2025)** | 4,69 ms | 4,36 ms | 1,65 ms | 38,2 ms | 7,69 ms | 10,3 ms |
|
|
423
|
+
| **Logement individuel (2025)** | 5,25 ms | 4,70 ms | 1,64 ms | 43,6 ms | 9,63 ms | 12,9 ms |
|
|
424
|
+
| **Maison individuelle (2025)** | 6,17 ms | 5,56 ms | 1,99 ms | 53,3 ms | 11,0 ms | 14,5 ms |
|
|
425
|
+
| **Immeuble · chauffage collectif** | 5,61 ms | 4,85 ms | 0,21 ms | 104,4 ms | 10,3 ms | 16,3 ms |
|
|
426
|
+
| **Immeuble · chauffage individuel** | 6,70 ms | 5,71 ms | 0,11 ms | 164,2 ms | 13,6 ms | 21,3 ms |
|
|
427
|
+
| **Immeuble · chauffage mixte** | 5,70 ms | 4,65 ms | 0,20 ms | 655,4 ms | 12,0 ms | 19,4 ms |
|
|
428
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,33 ms | 5,45 ms | 1,65 ms | 53,8 ms | 11,9 ms | 19,5 ms |
|
|
424
429
|
|
|
425
430
|
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
426
431
|
dizaines de fois la médiane : c’est la médiane qui décrit le cas courant, et p95/p99 le cas
|
|
@@ -441,6 +446,7 @@ flowchart TD
|
|
|
441
446
|
subgraph fait ["✅ Fait"]
|
|
442
447
|
A1["Site Open3CL"]
|
|
443
448
|
A2["Rapports de corpus interactifs"]
|
|
449
|
+
A3["Démo 3D en ligne"]
|
|
444
450
|
end
|
|
445
451
|
subgraph cours ["🚧 En cours"]
|
|
446
452
|
B1["Refonte technique"]
|
|
@@ -450,13 +456,14 @@ flowchart TD
|
|
|
450
456
|
fait --> cours
|
|
451
457
|
```
|
|
452
458
|
|
|
453
|
-
| Étape | Statut | Détail
|
|
454
|
-
| :------------------ | :---------- |
|
|
455
|
-
| Site Open3CL | ✅ Terminé | [open3cl.fr](https://open3cl.fr)
|
|
456
|
-
|
|
|
457
|
-
|
|
|
458
|
-
|
|
|
459
|
-
|
|
|
459
|
+
| Étape | Statut | Détail |
|
|
460
|
+
| :------------------ | :---------- | :----------------------------------------------------------------------------------------------- |
|
|
461
|
+
| Site Open3CL | ✅ Terminé | [open3cl.fr](https://open3cl.fr) |
|
|
462
|
+
| Démo 3D en ligne | ✅ Terminé | [Visualisation 3D des déperditions](https://open3cl.github.io/engine/), publiée sur GitHub Pages |
|
|
463
|
+
| Rapports de tests | ✅ Terminé | Tableau de bord interactif généré à chaque exécution |
|
|
464
|
+
| Refonte technique | 🚧 En cours | Découpage par article de la méthode, couverture de tests à 100 % |
|
|
465
|
+
| DPE à l'immeuble | 🚧 En cours | Fiabilisation des appartements générés depuis un DPE immeuble |
|
|
466
|
+
| Certification ADEME | 🚧 En cours | Objectif de long terme |
|
|
460
467
|
|
|
461
468
|
Le détail complet des bugs et fonctionnalités en cours est dans les
|
|
462
469
|
[issues](https://github.com/Open3CL/engine/issues).
|
package/conso.js
CHANGED
|
@@ -962,9 +962,11 @@ function getChauffageConso(gen_ch, field, coef, prorataChauffage, prefix, facteu
|
|
|
962
962
|
const conso =
|
|
963
963
|
gen_ch.donnee_intermediaire[field] * (typeEnergie === 'électricité ch' ? facteurAc : 1);
|
|
964
964
|
|
|
965
|
-
// La clé de répartition n'est utilisée que dans le cadre des chauffages collectifs
|
|
965
|
+
// La clé de répartition n'est utilisée que dans le cadre des chauffages collectifs, ou d'un
|
|
966
|
+
// chauffage individuel calculé à l'échelle de l'immeuble (rdim reconstitué, § 17.2.2.2.2)
|
|
966
967
|
const repartition =
|
|
967
|
-
gen_ch.donnee_entree.enum_type_installation_id !== '1'
|
|
968
|
+
gen_ch.donnee_entree.enum_type_installation_id !== '1' ||
|
|
969
|
+
gen_ch.donnee_entree.rdim_reconstitue
|
|
968
970
|
? gen_ch.donnee_entree.cle_repartition_ch || prorataChauffage
|
|
969
971
|
: prorataChauffage;
|
|
970
972
|
|
|
@@ -1014,7 +1016,10 @@ function calc_conso_pond(
|
|
|
1014
1016
|
ret.auxiliaire_generation_ch = gen_ch.reduce((acc, gen_ch) => {
|
|
1015
1017
|
const conso =
|
|
1016
1018
|
(gen_ch.donnee_intermediaire.conso_auxiliaire_generation_ch || 0) *
|
|
1017
|
-
fAc('auxiliaire_generation_ch')
|
|
1019
|
+
fAc('auxiliaire_generation_ch') *
|
|
1020
|
+
(gen_ch.donnee_entree.rdim_reconstitue
|
|
1021
|
+
? gen_ch.donnee_entree.cle_repartition_ch || prorataChauffage
|
|
1022
|
+
: 1);
|
|
1018
1023
|
return acc + getConso(coef, 'électricité auxiliaire', conso);
|
|
1019
1024
|
}, 0);
|
|
1020
1025
|
|
package/conso.spec.js
CHANGED
|
@@ -393,6 +393,66 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
393
393
|
expect(res.ef_conso.conso_auxiliaire_distribution_ch).toBe(200);
|
|
394
394
|
});
|
|
395
395
|
|
|
396
|
+
test('chauffage individuel avec rdim reconstitué (issue #287) : clé appliquée à conso et aux. génération, pas à la distribution', () => {
|
|
397
|
+
// § 17.2.2.2.2 : chauffage individuel, coef_IFC = 1 → Cch_ap = Clé_ap × Cch_immeuble
|
|
398
|
+
const ch = [
|
|
399
|
+
installCh({ cle_repartition_ch: 0.25, enum_methode_calcul_conso_id: '3' }, [
|
|
400
|
+
genCh(
|
|
401
|
+
'1',
|
|
402
|
+
{
|
|
403
|
+
conso_ch: 1000,
|
|
404
|
+
conso_ch_depensier: 1200,
|
|
405
|
+
conso_auxiliaire_generation_ch: 40,
|
|
406
|
+
conso_auxiliaire_distribution_ch: 80
|
|
407
|
+
},
|
|
408
|
+
{ rdim_reconstitue: true }
|
|
409
|
+
)
|
|
410
|
+
])
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
414
|
+
|
|
415
|
+
expect(res.ef_conso.conso_ch).toBe(250);
|
|
416
|
+
expect(res.ef_conso.conso_auxiliaire_generation_ch).toBe(10);
|
|
417
|
+
// Distribution non proratisée, comme dans les sorties ADEME
|
|
418
|
+
expect(res.ef_conso.conso_auxiliaire_distribution_ch).toBe(80);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('chauffage individuel sans rdim reconstitué : clé ignorée (prorata chauffage)', () => {
|
|
422
|
+
const ch = [
|
|
423
|
+
installCh({ cle_repartition_ch: 0.25, enum_methode_calcul_conso_id: '3' }, [
|
|
424
|
+
genCh('1', {
|
|
425
|
+
conso_ch: 1000,
|
|
426
|
+
conso_ch_depensier: 1200,
|
|
427
|
+
conso_auxiliaire_generation_ch: 40
|
|
428
|
+
})
|
|
429
|
+
])
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
433
|
+
|
|
434
|
+
expect(res.ef_conso.conso_ch).toBe(1000);
|
|
435
|
+
expect(res.ef_conso.conso_auxiliaire_generation_ch).toBe(40);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('chauffage individuel avec rdim reconstitué et clé nulle : repli sur le prorata chauffage, comme la conso', () => {
|
|
439
|
+
// clé installation 0 et prorata 0 → clé générateur 0 → repli sur prorataChauffage (0)
|
|
440
|
+
const ch = [
|
|
441
|
+
installCh({ cle_repartition_ch: 0, enum_methode_calcul_conso_id: '3' }, [
|
|
442
|
+
genCh(
|
|
443
|
+
'1',
|
|
444
|
+
{ conso_ch: 1000, conso_ch_depensier: 1200, conso_auxiliaire_generation_ch: 40 },
|
|
445
|
+
{ rdim_reconstitue: true }
|
|
446
|
+
)
|
|
447
|
+
])
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 0, DATE_DPE, coef_ep);
|
|
451
|
+
|
|
452
|
+
expect(res.ef_conso.conso_ch).toBe(0);
|
|
453
|
+
expect(res.ef_conso.conso_auxiliaire_generation_ch).toBe(0);
|
|
454
|
+
});
|
|
455
|
+
|
|
396
456
|
test('installation avec clé absente (non collective) : répartition = prorata chauffage', () => {
|
|
397
457
|
const ch = [
|
|
398
458
|
installCh(
|
package/engine.js
CHANGED
|
@@ -32,8 +32,10 @@ import { Inertie } from './7_inertie.js';
|
|
|
32
32
|
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
|
+
import { reconstituerRdimDpeGenereImmeuble } from './17.2_rdim_dpe_genere_immeuble.js';
|
|
36
|
+
import { reconstituerRdimEcsIndividuelleUnique } from './11_rdim_ecs_individuelle_unique.js';
|
|
35
37
|
|
|
36
|
-
const LIB_VERSION = '1.8.
|
|
38
|
+
const LIB_VERSION = '1.8.59';
|
|
37
39
|
|
|
38
40
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
41
|
|
|
@@ -290,7 +292,27 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
290
292
|
const ilpa = logement.meteo.batiment_materiaux_anciens === 1 ? '1' : '0';
|
|
291
293
|
|
|
292
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);
|
|
293
301
|
const Nb_lgt = cg.nombre_appartement || 1;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* DPE appartement généré depuis un DPE immeuble : installation individuelle décrite sur la surface
|
|
305
|
+
* de l'immeuble avec un rdim exporté à 1 → reconstitution du rdim implicite, calcul à l'échelle de
|
|
306
|
+
* l'immeuble puis application de la clé de répartition (§ 17.2.2.2.2).
|
|
307
|
+
* @see https://github.com/Open3CL/engine/issues/287
|
|
308
|
+
*/
|
|
309
|
+
const rdimReconstitue =
|
|
310
|
+
bug_for_bug_compat && dpeGenereImmeuble
|
|
311
|
+
? reconstituerRdimDpeGenereImmeuble(cg, instal_ch, ecs)
|
|
312
|
+
: { ch: false, ecs: false };
|
|
313
|
+
// Type de bâtiment pour le besoin / l'ECS (thCalc) et pour le taux de charge (thCh)
|
|
314
|
+
const thCalc = rdimReconstitue.ecs ? 'immeuble' : th;
|
|
315
|
+
const thCh = rdimReconstitue.ch ? 'immeuble' : th;
|
|
294
316
|
const hsp = cg.hsp;
|
|
295
317
|
const clim = logement.climatisation_collection.climatisation || [];
|
|
296
318
|
let apport_et_besoin = calc_apport_et_besoin(
|
|
@@ -339,9 +361,9 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
339
361
|
if (ecs.length > 1) {
|
|
340
362
|
// Immeuble avec différents systèmes individuels
|
|
341
363
|
isImmeubleSystemEcsIndividuels =
|
|
342
|
-
|
|
364
|
+
thCalc === 'immeuble' && ecs.every((e) => e.donnee_entree.enum_type_installation_id === '1');
|
|
343
365
|
|
|
344
|
-
if (
|
|
366
|
+
if (thCalc === 'immeuble' && !isImmeubleSystemEcsIndividuels) {
|
|
345
367
|
isImmeubleMultiEcs =
|
|
346
368
|
Number(cg.surface_habitable_immeuble) > 0 &&
|
|
347
369
|
ecs.every((e) => Number(e.donnee_entree.surface_habitable) > 0);
|
|
@@ -454,7 +476,7 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
454
476
|
GV,
|
|
455
477
|
ca_id,
|
|
456
478
|
zc_id,
|
|
457
|
-
|
|
479
|
+
thCalc,
|
|
458
480
|
virtualisationECS,
|
|
459
481
|
dpe.logement.caracteristique_generale.surface_habitable_immeuble,
|
|
460
482
|
dpe.logement.caracteristique_generale.nombre_appartement,
|
|
@@ -521,7 +543,7 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
521
543
|
instal_ch,
|
|
522
544
|
bv_list,
|
|
523
545
|
ets,
|
|
524
|
-
|
|
546
|
+
thCalc,
|
|
525
547
|
dpe.logement.caracteristique_generale.nombre_appartement
|
|
526
548
|
);
|
|
527
549
|
apport_et_besoin = { ...apport_et_besoin, ...besoin_ch };
|
|
@@ -535,7 +557,7 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
535
557
|
* 13.2.1.2 Présence d’un ou plusieurs générateurs à combustion indépendants
|
|
536
558
|
* Calcul des taux de charge pour chacun des générateurs de chauffage
|
|
537
559
|
*/
|
|
538
|
-
tauxChargeForGenerator(instal_ch, GV, ca_id, zc_id,
|
|
560
|
+
tauxChargeForGenerator(instal_ch, GV, ca_id, zc_id, thCh);
|
|
539
561
|
|
|
540
562
|
instal_ch.forEach((ch) => {
|
|
541
563
|
calc_chauffage(
|
package/engine.spec.js
CHANGED
|
@@ -70,6 +70,12 @@ vi.mock('./15_conso_aux.js', () => ({ conso_aux_distribution_ecs: vi.fn() }));
|
|
|
70
70
|
vi.mock('./2021_04_13_confort_ete.js', () => ({ default: vi.fn() }));
|
|
71
71
|
vi.mock('./2021_04_13_qualite_isolation.js', () => ({ default: vi.fn() }));
|
|
72
72
|
vi.mock('./ficheTechnique.js', () => ({ default: vi.fn() }));
|
|
73
|
+
vi.mock('./17.2_rdim_dpe_genere_immeuble.js', () => ({
|
|
74
|
+
reconstituerRdimDpeGenereImmeuble: vi.fn()
|
|
75
|
+
}));
|
|
76
|
+
vi.mock('./11_rdim_ecs_individuelle_unique.js', () => ({
|
|
77
|
+
reconstituerRdimEcsIndividuelleUnique: vi.fn()
|
|
78
|
+
}));
|
|
73
79
|
|
|
74
80
|
// Les instances Inertie / ProductionENR sont créées au chargement du module.
|
|
75
81
|
// On expose des méthodes mockées récupérables dans les tests.
|
|
@@ -112,6 +118,9 @@ const { conso_aux_distribution_ecs } = await import('./15_conso_aux.js');
|
|
|
112
118
|
const calc_confort_ete = (await import('./2021_04_13_confort_ete.js')).default;
|
|
113
119
|
const calc_qualite_isolation = (await import('./2021_04_13_qualite_isolation.js')).default;
|
|
114
120
|
const getFicheTechnique = (await import('./ficheTechnique.js')).default;
|
|
121
|
+
const { reconstituerRdimDpeGenereImmeuble } = await import('./17.2_rdim_dpe_genere_immeuble.js');
|
|
122
|
+
const { reconstituerRdimEcsIndividuelleUnique } =
|
|
123
|
+
await import('./11_rdim_ecs_individuelle_unique.js');
|
|
115
124
|
|
|
116
125
|
/** Objet conso renvoyé par le mock de `calc_conso` (mêmes réf pour les 3 appels). */
|
|
117
126
|
let consoResult;
|
|
@@ -1098,6 +1107,123 @@ describe('calcul_3cl - réalignements ECS (bug_for_bug_compat)', () => {
|
|
|
1098
1107
|
});
|
|
1099
1108
|
});
|
|
1100
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* DPE appartement généré depuis un DPE immeuble : reconstitution du rdim (issue #287).
|
|
1112
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - § 17.2.2.2.2
|
|
1113
|
+
*/
|
|
1114
|
+
describe('calcul_3cl - rdim reconstitué des DPE générés depuis un DPE immeuble (#287)', () => {
|
|
1115
|
+
const METHODE_GENEREE = '10';
|
|
1116
|
+
|
|
1117
|
+
function makeDpeGenere() {
|
|
1118
|
+
enums.methode_application_dpe_log = {
|
|
1119
|
+
1: 'dpe maison individuelle',
|
|
1120
|
+
[METHODE_GENEREE]:
|
|
1121
|
+
'dpe appartement généré à partir des données dpe immeuble chauffage individuel ecs individuel'
|
|
1122
|
+
};
|
|
1123
|
+
const dpe = makeDpe();
|
|
1124
|
+
dpe.logement.caracteristique_generale.enum_methode_application_dpe_log_id = METHODE_GENEREE;
|
|
1125
|
+
return dpe;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/** Type de bâtiment transmis à calc_ecs, calc_besoin_ch et au taux de charge. */
|
|
1129
|
+
const thTransmis = () => ({
|
|
1130
|
+
ecs: calc_ecs.mock.calls[0][7],
|
|
1131
|
+
besoinCh: calc_besoin_ch.mock.calls[0][11],
|
|
1132
|
+
tauxCharge: tauxChargeForGenerator.mock.calls[0][4]
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
test('bug_for_bug_compat : reconstitution appelée avec les caractéristiques et les installations', () => {
|
|
1136
|
+
bugForBugCompat = true;
|
|
1137
|
+
vi.mocked(reconstituerRdimDpeGenereImmeuble).mockReturnValue({ ch: false, ecs: false });
|
|
1138
|
+
const dpe = makeDpeGenere();
|
|
1139
|
+
|
|
1140
|
+
calcul_3cl(dpe, { sanitize: false });
|
|
1141
|
+
|
|
1142
|
+
expect(reconstituerRdimDpeGenereImmeuble).toHaveBeenCalledWith(
|
|
1143
|
+
dpe.logement.caracteristique_generale,
|
|
1144
|
+
dpe.logement.installation_chauffage_collection.installation_chauffage,
|
|
1145
|
+
dpe.logement.installation_ecs_collection.installation_ecs
|
|
1146
|
+
);
|
|
1147
|
+
expect(thTransmis()).toEqual({
|
|
1148
|
+
ecs: 'appartement',
|
|
1149
|
+
besoinCh: 'appartement',
|
|
1150
|
+
tauxCharge: 'appartement'
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
test('chauffage et ECS reconstitués : calcul à l’échelle de l’immeuble partout', () => {
|
|
1155
|
+
bugForBugCompat = true;
|
|
1156
|
+
vi.mocked(reconstituerRdimDpeGenereImmeuble).mockReturnValue({ ch: true, ecs: true });
|
|
1157
|
+
|
|
1158
|
+
calcul_3cl(makeDpeGenere(), { sanitize: false });
|
|
1159
|
+
|
|
1160
|
+
expect(thTransmis()).toEqual({ ecs: 'immeuble', besoinCh: 'immeuble', tauxCharge: 'immeuble' });
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
test('chauffage seul reconstitué : seul le taux de charge passe à l’échelle de l’immeuble', () => {
|
|
1164
|
+
bugForBugCompat = true;
|
|
1165
|
+
vi.mocked(reconstituerRdimDpeGenereImmeuble).mockReturnValue({ ch: true, ecs: false });
|
|
1166
|
+
|
|
1167
|
+
calcul_3cl(makeDpeGenere(), { sanitize: false });
|
|
1168
|
+
|
|
1169
|
+
expect(thTransmis()).toEqual({
|
|
1170
|
+
ecs: 'appartement',
|
|
1171
|
+
besoinCh: 'appartement',
|
|
1172
|
+
tauxCharge: 'immeuble'
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
test('ECS seule reconstituée : besoin et ECS à l’échelle de l’immeuble, pas le taux de charge', () => {
|
|
1177
|
+
bugForBugCompat = true;
|
|
1178
|
+
vi.mocked(reconstituerRdimDpeGenereImmeuble).mockReturnValue({ ch: false, ecs: true });
|
|
1179
|
+
|
|
1180
|
+
calcul_3cl(makeDpeGenere(), { sanitize: false });
|
|
1181
|
+
|
|
1182
|
+
expect(thTransmis()).toEqual({
|
|
1183
|
+
ecs: 'immeuble',
|
|
1184
|
+
besoinCh: 'immeuble',
|
|
1185
|
+
tauxCharge: 'appartement'
|
|
1186
|
+
});
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test('sans bug_for_bug_compat : aucune reconstitution', () => {
|
|
1190
|
+
calcul_3cl(makeDpeGenere(), { sanitize: false });
|
|
1191
|
+
|
|
1192
|
+
expect(reconstituerRdimDpeGenereImmeuble).not.toHaveBeenCalled();
|
|
1193
|
+
expect(thTransmis().tauxCharge).toBe('appartement');
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
test('DPE non généré depuis un immeuble : aucune reconstitution, même en bug_for_bug_compat', () => {
|
|
1197
|
+
bugForBugCompat = true;
|
|
1198
|
+
|
|
1199
|
+
calcul_3cl(makeDpe(), { sanitize: false });
|
|
1200
|
+
|
|
1201
|
+
expect(reconstituerRdimDpeGenereImmeuble).not.toHaveBeenCalled();
|
|
1202
|
+
expect(thTransmis().tauxCharge).toBe('maison');
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
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
|
+
|
|
1101
1227
|
describe('get_conso_coeff_1_7_2027', () => {
|
|
1102
1228
|
test('projette la conso EP 2027 pour une maison', () => {
|
|
1103
1229
|
vi.mocked(classe_bilan_dpe).mockReturnValue('C');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open3cl/engine",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.59",
|
|
4
4
|
"description": "Open Source 3CL-DPE engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
|
85
85
|
},
|
|
86
86
|
"author": "Open3Cl",
|
|
87
|
-
"license": "
|
|
87
|
+
"license": "MIT",
|
|
88
88
|
"engines": {
|
|
89
89
|
"node": ">=24.14.1"
|
|
90
90
|
},
|