@open3cl/engine 1.8.63 → 1.8.64
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_stockage_ecs_volume_chauffe.js +31 -0
- package/11_stockage_ecs_volume_chauffe.spec.js +35 -0
- package/9_besoin_ch.js +2 -4
- package/9_besoin_ch.spec.js +58 -1
- package/README.md +18 -18
- package/engine.js +1 -1
- package/features/engine/domain/ecs/installation-ecs.service.js +3 -5
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le stockage ECS d'un générateur est-il en volume chauffé ?
|
|
3
|
+
*
|
|
4
|
+
* Tribu porte l'information au niveau de l'installation (`installation_ECS.Is_volume_chauffee`,
|
|
5
|
+
* Calcul_batiment.cs l.375-377). Dans l'export ADEME, elle se lit, par ordre de priorité :
|
|
6
|
+
* 1. `position_volume_chauffe_stockage` du générateur, s'il est renseigné ;
|
|
7
|
+
* 2. `tv_rendement_distribution_ecs_id` de l'installation : 1 ou 2 (production en volume
|
|
8
|
+
* habitable) → oui, 3 (production hors volume habitable) → non ;
|
|
9
|
+
* 3. `position_volume_chauffe` du générateur.
|
|
10
|
+
*
|
|
11
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §9.1.1 et §11.3 (rendement de distribution)
|
|
12
|
+
* @param deGenerateur {object} donnee_entree du générateur ECS
|
|
13
|
+
* @param deInstallation {object} donnee_entree de l'installation ECS
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
export function isStockageEcsVolumeChauffe(deGenerateur, deInstallation) {
|
|
17
|
+
const positionStockage = Number.parseInt(deGenerateur.position_volume_chauffe_stockage);
|
|
18
|
+
if (positionStockage === 0 || positionStockage === 1) {
|
|
19
|
+
return positionStockage === 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rdId = Number.parseInt(deInstallation?.tv_rendement_distribution_ecs_id);
|
|
23
|
+
if (rdId === 1 || rdId === 2) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (rdId === 3) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Number.parseInt(deGenerateur.position_volume_chauffe) !== 0;
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { isStockageEcsVolumeChauffe } from './11_stockage_ecs_volume_chauffe.js';
|
|
3
|
+
|
|
4
|
+
describe('isStockageEcsVolumeChauffe - stockage ECS en volume chauffé (#293)', () => {
|
|
5
|
+
/**
|
|
6
|
+
* Tribu : installation_ECS.Is_volume_chauffee (Calcul_batiment.cs l.375-377), lue dans l'export
|
|
7
|
+
* par position_volume_chauffe_stockage, puis tv_rendement_distribution_ecs_id, puis
|
|
8
|
+
* position_volume_chauffe.
|
|
9
|
+
*/
|
|
10
|
+
test.each([
|
|
11
|
+
// [position stockage, rendement distribution, position générateur, attendu]
|
|
12
|
+
[1, '3', 0, true], // stockage renseigné prioritaire (oui)
|
|
13
|
+
[0, '1', 1, false], // stockage renseigné prioritaire (non)
|
|
14
|
+
['1', undefined, 0, true], // valeur texte acceptée
|
|
15
|
+
[undefined, '1', 0, true], // production en volume habitable
|
|
16
|
+
[undefined, 2, 0, true], // production en volume habitable, pièces non contiguës
|
|
17
|
+
[undefined, '3', 1, false], // production hors volume habitable
|
|
18
|
+
[undefined, '4', 0, false], // ligne collective : repli sur la position du générateur
|
|
19
|
+
[undefined, undefined, 1, true], // repli : générateur en volume chauffé
|
|
20
|
+
[undefined, undefined, 0, false], // repli : générateur hors volume chauffé
|
|
21
|
+
[undefined, undefined, undefined, true], // aucune information : récupération conservée
|
|
22
|
+
[null, null, 0, false] // champs nuls traités comme absents
|
|
23
|
+
])('stockage %s, rd %s, générateur %s => %s', (pvcs, rd, pvc, attendu) => {
|
|
24
|
+
expect(
|
|
25
|
+
isStockageEcsVolumeChauffe(
|
|
26
|
+
{ position_volume_chauffe_stockage: pvcs, position_volume_chauffe: pvc },
|
|
27
|
+
{ tv_rendement_distribution_ecs_id: rd }
|
|
28
|
+
)
|
|
29
|
+
).toBe(attendu);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('installation absente : repli sur la position du générateur', () => {
|
|
33
|
+
expect(isStockageEcsVolumeChauffe({ position_volume_chauffe: 0 }, undefined)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
package/9_besoin_ch.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { calc_ai_j, calc_as_j } from './6.1_apport_gratuit.js';
|
|
10
10
|
import { calc_sse_j } from './6.2_surface_sud_equivalente.js';
|
|
11
11
|
import { bug_for_bug_compat, mois_liste } from './utils.js';
|
|
12
|
+
import { isStockageEcsVolumeChauffe } from './11_stockage_ecs_volume_chauffe.js';
|
|
12
13
|
|
|
13
14
|
export default function calc_besoin_ch(
|
|
14
15
|
ilpa,
|
|
@@ -67,10 +68,7 @@ export default function calc_besoin_ch(
|
|
|
67
68
|
} else {
|
|
68
69
|
Qgw = gen_ecs.reduce((acc, gen_ecs) => {
|
|
69
70
|
// Pas de récupération de stockage si le ballon est hors volume chauffé
|
|
70
|
-
if (
|
|
71
|
-
gen_ecs.donnee_entree.position_volume_chauffe_stockage === 0 ||
|
|
72
|
-
gen_ecs.donnee_entree.position_volume_chauffe === 0
|
|
73
|
-
) {
|
|
71
|
+
if (!isStockageEcsVolumeChauffe(gen_ecs.donnee_entree, instal_ecs.donnee_entree)) {
|
|
74
72
|
return acc;
|
|
75
73
|
}
|
|
76
74
|
|
package/9_besoin_ch.spec.js
CHANGED
|
@@ -313,7 +313,7 @@ describe('calc_besoin_ch - branches complémentaires', () => {
|
|
|
313
313
|
donnee_intermediaire: { Qgw: 100 }
|
|
314
314
|
},
|
|
315
315
|
{
|
|
316
|
-
// générateur hors volume chauffé =>
|
|
316
|
+
// générateur hors volume chauffé mais stockage en volume chauffé => compté (#293)
|
|
317
317
|
donnee_entree: { position_volume_chauffe_stockage: 1, position_volume_chauffe: 0 },
|
|
318
318
|
donnee_intermediaire: { Qgw: 100 }
|
|
319
319
|
},
|
|
@@ -660,3 +660,60 @@ describe('calc_besoin_ch - immeuble à installations ECS individuelles et collec
|
|
|
660
660
|
expect(ret.pertes_distribution_ecs_recup).toBeCloseTo((0.48 * 100 * total) / 8760, 9);
|
|
661
661
|
});
|
|
662
662
|
});
|
|
663
|
+
|
|
664
|
+
describe('calc_besoin_ch - stockage ECS en volume chauffé (#293)', () => {
|
|
665
|
+
/**
|
|
666
|
+
* Cas des exemples de l'issue (2301E2484046J, 2322E2765016S) : ballon électrique LICIEL
|
|
667
|
+
* exporté avec position_volume_chauffe = 0 mais production en volume habitable (rd 1).
|
|
668
|
+
*/
|
|
669
|
+
test('ballon en volume habitable exporté avec position_volume_chauffe = 0 : pertes récupérées', () => {
|
|
670
|
+
utilState.bug = false;
|
|
671
|
+
vi.mocked(calc_ai_j).mockReset().mockReturnValue(5000);
|
|
672
|
+
vi.mocked(calc_as_j).mockReset().mockReturnValue(2000);
|
|
673
|
+
vi.mocked(calc_sse_j).mockReset().mockReturnValue(10);
|
|
674
|
+
vi.mocked(calc_besoin_ecs_j).mockReset().mockReturnValue(10);
|
|
675
|
+
vi.mocked(calc_Qrec_gen_j).mockReset().mockReturnValue(0);
|
|
676
|
+
const instal = (rd) => [
|
|
677
|
+
{
|
|
678
|
+
donnee_entree: { enum_type_installation_id: '1', tv_rendement_distribution_ecs_id: rd },
|
|
679
|
+
generateur_ecs_collection: {
|
|
680
|
+
generateur_ecs: [
|
|
681
|
+
{ donnee_entree: { position_volume_chauffe: 0 }, donnee_intermediaire: { Qgw: 8760 } }
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
const enVolume = calc_besoin_ch(
|
|
687
|
+
0,
|
|
688
|
+
0,
|
|
689
|
+
0,
|
|
690
|
+
0,
|
|
691
|
+
100,
|
|
692
|
+
100,
|
|
693
|
+
3,
|
|
694
|
+
instal('1'),
|
|
695
|
+
[],
|
|
696
|
+
[],
|
|
697
|
+
null,
|
|
698
|
+
'maison',
|
|
699
|
+
1
|
|
700
|
+
);
|
|
701
|
+
const horsVolume = calc_besoin_ch(
|
|
702
|
+
0,
|
|
703
|
+
0,
|
|
704
|
+
0,
|
|
705
|
+
0,
|
|
706
|
+
100,
|
|
707
|
+
100,
|
|
708
|
+
3,
|
|
709
|
+
instal('3'),
|
|
710
|
+
[],
|
|
711
|
+
[],
|
|
712
|
+
null,
|
|
713
|
+
'maison',
|
|
714
|
+
1
|
|
715
|
+
);
|
|
716
|
+
expect(enVolume.pertes_stockage_ecs_recup).toBeGreaterThan(0);
|
|
717
|
+
expect(horsVolume.pertes_stockage_ecs_recup).toBe(0);
|
|
718
|
+
});
|
|
719
|
+
});
|
package/README.md
CHANGED
|
@@ -375,15 +375,15 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
375
375
|
|
|
376
376
|
<!-- CORPUS:START -->
|
|
377
377
|
|
|
378
|
-
> **Version `1.8.
|
|
378
|
+
> **Version `1.8.63`** · branche `main` · généré le 2026-10-15
|
|
379
379
|
> Seuil de tolérance **5%**
|
|
380
380
|
|
|
381
381
|
<table>
|
|
382
382
|
<tr>
|
|
383
383
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
384
384
|
<td align="center"><strong>89 418</strong><br/><sub>DPE analysés</sub></td>
|
|
385
|
-
<td align="center"><strong>71
|
|
386
|
-
<td align="center"><strong>79,
|
|
385
|
+
<td align="center"><strong>71 098</strong><br/><sub>DPE conformes</sub></td>
|
|
386
|
+
<td align="center"><strong>79,51 %</strong><br/><sub>réussite globale</sub></td>
|
|
387
387
|
</tr>
|
|
388
388
|
</table>
|
|
389
389
|
|
|
@@ -392,11 +392,11 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
392
392
|
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **47,82 %** | `██████████░░░░░░░░░░` | 4 782 / 9 999 |
|
|
393
393
|
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **93,94 %** | `███████████████████░` | 9 394 / 10 000 |
|
|
394
394
|
| 🟢 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **93,64 %** | `███████████████████░` | 9 364 / 10 000 |
|
|
395
|
-
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **91,
|
|
395
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **91,57 %** | `██████████████████░░` | 9 150 / 9 992 |
|
|
396
396
|
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **89,44 %** | `██████████████████░░` | 8 943 / 9 999 |
|
|
397
397
|
| 🟢 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **88,20 %** | `██████████████████░░` | 8 656 / 9 814 |
|
|
398
398
|
| 🟢 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **85,14 %** | `█████████████████░░░` | 8 512 / 9 998 |
|
|
399
|
-
| 🟡 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **69,
|
|
399
|
+
| 🟡 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **69,53 %** | `██████████████░░░░░░` | 6 686 / 9 616 |
|
|
400
400
|
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **56,11 %** | `███████████░░░░░░░░░` | 5 611 / 10 000 |
|
|
401
401
|
|
|
402
402
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
@@ -408,24 +408,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
408
408
|
|
|
409
409
|
<table>
|
|
410
410
|
<tr>
|
|
411
|
-
<td align="center"><strong>
|
|
412
|
-
<td align="center"><strong>5,
|
|
413
|
-
<td align="center"><strong>0,
|
|
414
|
-
<td align="center"><strong>
|
|
411
|
+
<td align="center"><strong>6,64 ms</strong><br/><sub>moyenne</sub></td>
|
|
412
|
+
<td align="center"><strong>5,83 ms</strong><br/><sub>médiane</sub></td>
|
|
413
|
+
<td align="center"><strong>0,13 ms</strong><br/><sub>min</sub></td>
|
|
414
|
+
<td align="center"><strong>759,2 ms</strong><br/><sub>max</sub></td>
|
|
415
415
|
</tr>
|
|
416
416
|
</table>
|
|
417
417
|
|
|
418
418
|
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
419
419
|
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
420
|
-
| **Généraliste** |
|
|
421
|
-
| **Appartement · chauffage individuel (2025)** | 5,
|
|
422
|
-
| **Appartement · chauffage collectif (2025)** |
|
|
423
|
-
| **Logement individuel (2025)** |
|
|
424
|
-
| **Maison individuelle (2025)** |
|
|
425
|
-
| **Immeuble · chauffage collectif** |
|
|
426
|
-
| **Immeuble · chauffage individuel** |
|
|
427
|
-
| **Immeuble · chauffage mixte** |
|
|
428
|
-
| **Individuel généré depuis l'immeuble (2026)** |
|
|
420
|
+
| **Généraliste** | 5,27 ms | 4,83 ms | 0,23 ms | 38,0 ms | 9,31 ms | 12,8 ms |
|
|
421
|
+
| **Appartement · chauffage individuel (2025)** | 5,05 ms | 4,61 ms | 1,84 ms | 39,8 ms | 8,69 ms | 11,1 ms |
|
|
422
|
+
| **Appartement · chauffage collectif (2025)** | 5,64 ms | 5,11 ms | 1,92 ms | 58,6 ms | 9,35 ms | 14,6 ms |
|
|
423
|
+
| **Logement individuel (2025)** | 6,17 ms | 5,54 ms | 2,13 ms | 48,5 ms | 11,3 ms | 15,3 ms |
|
|
424
|
+
| **Maison individuelle (2025)** | 7,18 ms | 6,49 ms | 2,29 ms | 52,4 ms | 12,7 ms | 16,4 ms |
|
|
425
|
+
| **Immeuble · chauffage collectif** | 7,20 ms | 6,19 ms | 0,24 ms | 140,8 ms | 13,4 ms | 22,0 ms |
|
|
426
|
+
| **Immeuble · chauffage individuel** | 8,44 ms | 7,21 ms | 0,13 ms | 115,0 ms | 17,0 ms | 27,1 ms |
|
|
427
|
+
| **Immeuble · chauffage mixte** | 7,18 ms | 5,88 ms | 0,23 ms | 759,2 ms | 15,3 ms | 24,7 ms |
|
|
428
|
+
| **Individuel généré depuis l'immeuble (2026)** | 7,70 ms | 6,63 ms | 2,02 ms | 81,6 ms | 14,5 ms | 24,0 ms |
|
|
429
429
|
|
|
430
430
|
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
431
431
|
dizaines de fois la médiane : c’est la médiane qui décrit le cas courant, et p95/p99 le cas
|
package/engine.js
CHANGED
|
@@ -35,7 +35,7 @@ import DpeSanitizerService from './dpe-sanitizer.service.js';
|
|
|
35
35
|
import { reconstituerRdimDpeGenereImmeuble } from './17.2_rdim_dpe_genere_immeuble.js';
|
|
36
36
|
import { reconstituerRdimEcsIndividuelleUnique } from './11_rdim_ecs_individuelle_unique.js';
|
|
37
37
|
|
|
38
|
-
const LIB_VERSION = '1.8.
|
|
38
|
+
const LIB_VERSION = '1.8.64';
|
|
39
39
|
|
|
40
40
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
41
41
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GenerateurEcsService } from './generateur-ecs.service.js';
|
|
2
2
|
import { inject } from 'dioma';
|
|
3
|
+
import { isStockageEcsVolumeChauffe } from '../../../../11_stockage_ecs_volume_chauffe.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Calcul des besoins et pertes des installations ECS
|
|
@@ -142,11 +143,8 @@ export class InstallationEcsService {
|
|
|
142
143
|
if (installationIndividuelle) {
|
|
143
144
|
installationEcs.donnee_utilisateur.QgwRecuperable =
|
|
144
145
|
installationEcs.generateur_ecs_collection?.generateur_ecs
|
|
145
|
-
.filter(
|
|
146
|
-
(generateurEcs)
|
|
147
|
-
parseInt(generateurEcs.donnee_entree.position_volume_chauffe_stockage) === 1 ||
|
|
148
|
-
(generateurEcs.donnee_entree.position_volume_chauffe_stockage === undefined &&
|
|
149
|
-
parseInt(generateurEcs.donnee_entree.position_volume_chauffe) === 1)
|
|
146
|
+
.filter((generateurEcs) =>
|
|
147
|
+
isStockageEcsVolumeChauffe(generateurEcs.donnee_entree, installationEcsDE)
|
|
150
148
|
)
|
|
151
149
|
.reduce((acc, generateurEcs) => {
|
|
152
150
|
return acc + generateurEcs.donnee_utilisateur.Qgw;
|