@open3cl/engine 1.8.8 → 1.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/13.2_generateur_combustion.js +116 -31
- package/13.2_generateur_combustion.spec.js +286 -2
- package/13.2_generateur_combustion_pn.js +248 -0
- package/13.2_generateur_combustion_pn.spec.js +476 -0
- package/9_besoin_ch.js +26 -1
- package/9_besoin_ch.spec.js +151 -2
- package/9_generateur_ch.js +46 -0
- package/9_generateur_ch.spec.js +67 -0
- package/README.md +24 -24
- package/conso.js +178 -4
- package/conso.spec.js +187 -1
- package/engine.js +7 -4
- package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.js +54 -8
- package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +185 -0
- package/package.json +1 -1
- package/utils.js +5 -0
- package/utils.spec.js +53 -0
package/9_generateur_ch.js
CHANGED
|
@@ -44,6 +44,52 @@ export function calc_Qrec_gen_j(gen_ch, nrefj, Bch_hp_j) {
|
|
|
44
44
|
return 0.48 * Cper * di.qp0 * Dperj || 0;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 9.1.1 - Pertes récupérées de génération pour le chauffage sur le mois j (Wh)
|
|
49
|
+
* pour un générateur de la collection ECS assurant l'ECS uniquement.
|
|
50
|
+
*
|
|
51
|
+
* Pour les générateurs assurant l'ECS uniquement :
|
|
52
|
+
* Dperj = Nrefj * 1790 / 8760
|
|
53
|
+
* Qgen_rec_j = 0,48 * Cper * Qp0 * Dperj
|
|
54
|
+
*
|
|
55
|
+
* Seuls les générateurs avec des pertes à l'arrêt (Qp0 > 0), situés en volume chauffé et dont
|
|
56
|
+
* l'usage est 'ecs' sont concernés. Les générateurs 'chauffage + ecs' sont déjà pris en compte via
|
|
57
|
+
* la collection des générateurs de chauffage (cf. issue #153).
|
|
58
|
+
*
|
|
59
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §9.1.1
|
|
60
|
+
*
|
|
61
|
+
* @param gen_ecs {GenerateurEcsItem}
|
|
62
|
+
* @param nrefj {number}
|
|
63
|
+
* @returns {number}
|
|
64
|
+
*/
|
|
65
|
+
export function calc_Qrec_gen_ecs_j(gen_ecs, nrefj) {
|
|
66
|
+
const de = gen_ecs.donnee_entree;
|
|
67
|
+
const di = gen_ecs.donnee_intermediaire || {};
|
|
68
|
+
|
|
69
|
+
if (!isGenerateurEcsSeulRecuperable(gen_ecs)) return 0;
|
|
70
|
+
|
|
71
|
+
const Cper = Number(de.presence_ventouse) === 1 ? 0.75 : 0.5;
|
|
72
|
+
return 0.48 * Cper * di.qp0 * pertes_gen_ecs(nrefj);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Un générateur ECS est concerné par la récupération des pertes de génération pour le chauffage
|
|
77
|
+
* s'il a des pertes à l'arrêt (Qp0 > 0), s'il est en volume chauffé et si son usage est 'ecs' seul
|
|
78
|
+
* (enum_usage_generateur_id = 2).
|
|
79
|
+
*
|
|
80
|
+
* @param gen_ecs {GenerateurEcsItem}
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
export function isGenerateurEcsSeulRecuperable(gen_ecs) {
|
|
84
|
+
const de = gen_ecs.donnee_entree || {};
|
|
85
|
+
const di = gen_ecs.donnee_intermediaire || {};
|
|
86
|
+
return (
|
|
87
|
+
Number(di.qp0 || 0) > 0 &&
|
|
88
|
+
Number(de.position_volume_chauffe ?? 0) === 1 &&
|
|
89
|
+
Number(de.enum_usage_generateur_id) === 2
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
47
93
|
function tv_rendement_generation(di, de, du) {
|
|
48
94
|
const matcher = {
|
|
49
95
|
enum_type_generateur_ch_id: requestInputID(de, du, 'type_generateur_ch')
|
package/9_generateur_ch.spec.js
CHANGED
|
@@ -59,6 +59,8 @@ vi.mock('./13.2_generateur_combustion.js', () => ({
|
|
|
59
59
|
|
|
60
60
|
const {
|
|
61
61
|
calc_Qrec_gen_j,
|
|
62
|
+
calc_Qrec_gen_ecs_j,
|
|
63
|
+
isGenerateurEcsSeulRecuperable,
|
|
62
64
|
type_generateur_ch,
|
|
63
65
|
checkForGeneratorType,
|
|
64
66
|
calc_generateur_ch,
|
|
@@ -147,6 +149,71 @@ describe('calc_Qrec_gen_j - pertes de génération récupérées', () => {
|
|
|
147
149
|
});
|
|
148
150
|
});
|
|
149
151
|
|
|
152
|
+
/**
|
|
153
|
+
* 9.1.1 Pertes récupérées de génération pour le chauffage : générateur de la collection ECS
|
|
154
|
+
* assurant l'ECS uniquement (issue #153).
|
|
155
|
+
* Dperj = Nrefj * 1790 / 8760 ; Qgen_rec_j = 0,48 * Cper * Qp0 * Dperj
|
|
156
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §9.1.1
|
|
157
|
+
*/
|
|
158
|
+
describe('calc_Qrec_gen_ecs_j / isGenerateurEcsSeulRecuperable - générateur ECS seul', () => {
|
|
159
|
+
/** Générateur ECS seul (usage 2), en volume chauffé, avec Qp0 = 100 W. */
|
|
160
|
+
function generateurEcs(overridesDe = {}, di = { qp0: 100 }) {
|
|
161
|
+
return {
|
|
162
|
+
donnee_entree: {
|
|
163
|
+
position_volume_chauffe: 1,
|
|
164
|
+
enum_usage_generateur_id: '2',
|
|
165
|
+
presence_ventouse: 0,
|
|
166
|
+
...overridesDe
|
|
167
|
+
},
|
|
168
|
+
donnee_intermediaire: di
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
test('ECS seul, sans ventouse (Cper = 0.5) : 0.48 * 0.5 * Qp0 * Nref * 1790 / 8760', () => {
|
|
173
|
+
// 0.48 * 0.5 * 100 * (50 * 1790 / 8760) = 245.205479452...
|
|
174
|
+
expect(calc_Qrec_gen_ecs_j(generateurEcs(), 50)).toBeCloseTo(245.20547945205482, 9);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('ECS seul, avec ventouse (Cper = 0.75)', () => {
|
|
178
|
+
// 0.48 * 0.75 * 100 * (50 * 1790 / 8760) = 367.808219178...
|
|
179
|
+
const gen = generateurEcs({ presence_ventouse: 1 });
|
|
180
|
+
expect(calc_Qrec_gen_ecs_j(gen, 50)).toBeCloseTo(367.8082191780822, 9);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('la récupération est indépendante du besoin de chauffage et proportionnelle à Nref', () => {
|
|
184
|
+
const gen = generateurEcs();
|
|
185
|
+
expect(calc_Qrec_gen_ecs_j(gen, 100)).toBeCloseTo(2 * calc_Qrec_gen_ecs_j(gen, 50), 9);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('générateur hors volume chauffé : aucune récupération', () => {
|
|
189
|
+
const gen = generateurEcs({ position_volume_chauffe: 0 });
|
|
190
|
+
expect(isGenerateurEcsSeulRecuperable(gen)).toBe(false);
|
|
191
|
+
expect(calc_Qrec_gen_ecs_j(gen, 50)).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('position en volume chauffé non renseignée : aucune récupération', () => {
|
|
195
|
+
const gen = generateurEcs({ position_volume_chauffe: undefined });
|
|
196
|
+
expect(calc_Qrec_gen_ecs_j(gen, 50)).toBe(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("usage 'chauffage + ecs' : exclu (déjà compté dans la collection chauffage)", () => {
|
|
200
|
+
const gen = generateurEcs({ enum_usage_generateur_id: '3' });
|
|
201
|
+
expect(isGenerateurEcsSeulRecuperable(gen)).toBe(false);
|
|
202
|
+
expect(calc_Qrec_gen_ecs_j(gen, 50)).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('Qp0 nul ou absent : aucune récupération', () => {
|
|
206
|
+
expect(calc_Qrec_gen_ecs_j(generateurEcs({}, { qp0: 0 }), 50)).toBe(0);
|
|
207
|
+
expect(calc_Qrec_gen_ecs_j(generateurEcs({}, null), 50)).toBe(0);
|
|
208
|
+
expect(isGenerateurEcsSeulRecuperable({})).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('identifiants numériques acceptés (usage 2, position 1)', () => {
|
|
212
|
+
const gen = generateurEcs({ enum_usage_generateur_id: 2, position_volume_chauffe: '1' });
|
|
213
|
+
expect(isGenerateurEcsSeulRecuperable(gen)).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
150
217
|
/**
|
|
151
218
|
* Détermination du type de générateur exploitable pour un usage donné.
|
|
152
219
|
*/
|
package/README.md
CHANGED
|
@@ -370,28 +370,28 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
370
370
|
|
|
371
371
|
<!-- CORPUS:START -->
|
|
372
372
|
|
|
373
|
-
> **Version `1.8.
|
|
373
|
+
> **Version `1.8.8`** · à publier · branche `main` · généré le 2026-10-02
|
|
374
374
|
> Seuil de tolérance **5%**
|
|
375
375
|
|
|
376
376
|
<table>
|
|
377
377
|
<tr>
|
|
378
378
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
379
379
|
<td align="center"><strong>89 990</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
-
<td align="center"><strong>59
|
|
381
|
-
<td align="center"><strong>66,
|
|
380
|
+
<td align="center"><strong>59 879</strong><br/><sub>DPE conformes</sub></td>
|
|
381
|
+
<td align="center"><strong>66,54 %</strong><br/><sub>réussite globale</sub></td>
|
|
382
382
|
</tr>
|
|
383
383
|
</table>
|
|
384
384
|
|
|
385
385
|
| | Corpus | Réussite | | DPE conformes |
|
|
386
386
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
387
|
-
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,
|
|
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> | **88,
|
|
391
|
-
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **74,
|
|
392
|
-
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **
|
|
393
|
-
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,
|
|
394
|
-
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **49,
|
|
387
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,28 %** | `█████████░░░░░░░░░░░` | 4 628 / 10 000 |
|
|
388
|
+
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,80 %** | `██████████████████░░` | 9 180 / 10 000 |
|
|
389
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,70 %** | `██████████████████░░` | 8 863 / 9 992 |
|
|
390
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,40 %** | `██████████████████░░` | 8 840 / 10 000 |
|
|
391
|
+
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **74,14 %** | `███████████████░░░░░` | 7 413 / 9 999 |
|
|
392
|
+
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **70,61 %** | `██████████████░░░░░░` | 7 061 / 10 000 |
|
|
393
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,50 %** | `████████████░░░░░░░░` | 6 249 / 9 999 |
|
|
394
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **49,06 %** | `██████████░░░░░░░░░░` | 4 906 / 10 000 |
|
|
395
395
|
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,39 %** | `█████░░░░░░░░░░░░░░░` | 2 739 / 10 000 |
|
|
396
396
|
|
|
397
397
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
@@ -403,24 +403,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
403
403
|
|
|
404
404
|
<table>
|
|
405
405
|
<tr>
|
|
406
|
-
<td align="center"><strong>
|
|
407
|
-
<td align="center"><strong>4,
|
|
408
|
-
<td align="center"><strong>0,
|
|
409
|
-
<td align="center"><strong>
|
|
406
|
+
<td align="center"><strong>5,31 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>4,65 ms</strong><br/><sub>médiane</sub></td>
|
|
408
|
+
<td align="center"><strong>0,12 ms</strong><br/><sub>min</sub></td>
|
|
409
|
+
<td align="center"><strong>893,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** | 4,
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** |
|
|
417
|
-
| **Logement individuel (2025)** | 4,
|
|
418
|
-
| **Maison individuelle (2025)** | 5,
|
|
419
|
-
| **Immeuble · chauffage individuel** |
|
|
420
|
-
| **Appartement · chauffage collectif (2025)** |
|
|
421
|
-
| **Immeuble · chauffage collectif** | 5,
|
|
422
|
-
| **Immeuble · chauffage mixte** |
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** |
|
|
415
|
+
| **Généraliste** | 4,56 ms | 4,09 ms | 0,21 ms | 37,2 ms | 8,41 ms | 12,0 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 4,01 ms | 3,65 ms | 1,47 ms | 33,8 ms | 6,95 ms | 9,38 ms |
|
|
417
|
+
| **Logement individuel (2025)** | 4,98 ms | 4,43 ms | 1,62 ms | 38,0 ms | 9,30 ms | 12,8 ms |
|
|
418
|
+
| **Maison individuelle (2025)** | 5,92 ms | 5,33 ms | 1,85 ms | 40,3 ms | 10,6 ms | 13,7 ms |
|
|
419
|
+
| **Immeuble · chauffage individuel** | 6,70 ms | 5,72 ms | 0,13 ms | 136,8 ms | 13,4 ms | 22,2 ms |
|
|
420
|
+
| **Appartement · chauffage collectif (2025)** | 4,60 ms | 4,36 ms | 1,46 ms | 37,2 ms | 7,27 ms | 9,45 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 5,31 ms | 4,38 ms | 0,12 ms | 99,5 ms | 9,95 ms | 17,7 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 5,38 ms | 4,34 ms | 0,20 ms | 893,4 ms | 11,2 ms | 18,5 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,36 ms | 5,53 ms | 1,67 ms | 50,2 ms | 12,4 ms | 19,1 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/conso.js
CHANGED
|
@@ -122,6 +122,129 @@ function cout_electricite(cef) {
|
|
|
122
122
|
else return 56 + 0.15989 * cef;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Date d'entrée en vigueur du barème des prix des énergies mis à jour.
|
|
127
|
+
* Constatée sur les DPE publiés par l'ADEME (dataset dpe03existant) : barème 2021 jusqu'au
|
|
128
|
+
* 30/06/2024, barème mis à jour pour les DPE établis à partir du 01/07/2024 (et toujours en 2026).
|
|
129
|
+
*/
|
|
130
|
+
export const DATE_BAREME_COUT_2024 = '2024-07-01';
|
|
131
|
+
|
|
132
|
+
function cout_gaz_naturel_2024(cef) {
|
|
133
|
+
if (cef < 5009) return 0.1312 * cef;
|
|
134
|
+
else if (cef < 50055) return 182 + 0.09488 * cef;
|
|
135
|
+
else return 288 + 0.09274 * cef;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cout_electricite_2024(cef) {
|
|
139
|
+
if (cef < 1000) return 0.34721 * cef;
|
|
140
|
+
else if (cef < 2500) return 158 + 0.18954 * cef;
|
|
141
|
+
else if (cef < 5000) return 158 + 0.18949 * cef;
|
|
142
|
+
else if (cef < 15000) return 119 + 0.19726 * cef;
|
|
143
|
+
else return 78 + 0.20001 * cef;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Barème des prix des énergies applicable aux DPE établis à partir du 01/07/2024.
|
|
148
|
+
* Mêmes tranches qu'en 2021 ; valeurs calées sur les sorties Tribu (moteur CSTB) et les DPE ADEME.
|
|
149
|
+
*/
|
|
150
|
+
const coef_cout_2024 = {
|
|
151
|
+
'fioul domestique': 0.14821,
|
|
152
|
+
'réseau de chauffage urbain': 0.08921,
|
|
153
|
+
propane: 0.15672,
|
|
154
|
+
gpl: 0.15672,
|
|
155
|
+
butane: 0.23429,
|
|
156
|
+
charbon: 0.02787,
|
|
157
|
+
'bois – granulés (pellets) ou briquettes': 0.09897,
|
|
158
|
+
'bois – bûches': 0.042,
|
|
159
|
+
'bois – plaquettes forestières': 0.042,
|
|
160
|
+
'bois – plaquettes d’industrie': 0.042,
|
|
161
|
+
'gaz naturel': cout_gaz_naturel_2024,
|
|
162
|
+
"électricité d'origine renouvelable utilisée dans le bâtiment": cout_electricite_2024,
|
|
163
|
+
'électricité ch': cout_electricite_2024,
|
|
164
|
+
'électricité ecs': cout_electricite_2024,
|
|
165
|
+
'électricité fr': cout_electricite_2024,
|
|
166
|
+
'électricité éclairage': cout_electricite_2024,
|
|
167
|
+
'électricité auxiliaire': cout_electricite_2024
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sélection du barème des prix des énergies selon la date d'établissement du DPE.
|
|
172
|
+
* @param dateDpe {string} date d'établissement du DPE (AAAA-MM-JJ)
|
|
173
|
+
* @returns {object} barème 2021 (Annexe 7) avant le 01/07/2024, barème mis à jour ensuite
|
|
174
|
+
*/
|
|
175
|
+
export function getCoefCout(dateDpe) {
|
|
176
|
+
return new Date(dateDpe) >= new Date(DATE_BAREME_COUT_2024) ? coef_cout_2024 : coef_cout;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Clé (symbole) portant le barème des générateurs d'installations collectives d'un immeuble. */
|
|
180
|
+
const COUT_COLLECTIF = Symbol('cout_collectif');
|
|
181
|
+
|
|
182
|
+
/** Énergies dont le coût suit un barème par tranches, regroupées par énergie facturée. */
|
|
183
|
+
const ENERGIES_A_TRANCHES = {
|
|
184
|
+
'électricité ch': 'électricité',
|
|
185
|
+
'électricité ecs': 'électricité',
|
|
186
|
+
'électricité fr': 'électricité',
|
|
187
|
+
'électricité éclairage': 'électricité',
|
|
188
|
+
'électricité auxiliaire': 'électricité',
|
|
189
|
+
"électricité d'origine renouvelable utilisée dans le bâtiment": 'électricité renouvelable',
|
|
190
|
+
'gaz naturel': 'gaz naturel'
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Table « masque » : consommation conservée pour les clés du groupe, annulée pour les autres.
|
|
195
|
+
* Permet de totaliser la consommation d'une énergie avec les fonctions d'agrégation existantes.
|
|
196
|
+
* @param groupe {string} énergie facturée (valeur de ENERGIES_A_TRANCHES)
|
|
197
|
+
* @returns {object}
|
|
198
|
+
*/
|
|
199
|
+
export function masqueEnergie(groupe) {
|
|
200
|
+
const cles = Object.values(enums.type_energie)
|
|
201
|
+
.filter((label) => label !== 'électricité')
|
|
202
|
+
.concat(Object.keys(ENERGIES_A_TRANCHES));
|
|
203
|
+
return Object.fromEntries(
|
|
204
|
+
cles.map((cle) => [cle, ENERGIES_A_TRANCHES[cle] === groupe ? (c) => c : () => 0])
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Barème « prix moyen » pour les DPE établis à partir du 01/07/2024 (constaté sur Tribu et sur
|
|
210
|
+
* les DPE publiés par l'ADEME) : pour l'électricité et le gaz naturel, la tranche est déterminée
|
|
211
|
+
* sur la consommation annuelle TOTALE de l'énergie (tous usages) et le coût est réparti entre les
|
|
212
|
+
* postes au prorata de leur consommation.
|
|
213
|
+
* Immeuble : consommation des usages individuels rapportée au logement (N × f(C / N)) ;
|
|
214
|
+
* chauffage / ECS des installations collectives tarifés sur leur total (f(C)).
|
|
215
|
+
*
|
|
216
|
+
* @param base {object} barème (prix unitaires et fonctions par tranches)
|
|
217
|
+
* @param consoGroupe {function(masque): {total: number, collectif: number}} consommations EF
|
|
218
|
+
* @param nbLogements {number} nombre de logements (1 hors immeuble)
|
|
219
|
+
* @param genCollectifs {Set} générateurs de chauffage / ECS d'installations collectives
|
|
220
|
+
* @returns {object}
|
|
221
|
+
*/
|
|
222
|
+
export function coefCoutProrata(base, consoGroupe, nbLogements, genCollectifs) {
|
|
223
|
+
const coef = { ...base };
|
|
224
|
+
const coefCollectif = { ...base };
|
|
225
|
+
new Set(Object.values(ENERGIES_A_TRANCHES)).forEach((groupe) => {
|
|
226
|
+
const { total, collectif } = consoGroupe(masqueEnergie(groupe));
|
|
227
|
+
const individuel = total - collectif;
|
|
228
|
+
Object.keys(ENERGIES_A_TRANCHES)
|
|
229
|
+
.filter((cle) => ENERGIES_A_TRANCHES[cle] === groupe)
|
|
230
|
+
.forEach((cle) => {
|
|
231
|
+
const f = base[cle];
|
|
232
|
+
const prix = individuel > 0 ? (nbLogements * f(individuel / nbLogements)) / individuel : 0;
|
|
233
|
+
const prixCollectif = collectif > 0 ? f(collectif) / collectif : 0;
|
|
234
|
+
coef[cle] = (c) => prix * c;
|
|
235
|
+
coefCollectif[cle] = (c) => prixCollectif * c;
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
coef[COUT_COLLECTIF] = { generateurs: genCollectifs, coef: coefCollectif };
|
|
239
|
+
return coef;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Barème d'un générateur : barème collectif s'il appartient à une installation collective. */
|
|
243
|
+
function coefPourGenerateur(coef, gen) {
|
|
244
|
+
const collectif = coef?.[COUT_COLLECTIF];
|
|
245
|
+
return collectif?.generateurs.has(gen) ? collectif.coef : coef;
|
|
246
|
+
}
|
|
247
|
+
|
|
125
248
|
function getConso(coef, type_energie, conso) {
|
|
126
249
|
// is coef is a function, execute it
|
|
127
250
|
if (!coef) return conso;
|
|
@@ -197,7 +320,8 @@ export default function calc_conso(
|
|
|
197
320
|
prorataECS,
|
|
198
321
|
prorataChauffage,
|
|
199
322
|
dateDpe,
|
|
200
|
-
coeffEp
|
|
323
|
+
coeffEp,
|
|
324
|
+
nbLogements = 1
|
|
201
325
|
) {
|
|
202
326
|
const gen_ch = ch.reduce((acc, ch) => {
|
|
203
327
|
const generateur_chauffage = ch.generateur_chauffage_collection.generateur_chauffage;
|
|
@@ -237,6 +361,56 @@ export default function calc_conso(
|
|
|
237
361
|
return acc.concat(generateur_ecs);
|
|
238
362
|
}, []);
|
|
239
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Barème des coûts : barème 2021 (par poste) avant le 01/07/2024 ; ensuite barème mis à jour
|
|
366
|
+
* avec tranche déterminée sur la consommation totale de l'énergie (voir coefCoutProrata).
|
|
367
|
+
*/
|
|
368
|
+
const coefCout = () => {
|
|
369
|
+
const base = getCoefCout(dateDpe);
|
|
370
|
+
if (base === coef_cout) return coef_cout;
|
|
371
|
+
const genCollectifs = new Set();
|
|
372
|
+
if (nbLogements > 1) {
|
|
373
|
+
gen_ch
|
|
374
|
+
.filter((g) => g.donnee_entree.enum_type_installation_id !== '1')
|
|
375
|
+
.forEach((g) => genCollectifs.add(g));
|
|
376
|
+
ecs
|
|
377
|
+
.filter((inst) => (inst.donnee_entree.enum_type_installation_id ?? '1') !== '1')
|
|
378
|
+
.forEach((inst) =>
|
|
379
|
+
inst.generateur_ecs_collection.generateur_ecs.forEach((g) => genCollectifs.add(g))
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const consoGroupe = (masque) => {
|
|
383
|
+
const tot = calc_conso_pond(
|
|
384
|
+
Sh,
|
|
385
|
+
zc_id,
|
|
386
|
+
vt,
|
|
387
|
+
gen_ch,
|
|
388
|
+
gen_ecs,
|
|
389
|
+
fr,
|
|
390
|
+
'c',
|
|
391
|
+
masque,
|
|
392
|
+
prorataECS,
|
|
393
|
+
prorataChauffage,
|
|
394
|
+
ecs
|
|
395
|
+
);
|
|
396
|
+
const coll = calc_conso_pond(
|
|
397
|
+
0,
|
|
398
|
+
zc_id,
|
|
399
|
+
[],
|
|
400
|
+
gen_ch.filter((g) => genCollectifs.has(g)),
|
|
401
|
+
gen_ecs.filter((g) => genCollectifs.has(g)),
|
|
402
|
+
[],
|
|
403
|
+
'c',
|
|
404
|
+
masque,
|
|
405
|
+
prorataECS,
|
|
406
|
+
prorataChauffage,
|
|
407
|
+
[]
|
|
408
|
+
);
|
|
409
|
+
return { total: tot.c_5_usages, collectif: coll.c_ch + coll.c_ecs };
|
|
410
|
+
};
|
|
411
|
+
return coefCoutProrata(base, consoGroupe, nbLogements, genCollectifs);
|
|
412
|
+
};
|
|
413
|
+
|
|
240
414
|
const ret = {
|
|
241
415
|
ef_conso: calc_conso_pond(
|
|
242
416
|
Sh,
|
|
@@ -288,7 +462,7 @@ export default function calc_conso(
|
|
|
288
462
|
gen_ecs,
|
|
289
463
|
fr,
|
|
290
464
|
'cout',
|
|
291
|
-
|
|
465
|
+
coefCout(),
|
|
292
466
|
prorataECS,
|
|
293
467
|
prorataChauffage,
|
|
294
468
|
ecs,
|
|
@@ -424,7 +598,7 @@ function getEcsConso(gen_ecs, field, coef, prorataECS, prefix) {
|
|
|
424
598
|
const conso = gen_ecs.donnee_intermediaire[field];
|
|
425
599
|
const typeEnergie = getTypeEnergie(gen_ecs.donnee_entree, 'ecs');
|
|
426
600
|
|
|
427
|
-
let coeffConsoEcs = { ...coef };
|
|
601
|
+
let coeffConsoEcs = { ...coefPourGenerateur(coef, gen_ecs) };
|
|
428
602
|
|
|
429
603
|
if (prefix === 'emission_ges') {
|
|
430
604
|
coeffConsoEcs[typeEnergie] = gen_ecs.donnee_utilisateur.coeffEmissionGes;
|
|
@@ -458,7 +632,7 @@ function getChauffageConso(gen_ch, field, coef, prorataChauffage, prefix) {
|
|
|
458
632
|
? gen_ch.donnee_entree.cle_repartition_ch || prorataChauffage
|
|
459
633
|
: prorataChauffage;
|
|
460
634
|
|
|
461
|
-
let coeffConsoChauffage = { ...coef };
|
|
635
|
+
let coeffConsoChauffage = { ...coefPourGenerateur(coef, gen_ch) };
|
|
462
636
|
|
|
463
637
|
if (prefix === 'emission_ges') {
|
|
464
638
|
coeffConsoChauffage[typeEnergie] = gen_ch.donnee_utilisateur.coeffEmissionGes;
|
package/conso.spec.js
CHANGED
|
@@ -51,7 +51,11 @@ const {
|
|
|
51
51
|
classe_bilan_dpe,
|
|
52
52
|
classe_emission_ges,
|
|
53
53
|
getCoefKey,
|
|
54
|
-
coef_ep
|
|
54
|
+
coef_ep,
|
|
55
|
+
getCoefCout,
|
|
56
|
+
coefCoutProrata,
|
|
57
|
+
masqueEnergie,
|
|
58
|
+
DATE_BAREME_COUT_2024
|
|
55
59
|
} = await import('./conso.js');
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -499,3 +503,185 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
499
503
|
expect(ids).toContain('1');
|
|
500
504
|
});
|
|
501
505
|
});
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Barème des prix des énergies selon la date d'établissement du DPE.
|
|
509
|
+
* Barème 2021 : Annexe 7 de l'arrêté du 31/03/2021.
|
|
510
|
+
* Barème mis à jour (DPE à partir du 01/07/2024) : valeurs calées sur les sorties Tribu (CSTB)
|
|
511
|
+
* des autotests et sur les DPE publiés par l'ADEME (dataset dpe03existant), reproduites à ±1 €.
|
|
512
|
+
*/
|
|
513
|
+
describe('getCoefCout - sélection du barème par date de DPE', () => {
|
|
514
|
+
test('date de bascule : 01/07/2024', () => {
|
|
515
|
+
expect(DATE_BAREME_COUT_2024).toBe('2024-07-01');
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('DPE établi avant le 01/07/2024 : barème 2021 inchangé', () => {
|
|
519
|
+
const coef = getCoefCout('2024-06-30');
|
|
520
|
+
// Annexe 7 : électricité 1000 kWh => 149 + 0,14066 × 1000
|
|
521
|
+
expect(coef['électricité ch'](1000)).toBeCloseTo(149 + 0.14066 * 1000, 9);
|
|
522
|
+
expect(coef['gaz naturel'](3000)).toBeCloseTo(0.11121 * 3000, 9);
|
|
523
|
+
expect(coef['fioul domestique']).toBe(0.09142);
|
|
524
|
+
expect(coef.propane).toBe(0.14305);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test.each(['2024-07-01', '2025-12-31', '2026-01-15'])(
|
|
528
|
+
'DPE établi le %s : barème mis à jour',
|
|
529
|
+
(date) => {
|
|
530
|
+
const coef = getCoefCout(date);
|
|
531
|
+
// Tribu / ADEME : électricité 1000 kWh => 158 + 0,18954 × 1000
|
|
532
|
+
expect(coef['électricité ch'](1000)).toBeCloseTo(158 + 0.18954 * 1000, 9);
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
test('barème mis à jour : prix unitaires constants (fioul, propane, bois, réseau...)', () => {
|
|
537
|
+
const coef = getCoefCout('2026-01-15');
|
|
538
|
+
// Tribu : fioul 0,1482 €/kWh ; propane 0,1567 €/kWh (constants quelle que soit la conso)
|
|
539
|
+
expect(coef['fioul domestique']).toBe(0.14821);
|
|
540
|
+
expect(coef.propane).toBe(0.15672);
|
|
541
|
+
expect(coef.gpl).toBe(0.15672);
|
|
542
|
+
expect(coef.butane).toBe(0.23429);
|
|
543
|
+
expect(coef.charbon).toBe(0.02787);
|
|
544
|
+
expect(coef['réseau de chauffage urbain']).toBe(0.08921);
|
|
545
|
+
expect(coef['bois – granulés (pellets) ou briquettes']).toBe(0.09897);
|
|
546
|
+
expect(coef['bois – bûches']).toBe(0.042);
|
|
547
|
+
expect(coef['bois – plaquettes forestières']).toBe(0.042);
|
|
548
|
+
expect(coef['bois – plaquettes d’industrie']).toBe(0.042);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test.each([
|
|
552
|
+
// [conso, coût attendu] électricité, une valeur par tranche + limites
|
|
553
|
+
[500, 0.34721 * 500],
|
|
554
|
+
[999.99, 0.34721 * 999.99],
|
|
555
|
+
[1000, 158 + 0.18954 * 1000],
|
|
556
|
+
[2499.99, 158 + 0.18954 * 2499.99],
|
|
557
|
+
[2500, 158 + 0.18949 * 2500],
|
|
558
|
+
[4999.99, 158 + 0.18949 * 4999.99],
|
|
559
|
+
[5000, 119 + 0.19726 * 5000],
|
|
560
|
+
[14999.99, 119 + 0.19726 * 14999.99],
|
|
561
|
+
[15000, 78 + 0.20001 * 15000],
|
|
562
|
+
[20000, 78 + 0.20001 * 20000]
|
|
563
|
+
])('barème mis à jour - électricité %s kWh => %s €', (conso, attendu) => {
|
|
564
|
+
const coef = getCoefCout('2026-01-15');
|
|
565
|
+
[
|
|
566
|
+
'électricité ch',
|
|
567
|
+
'électricité ecs',
|
|
568
|
+
'électricité fr',
|
|
569
|
+
'électricité éclairage',
|
|
570
|
+
'électricité auxiliaire',
|
|
571
|
+
"électricité d'origine renouvelable utilisée dans le bâtiment"
|
|
572
|
+
].forEach((cle) => expect(coef[cle](conso)).toBeCloseTo(attendu, 9));
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test.each([
|
|
576
|
+
// [conso, coût attendu] gaz naturel, une valeur par tranche + limites
|
|
577
|
+
[3000, 0.1312 * 3000],
|
|
578
|
+
[5008.99, 0.1312 * 5008.99],
|
|
579
|
+
[5009, 182 + 0.09488 * 5009],
|
|
580
|
+
[50054.99, 182 + 0.09488 * 50054.99],
|
|
581
|
+
[50055, 288 + 0.09274 * 50055],
|
|
582
|
+
[100000, 288 + 0.09274 * 100000]
|
|
583
|
+
])('barème mis à jour - gaz naturel %s kWh => %s €', (conso, attendu) => {
|
|
584
|
+
expect(getCoefCout('2026-01-15')['gaz naturel'](conso)).toBeCloseTo(attendu, 9);
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
describe("masqueEnergie / coefCoutProrata - tranche sur la conso totale de l'énergie", () => {
|
|
589
|
+
test('masque électricité : clés électriques conservées, autres énergies annulées', () => {
|
|
590
|
+
const masque = masqueEnergie('électricité');
|
|
591
|
+
expect(masque['électricité ch'](10)).toBe(10);
|
|
592
|
+
expect(masque['électricité auxiliaire'](10)).toBe(10);
|
|
593
|
+
expect(masque['gaz naturel'](10)).toBe(0);
|
|
594
|
+
expect(masque['réseau de chauffage urbain'](10)).toBe(0);
|
|
595
|
+
expect(masque["électricité d'origine renouvelable utilisée dans le bâtiment"](10)).toBe(0);
|
|
596
|
+
expect(masque['électricité']).toBeUndefined();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('aucune consommation : prix moyen nul', () => {
|
|
600
|
+
const coef = coefCoutProrata(
|
|
601
|
+
getCoefCout('2026-01-15'),
|
|
602
|
+
() => ({ total: 0, collectif: 0 }),
|
|
603
|
+
1,
|
|
604
|
+
new Set()
|
|
605
|
+
);
|
|
606
|
+
expect(coef['électricité ch'](100)).toBe(0);
|
|
607
|
+
expect(coef['gaz naturel'](100)).toBe(0);
|
|
608
|
+
// Prix constants conservés
|
|
609
|
+
expect(coef['fioul domestique']).toBe(0.14821);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
describe('calc_conso - coût avec le barème mis à jour (DPE 2026)', () => {
|
|
614
|
+
const DATE_DPE_2026 = '2026-01-15';
|
|
615
|
+
let errorSpy;
|
|
616
|
+
|
|
617
|
+
beforeEach(() => {
|
|
618
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
afterEach(() => {
|
|
622
|
+
errorSpy.mockRestore();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('DPE antérieur au 01/07/2024 : coût par poste au barème 2021 (inchangé)', () => {
|
|
626
|
+
const ch = [installCh({}, [genCh('1', { conso_ch: 3000, conso_ch_depensier: 3000 })])];
|
|
627
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, '2024-06-30', coef_ep);
|
|
628
|
+
// Chauffage tarifé seul : 122 + 0,15176 × 3000 ; éclairage seul : 0,29007 × 100
|
|
629
|
+
expect(res.cout.cout_ch).toBeCloseTo(122 + 0.15176 * 3000, 9);
|
|
630
|
+
expect(res.cout.cout_eclairage).toBeCloseTo(0.29007 * 100, 9);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test('maison tout électrique : tranche sur la conso électrique totale, coût réparti au prorata', () => {
|
|
634
|
+
const ch = [installCh({}, [genCh('1', { conso_ch: 3000, conso_ch_depensier: 3000 })])];
|
|
635
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE_2026, coef_ep);
|
|
636
|
+
// Conso élec totale = 3000 (ch) + 100 (éclairage = 1 × Sh) = 3100 kWh
|
|
637
|
+
const total = 158 + 0.18949 * 3100;
|
|
638
|
+
expect(res.cout.cout_5_usages).toBeCloseTo(total, 9);
|
|
639
|
+
expect(res.cout.cout_ch).toBeCloseTo((total * 3000) / 3100, 9);
|
|
640
|
+
expect(res.cout.cout_eclairage).toBeCloseTo((total * 100) / 3100, 9);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test('maison gaz + électricité : chaque énergie tarifée sur sa propre conso totale', () => {
|
|
644
|
+
const ch = [installCh({}, [genCh('2', { conso_ch: 8000, conso_ch_depensier: 8000 })])];
|
|
645
|
+
const ecs = [installEcs({}, [genEcs('2', { conso_ecs: 2000, conso_ecs_depensier: 2000 })])];
|
|
646
|
+
const res = calc_conso(100, 1, 1, [], ch, ecs, [], 1, 1, DATE_DPE_2026, coef_ep);
|
|
647
|
+
// Gaz total 10000 kWh => 182 + 0,09488 × 10000 ; élec (éclairage) 100 kWh => 0,34721 × 100
|
|
648
|
+
const gaz = 182 + 0.09488 * 10000;
|
|
649
|
+
expect(res.cout.cout_ch).toBeCloseTo((gaz * 8000) / 10000, 9);
|
|
650
|
+
expect(res.cout.cout_ecs).toBeCloseTo((gaz * 2000) / 10000, 9);
|
|
651
|
+
expect(res.cout.cout_eclairage).toBeCloseTo(0.34721 * 100, 9);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
test('immeuble à chauffage individuel : barème appliqué par logement (N × f(C / N))', () => {
|
|
655
|
+
const ch = [installCh({}, [genCh('1', { conso_ch: 8000, conso_ch_depensier: 8000 })])];
|
|
656
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE_2026, coef_ep, 4);
|
|
657
|
+
// Élec totale 8100 kWh pour 4 logements : 4 × f(2025) = 4 × (158 + 0,18954 × 2025)
|
|
658
|
+
expect(res.cout.cout_5_usages).toBeCloseTo(4 * (158 + 0.18954 * 2025), 9);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('immeuble à installations collectives : chauffage / ECS collectifs tarifés sur leur total', () => {
|
|
662
|
+
const ch = [
|
|
663
|
+
installCh({ enum_type_installation_id: '2' }, [
|
|
664
|
+
genCh('2', { conso_ch: 60000, conso_ch_depensier: 60000 })
|
|
665
|
+
])
|
|
666
|
+
];
|
|
667
|
+
const ecs = [
|
|
668
|
+
installEcs({ enum_type_installation_id: '2' }, [
|
|
669
|
+
genEcs('1', { conso_ecs: 4000, conso_ecs_depensier: 4000 })
|
|
670
|
+
])
|
|
671
|
+
];
|
|
672
|
+
const res = calc_conso(100, 1, 1, [], ch, ecs, [], 1, 1, DATE_DPE_2026, coef_ep, 4);
|
|
673
|
+
// Gaz collectif 60000 kWh => 288 + 0,09274 × 60000 (pas de division par logement)
|
|
674
|
+
expect(res.cout.cout_ch).toBeCloseTo(288 + 0.09274 * 60000, 9);
|
|
675
|
+
// ECS élec collective 4000 kWh => 158 + 0,18949 × 4000
|
|
676
|
+
expect(res.cout.cout_ecs).toBeCloseTo(158 + 0.18949 * 4000, 9);
|
|
677
|
+
// Éclairage (usage individuel) 100 kWh / 4 logements => 4 × 0,34721 × 25
|
|
678
|
+
expect(res.cout.cout_eclairage).toBeCloseTo(4 * 0.34721 * 25, 9);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("immeuble, ECS sans type d'installation : considérée individuelle", () => {
|
|
682
|
+
const ecs = [installEcs({}, [genEcs('1', { conso_ecs: 4000, conso_ecs_depensier: 4000 })])];
|
|
683
|
+
const res = calc_conso(100, 1, 1, [], [], ecs, [], 1, 1, DATE_DPE_2026, coef_ep, 4);
|
|
684
|
+
// Élec totale 4100 kWh / 4 logements => 4 × (158 + 0,18954 × 1025)
|
|
685
|
+
expect(res.cout.cout_5_usages).toBeCloseTo(4 * (158 + 0.18954 * 1025), 9);
|
|
686
|
+
});
|
|
687
|
+
});
|