@open3cl/engine 1.8.50 → 1.8.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/11_ecs.js +33 -0
- package/11_ecs.spec.js +86 -1
- package/README.md +23 -23
- package/conso.js +130 -39
- package/conso.spec.js +228 -10
- package/engine.js +13 -5
- package/engine.spec.js +18 -0
- package/package.json +1 -1
package/11_ecs.js
CHANGED
|
@@ -179,6 +179,33 @@ export function ratio_besoin_ecs_individuel(dpe, de, surfaceImmeuble, nombreAppa
|
|
|
179
179
|
return ratio < 1 ? ratio : 1 / nombreAppartements;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* enum_type_installation_id des installations ECS collectives :
|
|
184
|
+
* 2 - installation collective ; 3 - installation collective multi-bâtiment (réseau de chaleur)
|
|
185
|
+
*/
|
|
186
|
+
const TYPES_INSTALLATION_ECS_COLLECTIVE = ['2', '3'];
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Rdim (nombre d'installations individuelles identiques) sans objet pour une installation ECS
|
|
190
|
+
* collective décrite dans un DPE immeuble : elle dessert tout l'immeuble, le besoin ECS n'est donc
|
|
191
|
+
* pas ramené à un logement type. Sinon, le besoin serait divisé par rdim alors que les pertes de
|
|
192
|
+
* stockage (Qgw) et à l'arrêt (qp0) du générateur collectif restent celles de l'immeuble,
|
|
193
|
+
* effondrant les rendements de génération et de stockage.
|
|
194
|
+
* @see Guide Tribu TE-211208 p. 24 (Rdim = nombre d'installations individuelles identiques)
|
|
195
|
+
* @see Moteur_DPE.dll Calcul_batiment.cs l.583-592 (Becs / Rdim seulement pour une installation
|
|
196
|
+
* individuelle en immeuble ; Becs × Sh_ecs / SH sans Rdim pour une installation collective)
|
|
197
|
+
* @param th {string} type de bâtiment (maison, appartement, immeuble)
|
|
198
|
+
* @param de {Donnee_entree} donnée d'entrée de l'installation ECS
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
export function isRdimEcsNeutralise(th, de) {
|
|
202
|
+
return (
|
|
203
|
+
th === 'immeuble' &&
|
|
204
|
+
TYPES_INSTALLATION_ECS_COLLECTIVE.includes(String(de.enum_type_installation_id)) &&
|
|
205
|
+
Number(de.rdim) > 1
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
182
209
|
export default function calc_ecs(
|
|
183
210
|
dpe,
|
|
184
211
|
ecs,
|
|
@@ -227,6 +254,12 @@ export default function calc_ecs(
|
|
|
227
254
|
((de.surface_habitable / (surfaceImmeuble || 1)) * (de.rdim || 1)) /
|
|
228
255
|
(de.nombre_logement * nombreAppartements);
|
|
229
256
|
}
|
|
257
|
+
} else if (isRdimEcsNeutralise(th, de)) {
|
|
258
|
+
/**
|
|
259
|
+
* Installation ECS collective d'un DPE immeuble : le besoin est celui de l'immeuble (pas de
|
|
260
|
+
* division par rdim), la consommation n'étant pas remultipliée par rdim (conso.js).
|
|
261
|
+
*/
|
|
262
|
+
di.rdim_neutralise = true;
|
|
230
263
|
} else if (de.rdim) {
|
|
231
264
|
di.ratio_besoin_ecs = 1 / de.rdim || 1;
|
|
232
265
|
}
|
package/11_ecs.spec.js
CHANGED
|
@@ -31,7 +31,7 @@ vi.mock('./14_generateur_ecs.js', () => ({
|
|
|
31
31
|
default: vi.fn()
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
const { default: calc_ecs } = await import('./11_ecs.js');
|
|
34
|
+
const { default: calc_ecs, isRdimEcsNeutralise } = await import('./11_ecs.js');
|
|
35
35
|
const { tv, requestInput } = await import('./utils.js');
|
|
36
36
|
const { default: calc_gen_ecs } = await import('./14_generateur_ecs.js');
|
|
37
37
|
|
|
@@ -379,6 +379,91 @@ describe('calc_ecs - immeuble à plusieurs installations ECS (prorata des surfac
|
|
|
379
379
|
});
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Installation ECS collective unique d'un DPE immeuble : le rdim (nombre d'installations
|
|
384
|
+
* individuelles identiques) est sans objet, le besoin n'est pas divisé par rdim et la consommation
|
|
385
|
+
* n'est pas remultipliée par rdim à l'agrégation (conso.js getRdimEcs).
|
|
386
|
+
* @see Guide Tribu TE-211208 p. 24
|
|
387
|
+
* @see Moteur_DPE.dll Calcul_batiment.cs l.583-592 (Becs / Rdim seulement en installation individuelle)
|
|
388
|
+
*/
|
|
389
|
+
describe('calc_ecs - installation ECS collective en immeuble : rdim neutralisé', () => {
|
|
390
|
+
test.each([
|
|
391
|
+
['collective', '2'],
|
|
392
|
+
['collective multi-bâtiment', '3']
|
|
393
|
+
])('installation %s, rdim 22 : besoin de l’immeuble (ratio 1)', (_libelle, type) => {
|
|
394
|
+
const ecs = makeEcs({
|
|
395
|
+
tv_rendement_distribution_ecs_id: '5',
|
|
396
|
+
enum_type_installation_id: type,
|
|
397
|
+
rdim: 22
|
|
398
|
+
});
|
|
399
|
+
calc_ecs({}, ecs, 1000, 1500, 1, 'ca1', 'h1a', 'immeuble', false, 681, 22, false, {});
|
|
400
|
+
|
|
401
|
+
expect(ecs.donnee_intermediaire.ratio_besoin_ecs).toBe(1);
|
|
402
|
+
expect(ecs.donnee_intermediaire.besoin_ecs).toBe(1000);
|
|
403
|
+
expect(ecs.donnee_intermediaire.besoin_ecs_depensier).toBe(1500);
|
|
404
|
+
expect(ecs.donnee_intermediaire.rdim_neutralise).toBe(true);
|
|
405
|
+
// le générateur reçoit le besoin de l'immeuble (cohérent avec ses pertes Qgw / qp0)
|
|
406
|
+
expect(vi.mocked(calc_gen_ecs).mock.calls[0][2].besoin_ecs).toBe(1000);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test.each([
|
|
410
|
+
['installation individuelle en immeuble', '1', 'immeuble', 0.25],
|
|
411
|
+
['installation hybride collective-individuelle', '4', 'immeuble', 0.25],
|
|
412
|
+
['DPE appartement (installation collective)', '2', 'appartement', 0.25],
|
|
413
|
+
['DPE maison', '2', 'maison', 0.25]
|
|
414
|
+
])('%s : division par rdim conservée', (_libelle, type, th, ratio) => {
|
|
415
|
+
const ecs = makeEcs({
|
|
416
|
+
tv_rendement_distribution_ecs_id: '5',
|
|
417
|
+
enum_type_installation_id: type,
|
|
418
|
+
rdim: 4
|
|
419
|
+
});
|
|
420
|
+
calc_ecs({}, ecs, 1000, 1500, 1, 'ca1', 'h1a', th, false, 200, null, false, {});
|
|
421
|
+
|
|
422
|
+
expect(ecs.donnee_intermediaire.ratio_besoin_ecs).toBe(ratio);
|
|
423
|
+
expect(ecs.donnee_intermediaire.rdim_neutralise).toBeUndefined();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('installation collective en immeuble, rdim 1 : inchangé (ratio 1, pas d’indicateur)', () => {
|
|
427
|
+
const ecs = makeEcs({
|
|
428
|
+
tv_rendement_distribution_ecs_id: '5',
|
|
429
|
+
enum_type_installation_id: '2',
|
|
430
|
+
rdim: 1
|
|
431
|
+
});
|
|
432
|
+
calc_ecs({}, ecs, 1000, 1500, 1, 'ca1', 'h1a', 'immeuble', false, 200, null, false, {});
|
|
433
|
+
|
|
434
|
+
expect(ecs.donnee_intermediaire.ratio_besoin_ecs).toBe(1);
|
|
435
|
+
expect(ecs.donnee_intermediaire.rdim_neutralise).toBeUndefined();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('immeuble multi-installations : prorata Sh / SH / rdim conservé (branche prioritaire)', () => {
|
|
439
|
+
const ecs = makeEcs({
|
|
440
|
+
tv_rendement_distribution_ecs_id: '5',
|
|
441
|
+
enum_type_installation_id: '2',
|
|
442
|
+
surface_habitable: 100,
|
|
443
|
+
rdim: 4
|
|
444
|
+
});
|
|
445
|
+
calc_ecs({}, ecs, 1000, 1500, 1, 'ca1', 'h1a', 'immeuble', false, 200, null, false, {}, true);
|
|
446
|
+
|
|
447
|
+
expect(ecs.donnee_intermediaire.ratio_besoin_ecs).toBeCloseTo(100 / 200 / 4, 9);
|
|
448
|
+
expect(ecs.donnee_intermediaire.rdim_neutralise).toBeUndefined();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('isRdimEcsNeutralise', () => {
|
|
453
|
+
test.each([
|
|
454
|
+
['immeuble', '2', 22, true],
|
|
455
|
+
['immeuble', 3, '2', true],
|
|
456
|
+
['immeuble', '2', 1, false],
|
|
457
|
+
['immeuble', '2', undefined, false],
|
|
458
|
+
['immeuble', '1', 22, false],
|
|
459
|
+
['immeuble', '4', 22, false],
|
|
460
|
+
['appartement', '2', 22, false],
|
|
461
|
+
['maison', '2', 22, false]
|
|
462
|
+
])('th %s, type %s, rdim %s → %s', (th, type, rdim, attendu) => {
|
|
463
|
+
expect(isRdimEcsNeutralise(th, { enum_type_installation_id: type, rdim })).toBe(attendu);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
382
467
|
describe('calc_ecs - rendement de distribution', () => {
|
|
383
468
|
test('la ligne forfaitaire alimente rendement_distribution et l’identifiant de table', () => {
|
|
384
469
|
const de = { tv_rendement_distribution_ecs_id: '5' };
|
package/README.md
CHANGED
|
@@ -370,29 +370,29 @@ 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.51`** · branche `main` · généré le 2026-10-12
|
|
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 984</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
-
<td align="center"><strong>
|
|
381
|
-
<td align="center"><strong>
|
|
380
|
+
<td align="center"><strong>65 391</strong><br/><sub>DPE conformes</sub></td>
|
|
381
|
+
<td align="center"><strong>72,67 %</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> | **92,
|
|
387
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,65 %** | `█████████░░░░░░░░░░░` | 4 665 / 9 999 |
|
|
388
|
+
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,90 %** | `███████████████████░` | 9 290 / 10 000 |
|
|
389
|
+
| 🟢 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **91,27 %** | `██████████████████░░` | 9 127 / 10 000 |
|
|
389
390
|
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,93 %** | `██████████████████░░` | 8 986 / 9 992 |
|
|
390
|
-
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,
|
|
391
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,88 %** | `██████████████████░░` | 8 887 / 9 999 |
|
|
391
392
|
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,51 %** | `█████████████████░░░` | 8 449 / 9 998 |
|
|
392
|
-
| 🟡 | **
|
|
393
|
-
|
|
|
394
|
-
| 🔴 | **
|
|
395
|
-
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,50 %** | `██████░░░░░░░░░░░░░░` | 2 750 / 10 000 |
|
|
393
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **74,46 %** | `███████████████░░░░░` | 7 445 / 9 999 |
|
|
394
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **56,59 %** | `███████████░░░░░░░░░` | 5 657 / 9 997 |
|
|
395
|
+
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **28,85 %** | `██████░░░░░░░░░░░░░░` | 2 885 / 10 000 |
|
|
396
396
|
|
|
397
397
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
398
398
|
|
|
@@ -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>5,
|
|
407
|
-
<td align="center"><strong>4,
|
|
408
|
-
<td align="center"><strong>0,
|
|
409
|
-
<td align="center"><strong>
|
|
406
|
+
<td align="center"><strong>5,45 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>4,78 ms</strong><br/><sub>médiane</sub></td>
|
|
408
|
+
<td align="center"><strong>0,15 ms</strong><br/><sub>min</sub></td>
|
|
409
|
+
<td align="center"><strong>841,3 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,50 ms | 4,
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** | 4,06 ms | 3,
|
|
417
|
-
| **
|
|
418
|
-
| **
|
|
419
|
-
| **
|
|
420
|
-
| **
|
|
421
|
-
| **Immeuble · chauffage collectif** | 5,
|
|
422
|
-
| **Immeuble · chauffage mixte** | 5,
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** | 6,
|
|
415
|
+
| **Généraliste** | 4,50 ms | 4,05 ms | 0,20 ms | 31,7 ms | 8,21 ms | 11,1 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 4,06 ms | 3,72 ms | 1,41 ms | 25,8 ms | 6,98 ms | 8,99 ms |
|
|
417
|
+
| **Appartement · chauffage collectif (2025)** | 4,59 ms | 4,24 ms | 1,54 ms | 36,0 ms | 7,48 ms | 9,85 ms |
|
|
418
|
+
| **Logement individuel (2025)** | 5,25 ms | 4,72 ms | 1,67 ms | 35,7 ms | 9,73 ms | 13,1 ms |
|
|
419
|
+
| **Maison individuelle (2025)** | 6,28 ms | 5,68 ms | 1,90 ms | 54,8 ms | 11,2 ms | 14,4 ms |
|
|
420
|
+
| **Immeuble · chauffage individuel** | 6,81 ms | 5,72 ms | 0,15 ms | 173,2 ms | 13,9 ms | 22,8 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 5,66 ms | 4,82 ms | 0,19 ms | 103,6 ms | 10,4 ms | 17,8 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 5,50 ms | 4,52 ms | 0,20 ms | 841,3 ms | 11,4 ms | 18,4 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,44 ms | 5,54 ms | 1,68 ms | 71,5 ms | 12,0 ms | 19,9 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import enums from './enums.js';
|
|
2
2
|
import calc_conso_eclairage from './16_conso_eclairage.js';
|
|
3
3
|
import tvs from './tv.js';
|
|
4
|
-
import { tv } from './utils.js';
|
|
4
|
+
import { bug_for_bug_compat, tv } from './utils.js';
|
|
5
5
|
|
|
6
6
|
export const COEFF_EP_2_3 = 2.3;
|
|
7
7
|
export const COEFF_EP_1_9 = 1.9;
|
|
@@ -311,53 +311,130 @@ function getConso(coef, type_energie, conso) {
|
|
|
311
311
|
return conso;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Arrêtés « réseaux de chaleur » et table de valeurs associée (modèle de données de l'observatoire
|
|
316
|
+
* DPE, `modele_donnee/arrete_reseau_chaleur.json`). Le nom de la table ne correspond pas à l'année
|
|
317
|
+
* de l'arrêté (ex. l'arrêté du 05/07/2024 publie la table `reseau_chaleur_2023`).
|
|
318
|
+
* - date_arrete : valeur attendue dans `date_arrete_reseau_chaleur` ;
|
|
319
|
+
* - date_application : date à partir de laquelle l'arrêté s'applique (DPE sans date d'arrêté).
|
|
320
|
+
*/
|
|
321
|
+
export const ARRETES_RESEAU_CHALEUR = [
|
|
322
|
+
{ table: 'reseau_chaleur_2020', date_arrete: '2020-10-12', date_application: '2020-10-17' },
|
|
323
|
+
{ table: 'reseau_chaleur_2021', date_arrete: '2021-10-21', date_application: '2022-01-18' },
|
|
324
|
+
{ table: 'reseau_chaleur_2022', date_arrete: '2023-03-16', date_application: '2023-04-16' },
|
|
325
|
+
{ table: 'reseau_chaleur_2023', date_arrete: '2024-07-05', date_application: '2024-08-09' },
|
|
326
|
+
{ table: 'reseau_chaleur_2024', date_arrete: '2025-04-11', date_application: '2025-05-26' },
|
|
327
|
+
{ table: 'reseau_chaleur_2025', date_arrete: '2026-04-25', date_application: '2026-05-25' }
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
/** Facteur « AUTRES RESEAUX DE CHALEUR » (réseau non répertorié ou inconnu), kgCO2/kWh */
|
|
331
|
+
export const COEF_GES_AUTRES_RESEAUX_CHALEUR = 0.385;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Normalise une date (chaîne ISO ou Date) au format AAAA-MM-JJ, null si invalide.
|
|
335
|
+
* @param date {string|Date|undefined}
|
|
336
|
+
* @returns {string|null}
|
|
337
|
+
*/
|
|
338
|
+
function isoDate(date) {
|
|
339
|
+
if (typeof date !== 'string' && !(date instanceof Date)) return null;
|
|
340
|
+
const d = new Date(date);
|
|
341
|
+
return isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Index (dans ARRETES_RESEAU_CHALEUR) de l'arrêté réseau de chaleur utilisé par le générateur :
|
|
346
|
+
* - `date_arrete_reseau_chaleur` renseignée : dernier arrêté publié à cette date ou avant ;
|
|
347
|
+
* - sinon : dernier arrêté applicable à la date d'établissement du DPE ;
|
|
348
|
+
* - date antérieure au premier arrêté ou illisible : premier arrêté.
|
|
349
|
+
* @param dateArrete {string|undefined} date_arrete_reseau_chaleur du générateur
|
|
350
|
+
* @param dateDpe {string|undefined} date d'établissement du DPE
|
|
351
|
+
* @returns {number}
|
|
352
|
+
*/
|
|
353
|
+
export function indexArreteReseauChaleur(dateArrete, dateDpe) {
|
|
354
|
+
const arrete = isoDate(dateArrete);
|
|
355
|
+
const ref = arrete ?? isoDate(dateDpe);
|
|
356
|
+
const champ = arrete ? 'date_arrete' : 'date_application';
|
|
357
|
+
let index = 0;
|
|
358
|
+
if (ref) {
|
|
359
|
+
ARRETES_RESEAU_CHALEUR.forEach((a, i) => {
|
|
360
|
+
if (a[champ] <= ref) index = i;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return index;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Contenu CO2 d'un réseau de chaleur répertorié (kgCO2/kWh).
|
|
368
|
+
*
|
|
369
|
+
* Le facteur réglementaire est le contenu CO2 en analyse de cycle de vie (`contenu_co2_acv`) de la
|
|
370
|
+
* table publiée par l'arrêté utilisé. Si l'identifiant n'y figure pas (réseau retiré d'un arrêté plus
|
|
371
|
+
* récent), on retient la table antérieure la plus récente qui le contient. Réseau introuvable :
|
|
372
|
+
* facteur « autres réseaux de chaleur » (0,385).
|
|
373
|
+
*
|
|
374
|
+
* Mode bug_for_bug_compat : avec l'arrêté du 05/07/2024 (table reseau_chaleur_2023), LICIEL
|
|
375
|
+
* transmet au moteur le contenu CO2 hors ACV (`contenu_co2`) : 1 226/1 234 générateurs LICIEL des
|
|
376
|
+
* corpus immeuble, alors que DPEWIN, Pleiades et 4.1.1 exportent la valeur ACV (ex. réseau 6403C
|
|
377
|
+
* de Pau : 0,047 au lieu de 0,061).
|
|
378
|
+
*
|
|
379
|
+
* @param identifiant_reseau {string}
|
|
380
|
+
* @param dateArrete {string|undefined}
|
|
381
|
+
* @param dateDpe {string|undefined}
|
|
382
|
+
* @param versionLogiciel {string|undefined} administratif.diagnostiqueur.version_logiciel
|
|
383
|
+
* @returns {number}
|
|
384
|
+
*/
|
|
385
|
+
export function getContenuCo2ReseauChaleur(
|
|
386
|
+
identifiant_reseau,
|
|
387
|
+
dateArrete,
|
|
388
|
+
dateDpe,
|
|
389
|
+
versionLogiciel
|
|
390
|
+
) {
|
|
391
|
+
const index = indexArreteReseauChaleur(dateArrete, dateDpe);
|
|
392
|
+
|
|
393
|
+
for (let i = index; i >= 0; i--) {
|
|
394
|
+
const { table } = ARRETES_RESEAU_CHALEUR[i];
|
|
395
|
+
const row = tv(table, { identifiant_reseau });
|
|
396
|
+
if (row) {
|
|
397
|
+
const contenuHorsAcv =
|
|
398
|
+
bug_for_bug_compat &&
|
|
399
|
+
table === 'reseau_chaleur_2023' &&
|
|
400
|
+
/liciel/i.test(versionLogiciel || '');
|
|
401
|
+
return Number(contenuHorsAcv ? row.contenu_co2 : row.contenu_co2_acv);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.error(
|
|
406
|
+
`Aucun coefficient GES trouvé pour le réseau de chaleur ${identifiant_reseau} : valeur « autres réseaux de chaleur » ${COEF_GES_AUTRES_RESEAUX_CHALEUR}`
|
|
407
|
+
);
|
|
408
|
+
return COEF_GES_AUTRES_RESEAUX_CHALEUR;
|
|
409
|
+
}
|
|
410
|
+
|
|
314
411
|
/**
|
|
315
412
|
* Récupération du coefficient d'émission GES pour le générateur
|
|
316
413
|
* @param gen_ch {GenerateurChauffageItem||GenerateurEcsItem}
|
|
317
414
|
* @param suffix {'ch'|'ecs'}
|
|
318
415
|
* @param dateDpe {string} date d'établissement du DPE
|
|
416
|
+
* @param versionLogiciel {string} version du logiciel de saisie
|
|
319
417
|
* @returns {number}
|
|
320
418
|
*/
|
|
321
|
-
function getGesCoeffForGenerateur(gen_ch, suffix, dateDpe) {
|
|
419
|
+
function getGesCoeffForGenerateur(gen_ch, suffix, dateDpe, versionLogiciel) {
|
|
322
420
|
const typeEnergie = getTypeEnergie(gen_ch.donnee_entree, suffix);
|
|
323
421
|
|
|
324
|
-
let anneeDpe;
|
|
325
|
-
try {
|
|
326
|
-
if (gen_ch.donnee_entree.date_arrete_reseau_chaleur) {
|
|
327
|
-
anneeDpe = new Date(gen_ch.donnee_entree.date_arrete_reseau_chaleur).getFullYear() - 1;
|
|
328
|
-
} else {
|
|
329
|
-
anneeDpe = new Date(dateDpe).getFullYear() - 1;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (anneeDpe < 2024) {
|
|
333
|
-
anneeDpe = 2022;
|
|
334
|
-
}
|
|
335
|
-
} catch (e) {
|
|
336
|
-
anneeDpe = 2022;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const key_reseau_chaleur = `reseau_chaleur_${anneeDpe}`;
|
|
340
|
-
|
|
341
422
|
/**
|
|
342
423
|
* Cas spécifique des réseaux de chaleur urbain qui ont des coefficients différents en fonction de réseau de distribution
|
|
343
424
|
*/
|
|
344
425
|
if (typeEnergie === 'réseau de chauffage urbain') {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
Aucun coefficient GES trouvé pour le réseau de chaleur ${identifiant_reseau}
|
|
355
|
-
`);
|
|
356
|
-
}
|
|
357
|
-
} else {
|
|
358
|
-
// Prendre la valeur pour "AUTRES RESEAUX DE CHALEUR"
|
|
359
|
-
return 0.385;
|
|
426
|
+
const { identifiant_reseau_chaleur, date_arrete_reseau_chaleur } = gen_ch.donnee_entree;
|
|
427
|
+
|
|
428
|
+
if (identifiant_reseau_chaleur) {
|
|
429
|
+
return getContenuCo2ReseauChaleur(
|
|
430
|
+
identifiant_reseau_chaleur,
|
|
431
|
+
date_arrete_reseau_chaleur,
|
|
432
|
+
dateDpe,
|
|
433
|
+
versionLogiciel
|
|
434
|
+
);
|
|
360
435
|
}
|
|
436
|
+
// Prendre la valeur pour "AUTRES RESEAUX DE CHALEUR"
|
|
437
|
+
return COEF_GES_AUTRES_RESEAUX_CHALEUR;
|
|
361
438
|
}
|
|
362
439
|
|
|
363
440
|
return coef_ges[typeEnergie] ?? 1;
|
|
@@ -377,7 +454,8 @@ export default function calc_conso(
|
|
|
377
454
|
coeffEp,
|
|
378
455
|
nbLogements = 1,
|
|
379
456
|
ratioSurfaceCollectif = 1,
|
|
380
|
-
facteursAc = {}
|
|
457
|
+
facteursAc = {},
|
|
458
|
+
versionLogiciel = undefined
|
|
381
459
|
) {
|
|
382
460
|
const gen_ch = ch.reduce((acc, ch) => {
|
|
383
461
|
const generateur_chauffage = ch.generateur_chauffage_collection.generateur_chauffage;
|
|
@@ -393,7 +471,7 @@ export default function calc_conso(
|
|
|
393
471
|
ch.donnee_entree.enum_methode_calcul_conso_id;
|
|
394
472
|
value.donnee_entree.enum_type_installation_id = ch.donnee_entree.enum_type_installation_id;
|
|
395
473
|
(value.donnee_utilisateur = value.donnee_utilisateur || {}).coeffEmissionGes =
|
|
396
|
-
getGesCoeffForGenerateur(value, 'ch', dateDpe);
|
|
474
|
+
getGesCoeffForGenerateur(value, 'ch', dateDpe, versionLogiciel);
|
|
397
475
|
});
|
|
398
476
|
return acc.concat(generateur_chauffage);
|
|
399
477
|
}, []);
|
|
@@ -406,9 +484,9 @@ export default function calc_conso(
|
|
|
406
484
|
// S'il existe une clé de repartition ECS, utilisation de celle-ci pour répartir la conso ECS collective à l'appartement
|
|
407
485
|
generateur_ecs.forEach((value) => {
|
|
408
486
|
value.donnee_entree.cle_repartition_ecs =
|
|
409
|
-
(ecs.donnee_entree.cle_repartition_ecs || 1) * (ecs
|
|
487
|
+
(ecs.donnee_entree.cle_repartition_ecs || 1) * getRdimEcs(ecs);
|
|
410
488
|
(value.donnee_utilisateur = value.donnee_utilisateur || {}).coeffEmissionGes =
|
|
411
|
-
getGesCoeffForGenerateur(value, 'ecs', dateDpe);
|
|
489
|
+
getGesCoeffForGenerateur(value, 'ecs', dateDpe, versionLogiciel);
|
|
412
490
|
});
|
|
413
491
|
}
|
|
414
492
|
return acc.concat(generateur_ecs);
|
|
@@ -794,6 +872,19 @@ function getEcsConso(gen_ecs, field, coef, prorataECS, prefix, facteurAc = 1) {
|
|
|
794
872
|
}, 0);
|
|
795
873
|
}
|
|
796
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Rdim appliqué à la consommation d'une installation ECS lors de l'agrégation. Il vaut 1 lorsque le
|
|
877
|
+
* besoin n'a pas été divisé par rdim (installation ECS collective en immeuble, cf. 11_ecs.js
|
|
878
|
+
* isRdimEcsNeutralise), afin de rester cohérent avec le besoin calculé.
|
|
879
|
+
* @see Moteur_DPE.dll Calcul_batiment.cs l.1067 (conso × Rdim, Rdim = 1 transmis en collectif)
|
|
880
|
+
* @param inst {InstallationEcs}
|
|
881
|
+
* @returns {number}
|
|
882
|
+
*/
|
|
883
|
+
export function getRdimEcs(inst) {
|
|
884
|
+
if (inst.donnee_intermediaire?.rdim_neutralise) return 1;
|
|
885
|
+
return Number(inst.donnee_entree.rdim) || 1;
|
|
886
|
+
}
|
|
887
|
+
|
|
797
888
|
/**
|
|
798
889
|
* Consommation des auxiliaires de génération ECS (circulateurs des générateurs).
|
|
799
890
|
* Le besoin d'une installation dimensionnée (rdim > 1) est exprimé par unité : comme la
|
|
@@ -809,7 +900,7 @@ function getEcsConso(gen_ecs, field, coef, prorataECS, prefix, facteurAc = 1) {
|
|
|
809
900
|
*/
|
|
810
901
|
export function getAuxGenerationEcs(ecs, field, coef, prorataECS, facteurAc = 1) {
|
|
811
902
|
return ecs.reduce((acc, inst) => {
|
|
812
|
-
const rdim = prorataECS === 1 ?
|
|
903
|
+
const rdim = prorataECS === 1 ? getRdimEcs(inst) : 1;
|
|
813
904
|
return inst.generateur_ecs_collection.generateur_ecs.reduce((acc2, gen) => {
|
|
814
905
|
const conso = (gen.donnee_intermediaire[field] || 0) * rdim * facteurAc;
|
|
815
906
|
return acc2 + getConso(coef, 'électricité auxiliaire', conso);
|
package/conso.spec.js
CHANGED
|
@@ -37,13 +37,21 @@ vi.mock('./tv.js', () => ({
|
|
|
37
37
|
}
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
+
/** Mode bug_for_bug_compat piloté par les tests (export « live » du module utils mocké). */
|
|
41
|
+
const utilsState = vi.hoisted(() => ({ bugForBugCompat: false }));
|
|
42
|
+
|
|
43
|
+
/** Lecture des tables par défaut : réseau de chaleur "R1" connu, "R2" inconnu. */
|
|
44
|
+
const tvParDefaut = (table, matcher) => {
|
|
45
|
+
if (matcher?.identifiant_reseau === 'R1') return { contenu_co2_acv: 0.15 };
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
40
49
|
vi.mock('./utils.js', () => ({
|
|
41
50
|
set_bug_for_bug_compat: vi.fn(),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
51
|
+
get bug_for_bug_compat() {
|
|
52
|
+
return utilsState.bugForBugCompat;
|
|
53
|
+
},
|
|
54
|
+
tv: vi.fn((table, matcher) => tvParDefaut(table, matcher))
|
|
47
55
|
}));
|
|
48
56
|
|
|
49
57
|
const {
|
|
@@ -56,10 +64,16 @@ const {
|
|
|
56
64
|
coefCoutProrata,
|
|
57
65
|
masqueEnergie,
|
|
58
66
|
getAuxGenerationEcs,
|
|
67
|
+
getRdimEcs,
|
|
59
68
|
DATE_BAREME_COUT_2024,
|
|
60
69
|
appointBijonction,
|
|
61
|
-
coutBijonction
|
|
70
|
+
coutBijonction,
|
|
71
|
+
ARRETES_RESEAU_CHALEUR,
|
|
72
|
+
COEF_GES_AUTRES_RESEAUX_CHALEUR,
|
|
73
|
+
indexArreteReseauChaleur,
|
|
74
|
+
getContenuCo2ReseauChaleur
|
|
62
75
|
} = await import('./conso.js');
|
|
76
|
+
const { tv } = await import('./utils.js');
|
|
63
77
|
|
|
64
78
|
/**
|
|
65
79
|
* Classe énergétique (bilan DPE)
|
|
@@ -432,7 +446,7 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
432
446
|
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(150, 9);
|
|
433
447
|
});
|
|
434
448
|
|
|
435
|
-
test('réseau de chaleur inconnu (identifiant sans ligne) : erreur signalée', () => {
|
|
449
|
+
test('réseau de chaleur inconnu (identifiant sans ligne) : erreur signalée, facteur « autres réseaux » 0,385', () => {
|
|
436
450
|
const ch = [
|
|
437
451
|
installCh({ cle_repartition_ch: 1 }, [
|
|
438
452
|
genCh(
|
|
@@ -443,8 +457,10 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
443
457
|
])
|
|
444
458
|
];
|
|
445
459
|
|
|
446
|
-
calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
460
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
447
461
|
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('R2'));
|
|
462
|
+
// Plus de repli sur 1 kgCO2/kWh (GES = consommation) : 1000 * 0,385
|
|
463
|
+
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(385, 9);
|
|
448
464
|
});
|
|
449
465
|
|
|
450
466
|
test('réseau de chaleur sans identifiant : coefficient GES forfaitaire 0,385', () => {
|
|
@@ -459,8 +475,8 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
459
475
|
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(385, 9);
|
|
460
476
|
});
|
|
461
477
|
|
|
462
|
-
test("date de
|
|
463
|
-
// date_arrete_reseau_chaleur = Symbol
|
|
478
|
+
test("date d'arrêté réseau de chaleur invalide : aucune exception", () => {
|
|
479
|
+
// date_arrete_reseau_chaleur = Symbol : ignorée (repli sur la date du DPE)
|
|
464
480
|
const ch = [
|
|
465
481
|
installCh({ cle_repartition_ch: 1 }, [
|
|
466
482
|
genCh(
|
|
@@ -536,6 +552,20 @@ describe('calc_conso - agrégation des consommations', () => {
|
|
|
536
552
|
expect(res.ef_conso.conso_ecs).toBe(600);
|
|
537
553
|
});
|
|
538
554
|
|
|
555
|
+
test('ECS collective en immeuble, rdim neutralisé (11_ecs.js) : conso non remultipliée par rdim', () => {
|
|
556
|
+
const inst = installEcs({ cle_repartition_ecs: 2, rdim: 22 }, [
|
|
557
|
+
genEcs('1', { conso_ecs: 100, conso_ecs_depensier: 120 })
|
|
558
|
+
]);
|
|
559
|
+
inst.donnee_intermediaire.rdim_neutralise = true;
|
|
560
|
+
|
|
561
|
+
const res = calc_conso(100, 1, 1, [], [], [inst], [], 1, 1, DATE_DPE, coef_ep);
|
|
562
|
+
// clé = 2 × 1 (rdim neutralisé) => conso_ecs = 100 × 2
|
|
563
|
+
expect(inst.generateur_ecs_collection.generateur_ecs[0].donnee_entree.cle_repartition_ecs).toBe(
|
|
564
|
+
2
|
|
565
|
+
);
|
|
566
|
+
expect(res.ef_conso.conso_ecs).toBe(200);
|
|
567
|
+
});
|
|
568
|
+
|
|
539
569
|
test('ECS avec prorataECS différent de 1 : clé issue du prorata', () => {
|
|
540
570
|
// Quand prorataECS !== 1, la boucle ECS ne renseigne pas donnee_utilisateur :
|
|
541
571
|
// on la fournit en amont (coefficient GES déjà connu) pour refléter les données réelles.
|
|
@@ -976,6 +1006,14 @@ describe('getAuxGenerationEcs - auxiliaires de génération ECS et rdim', () =>
|
|
|
976
1006
|
expect(getAuxGenerationEcs(ecs, 'conso_auxiliaire_generation_ecs', coefUnitaire, 1)).toBe(4);
|
|
977
1007
|
});
|
|
978
1008
|
|
|
1009
|
+
test('installation ECS collective en immeuble (rdim neutralisé) : auxiliaires non multipliés', () => {
|
|
1010
|
+
const collective = inst(22, 4);
|
|
1011
|
+
collective.donnee_intermediaire.rdim_neutralise = true;
|
|
1012
|
+
const ecs = [collective, inst(3, 1)];
|
|
1013
|
+
// 4 × 1 (rdim neutralisé) + 1 × 3
|
|
1014
|
+
expect(getAuxGenerationEcs(ecs, 'conso_auxiliaire_generation_ecs', coefUnitaire, 1)).toBe(7);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
979
1017
|
test('calc_conso : auxiliaire_generation_ecs intègre le rdim', () => {
|
|
980
1018
|
const ecs = [inst(2, 5)];
|
|
981
1019
|
const res = calc_conso(100, 1, 1, [], [], ecs, [], 1, 1, DATE_DPE, coef_ep);
|
|
@@ -1000,6 +1038,23 @@ describe('getAuxGenerationEcs - auxiliaires de génération ECS et rdim', () =>
|
|
|
1000
1038
|
});
|
|
1001
1039
|
});
|
|
1002
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Rdim appliqué à la conso ECS lors de l'agrégation : 1 si le besoin n'a pas été divisé par rdim.
|
|
1043
|
+
* @see Moteur_DPE.dll Calcul_batiment.cs l.1067
|
|
1044
|
+
*/
|
|
1045
|
+
describe('getRdimEcs', () => {
|
|
1046
|
+
test.each([
|
|
1047
|
+
['rdim renseigné', { rdim: 3 }, {}, 3],
|
|
1048
|
+
['rdim chaîne', { rdim: '2.5' }, {}, 2.5],
|
|
1049
|
+
['rdim absent', {}, {}, 1],
|
|
1050
|
+
['rdim non numérique', { rdim: 'abc' }, {}, 1],
|
|
1051
|
+
['rdim neutralisé', { rdim: 22 }, { rdim_neutralise: true }, 1],
|
|
1052
|
+
['sans donnée intermédiaire', { rdim: 4 }, undefined, 4]
|
|
1053
|
+
])('%s', (_libelle, de, di, attendu) => {
|
|
1054
|
+
expect(getRdimEcs({ donnee_entree: de, donnee_intermediaire: di })).toBe(attendu);
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1003
1058
|
/**
|
|
1004
1059
|
* Convecteurs bi-jonction (§9.1.6) : base (60 %) facturée en électricité collective, appoint
|
|
1005
1060
|
* individuel (40 %) en électricité individuelle.
|
|
@@ -1169,3 +1224,166 @@ describe('appointBijonction / coutBijonction', () => {
|
|
|
1169
1224
|
);
|
|
1170
1225
|
});
|
|
1171
1226
|
});
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Facteur d'émission des réseaux de chaleur urbains : table de l'arrêté utilisé
|
|
1230
|
+
* (modele_donnee/arrete_reseau_chaleur.json de l'observatoire DPE).
|
|
1231
|
+
*/
|
|
1232
|
+
describe('réseaux de chaleur - sélection de l’arrêté et du contenu CO2', () => {
|
|
1233
|
+
let errorSpy;
|
|
1234
|
+
|
|
1235
|
+
beforeEach(() => {
|
|
1236
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
1237
|
+
vi.mocked(tv).mockReset();
|
|
1238
|
+
utilsState.bugForBugCompat = false;
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
afterEach(() => {
|
|
1242
|
+
errorSpy.mockRestore();
|
|
1243
|
+
vi.mocked(tv).mockImplementation(tvParDefaut);
|
|
1244
|
+
utilsState.bugForBugCompat = false;
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
/** Tables mockées : identifiant -> { table: ligne } */
|
|
1248
|
+
const mockTables = (lignes) =>
|
|
1249
|
+
vi
|
|
1250
|
+
.mocked(tv)
|
|
1251
|
+
.mockImplementation((table, matcher) => lignes[matcher.identifiant_reseau]?.[table] ?? null);
|
|
1252
|
+
|
|
1253
|
+
test('six arrêtés, dans l’ordre chronologique, table ≠ année de l’arrêté', () => {
|
|
1254
|
+
expect(ARRETES_RESEAU_CHALEUR.map((a) => a.table)).toEqual([
|
|
1255
|
+
'reseau_chaleur_2020',
|
|
1256
|
+
'reseau_chaleur_2021',
|
|
1257
|
+
'reseau_chaleur_2022',
|
|
1258
|
+
'reseau_chaleur_2023',
|
|
1259
|
+
'reseau_chaleur_2024',
|
|
1260
|
+
'reseau_chaleur_2025'
|
|
1261
|
+
]);
|
|
1262
|
+
// L'arrêté du 05/07/2024 publie la table 2023 (cause du bug : 2024 - 1 = 2023 forcé à 2022)
|
|
1263
|
+
expect(ARRETES_RESEAU_CHALEUR[3]).toMatchObject({
|
|
1264
|
+
table: 'reseau_chaleur_2023',
|
|
1265
|
+
date_arrete: '2024-07-05'
|
|
1266
|
+
});
|
|
1267
|
+
const dates = ARRETES_RESEAU_CHALEUR.map((a) => a.date_arrete);
|
|
1268
|
+
expect([...dates].sort()).toEqual(dates);
|
|
1269
|
+
ARRETES_RESEAU_CHALEUR.forEach((a) => expect(a.date_application >= a.date_arrete).toBe(true));
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
test.each([
|
|
1273
|
+
['2020-10-12', 0],
|
|
1274
|
+
['2021-10-21', 1],
|
|
1275
|
+
['2022-01-18', 1], // date hors liste : dernier arrêté publié avant
|
|
1276
|
+
['2023-03-16', 2],
|
|
1277
|
+
['2024-07-05', 3],
|
|
1278
|
+
['2024-07-10', 3],
|
|
1279
|
+
['2025-04-11', 4],
|
|
1280
|
+
['2026-04-25', 5],
|
|
1281
|
+
['2030-01-01', 5],
|
|
1282
|
+
['2019-01-01', 0]
|
|
1283
|
+
])('date_arrete_reseau_chaleur %s : arrêté n°%i', (dateArrete, index) => {
|
|
1284
|
+
// La date du DPE est ignorée quand la date d'arrêté est renseignée
|
|
1285
|
+
expect(indexArreteReseauChaleur(dateArrete, '2030-01-01')).toBe(index);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
test.each([
|
|
1289
|
+
['2022-01-17', 0],
|
|
1290
|
+
['2022-01-18', 1],
|
|
1291
|
+
['2023-04-15', 1], // arrêté du 16/03/2023 applicable au 16/04/2023 seulement
|
|
1292
|
+
['2023-04-16', 2],
|
|
1293
|
+
['2024-08-09', 3],
|
|
1294
|
+
[undefined, 0],
|
|
1295
|
+
['date invalide', 0]
|
|
1296
|
+
])('sans date d’arrêté, DPE du %s : arrêté applicable n°%i', (dateDpe, index) => {
|
|
1297
|
+
expect(indexArreteReseauChaleur(undefined, dateDpe)).toBe(index);
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test('date d’arrêté illisible (non chaîne) : repli sur la date du DPE', () => {
|
|
1301
|
+
expect(indexArreteReseauChaleur(Symbol('invalide'), '2024-08-09')).toBe(3);
|
|
1302
|
+
expect(indexArreteReseauChaleur(new Date('2024-07-05'), undefined)).toBe(3);
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
test('arrêté du 05/07/2024 : lecture de reseau_chaleur_2023 et contenu ACV (cas Pau 6403C)', () => {
|
|
1306
|
+
mockTables({
|
|
1307
|
+
'6403C': { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
|
|
1308
|
+
});
|
|
1309
|
+
expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', 'Pleiades')).toBe(0.061);
|
|
1310
|
+
expect(tv).toHaveBeenCalledTimes(1);
|
|
1311
|
+
expect(tv).toHaveBeenCalledWith('reseau_chaleur_2023', { identifiant_reseau: '6403C' });
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
test('bug_for_bug_compat + LICIEL + table 2023 : contenu CO2 hors ACV transmis par le logiciel', () => {
|
|
1315
|
+
utilsState.bugForBugCompat = true;
|
|
1316
|
+
mockTables({
|
|
1317
|
+
'6403C': {
|
|
1318
|
+
reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' },
|
|
1319
|
+
reseau_chaleur_2022: { contenu_co2: '0.1', contenu_co2_acv: '0.2' }
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
const liciel = 'LICIEL Diagnostics v4 [Version XML:305]';
|
|
1323
|
+
expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', liciel)).toBe(0.047);
|
|
1324
|
+
// Autre logiciel : valeur ACV
|
|
1325
|
+
expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', 'DPEWIN V5.2.5')).toBe(
|
|
1326
|
+
0.061
|
|
1327
|
+
);
|
|
1328
|
+
expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', undefined)).toBe(0.061);
|
|
1329
|
+
// LICIEL avec un autre arrêté (16/03/2023 -> table 2022) : valeur ACV
|
|
1330
|
+
expect(getContenuCo2ReseauChaleur('6403C', '2023-03-16', '2023-06-01', liciel)).toBe(0.2);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
test('sans bug_for_bug_compat, LICIEL lit aussi le contenu ACV', () => {
|
|
1334
|
+
mockTables({
|
|
1335
|
+
R: { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
|
|
1336
|
+
});
|
|
1337
|
+
expect(getContenuCo2ReseauChaleur('R', '2024-07-05', undefined, 'LICIEL')).toBe(0.061);
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
test('réseau absent de la table de l’arrêté : table antérieure la plus récente qui le contient', () => {
|
|
1341
|
+
mockTables({
|
|
1342
|
+
R: {
|
|
1343
|
+
reseau_chaleur_2021: { contenu_co2: '0.3', contenu_co2_acv: '0.33' },
|
|
1344
|
+
reseau_chaleur_2020: { contenu_co2: '0.4', contenu_co2_acv: '0.44' }
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
expect(getContenuCo2ReseauChaleur('R', '2024-07-05', undefined, undefined)).toBe(0.33);
|
|
1348
|
+
expect(vi.mocked(tv).mock.calls.map((c) => c[0])).toEqual([
|
|
1349
|
+
'reseau_chaleur_2023',
|
|
1350
|
+
'reseau_chaleur_2022',
|
|
1351
|
+
'reseau_chaleur_2021'
|
|
1352
|
+
]);
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
test('réseau introuvable dans toutes les tables : facteur « autres réseaux de chaleur » 0,385 et erreur', () => {
|
|
1356
|
+
mockTables({});
|
|
1357
|
+
expect(COEF_GES_AUTRES_RESEAUX_CHALEUR).toBe(0.385);
|
|
1358
|
+
expect(getContenuCo2ReseauChaleur('XXXX', '2024-07-05', undefined, undefined)).toBe(0.385);
|
|
1359
|
+
expect(tv).toHaveBeenCalledTimes(4);
|
|
1360
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('XXXX'));
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
test('calc_conso : facteur réseau appliqué au chauffage et à l’ECS, version logiciel transmise', () => {
|
|
1364
|
+
utilsState.bugForBugCompat = true;
|
|
1365
|
+
mockTables({
|
|
1366
|
+
'6403C': { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
|
|
1367
|
+
});
|
|
1368
|
+
const de = { identifiant_reseau_chaleur: '6403C', date_arrete_reseau_chaleur: '2024-07-05' };
|
|
1369
|
+
const ch = [
|
|
1370
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
1371
|
+
genCh('3', { conso_ch: 1000, conso_ch_depensier: 1200 }, de)
|
|
1372
|
+
])
|
|
1373
|
+
];
|
|
1374
|
+
const ecs = [
|
|
1375
|
+
installEcs({ cle_repartition_ecs: 1 }, [
|
|
1376
|
+
genEcs('3', { conso_ecs: 500, conso_ecs_depensier: 600 }, de)
|
|
1377
|
+
])
|
|
1378
|
+
];
|
|
1379
|
+
const args = [100, 1, 1, [], ch, ecs, [], 1, 1, '2025-01-24', coef_ep, 1, 1, {}];
|
|
1380
|
+
|
|
1381
|
+
const licielRes = calc_conso(...args, 'LICIEL Diagnostics v4');
|
|
1382
|
+
// 1000 * 0,047 et 500 * 0,047 (valeur ADEME du DPE 2564E0255485Z) — avant : 1000 * 1
|
|
1383
|
+
expect(licielRes.emission_ges.emission_ges_ch).toBeCloseTo(47, 9);
|
|
1384
|
+
expect(licielRes.emission_ges.emission_ges_ecs).toBeCloseTo(23.5, 9);
|
|
1385
|
+
|
|
1386
|
+
const autreRes = calc_conso(...args);
|
|
1387
|
+
expect(autreRes.emission_ges.emission_ges_ch).toBeCloseTo(61, 9);
|
|
1388
|
+
});
|
|
1389
|
+
});
|
package/engine.js
CHANGED
|
@@ -33,7 +33,7 @@ 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
|
|
|
36
|
-
const LIB_VERSION = '1.8.
|
|
36
|
+
const LIB_VERSION = '1.8.52';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -85,6 +85,7 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
85
85
|
}
|
|
86
86
|
const modele = enums.modele_dpe[dpe.administratif.enum_modele_dpe_id];
|
|
87
87
|
const dateDpe = dpe.administratif.date_etablissement_dpe;
|
|
88
|
+
const versionLogiciel = dpe.administratif.diagnostiqueur?.version_logiciel;
|
|
88
89
|
if (modele !== 'dpe 3cl 2021 méthode logement') {
|
|
89
90
|
console.error('Moteur dpe non implémenté pour le modèle: ' + modele);
|
|
90
91
|
return null;
|
|
@@ -606,7 +607,9 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
606
607
|
dateDpe,
|
|
607
608
|
coef_ep,
|
|
608
609
|
th === 'immeuble' ? Nb_lgt : 1,
|
|
609
|
-
ratioSurfaceCoutCollectif
|
|
610
|
+
ratioSurfaceCoutCollectif,
|
|
611
|
+
{},
|
|
612
|
+
versionLogiciel
|
|
610
613
|
);
|
|
611
614
|
|
|
612
615
|
// Proratisation au logement d'une installation PV d'immeuble (appartement)
|
|
@@ -648,7 +651,8 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
648
651
|
coef_ep,
|
|
649
652
|
th === 'immeuble' ? Nb_lgt : 1,
|
|
650
653
|
ratioSurfaceCoutCollectif,
|
|
651
|
-
facteursAc
|
|
654
|
+
facteursAc,
|
|
655
|
+
versionLogiciel
|
|
652
656
|
);
|
|
653
657
|
conso.emission_ges = consoNette.emission_ges;
|
|
654
658
|
// Ligne électricité de sortie_par_energie nette de l'autoconsommation (issue #34)
|
|
@@ -669,7 +673,9 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
669
673
|
dateDpe,
|
|
670
674
|
coef_ep_1_7,
|
|
671
675
|
th === 'immeuble' ? Nb_lgt : 1,
|
|
672
|
-
ratioSurfaceCoutCollectif
|
|
676
|
+
ratioSurfaceCoutCollectif,
|
|
677
|
+
{},
|
|
678
|
+
versionLogiciel
|
|
673
679
|
);
|
|
674
680
|
|
|
675
681
|
productionENR.calculateEnr(
|
|
@@ -695,7 +701,9 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
695
701
|
dateDpe,
|
|
696
702
|
coef_ep_2_3,
|
|
697
703
|
th === 'immeuble' ? Nb_lgt : 1,
|
|
698
|
-
ratioSurfaceCoutCollectif
|
|
704
|
+
ratioSurfaceCoutCollectif,
|
|
705
|
+
{},
|
|
706
|
+
versionLogiciel
|
|
699
707
|
);
|
|
700
708
|
|
|
701
709
|
productionENR.calculateEnr(
|
package/engine.spec.js
CHANGED
|
@@ -518,6 +518,24 @@ describe('calcul_3cl - run intégral (maison)', () => {
|
|
|
518
518
|
expect(dpe.logement.sortie.cout).toEqual({ cout_5_usages: 2 });
|
|
519
519
|
});
|
|
520
520
|
|
|
521
|
+
test('version du logiciel de saisie transmise à tous les calculs de conso (facteur GES réseau de chaleur)', () => {
|
|
522
|
+
const dpe = makeDpe();
|
|
523
|
+
dpe.administratif.diagnostiqueur = { version_logiciel: 'LICIEL Diagnostics v4' };
|
|
524
|
+
const facteurs = { ch: 0.8 };
|
|
525
|
+
vi.mocked(calculateEnr).mockImplementationOnce((enr, conso, Sh, th, zc, coef, ratio, f) => {
|
|
526
|
+
Object.assign(f, facteurs);
|
|
527
|
+
return { pv: 1 };
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
calcul_3cl(dpe, { sanitize: false });
|
|
531
|
+
|
|
532
|
+
// Lue avant l'écrasement de `diagnostiqueur` par la version du moteur
|
|
533
|
+
expect(calc_conso).toHaveBeenCalledTimes(4);
|
|
534
|
+
calc_conso.mock.calls.forEach((call) => expect(call[14]).toBe('LICIEL Diagnostics v4'));
|
|
535
|
+
expect(calc_conso.mock.calls[0][13]).toEqual({});
|
|
536
|
+
expect(calc_conso.mock.calls[1][13]).toEqual(facteurs);
|
|
537
|
+
});
|
|
538
|
+
|
|
521
539
|
test('bâtiment matériaux anciens => ilpa = 1 ; incohérence inertie => erreur loggée', () => {
|
|
522
540
|
const dpe = makeDpe();
|
|
523
541
|
dpe.logement.meteo.batiment_materiaux_anciens = 1;
|