@open3cl/engine 1.8.44 → 1.8.46

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.
@@ -218,6 +218,14 @@ export function rgrsReseauUrbain(de, ecs_de) {
218
218
  if (de.enum_type_generateur_ecs_id === '73') {
219
219
  return 0.9;
220
220
  }
221
+ /**
222
+ * Type 72 explicitement non isolé : 0.75, quel que soit reseau_distribution_isole (qui porte sur
223
+ * le réseau de distribution ECS, pas sur l'équipement du réseau de chaleur). Moteur de référence
224
+ * CSTB : Calcul_installation_ECS.cs, isolation_equipement_reseau = installation_non_isoles -> 0.75.
225
+ */
226
+ if (de.enum_type_generateur_ecs_id === '72') {
227
+ return 0.75;
228
+ }
221
229
  /**
222
230
  * Pour les autres générateurs assimilés à un réseau de chaleur (ex : installation
223
231
  * collective multi-bâtiment modélisée comme un réseau de chaleur), on retombe sur
@@ -346,8 +354,23 @@ export default function calc_gen_ecs(dpe, gen_ecs, ecs_di, ecs_de, GV, ca_id, zc
346
354
  }
347
355
  }
348
356
 
349
- di.rendement_generation_stockage = rgrsReseauUrbain(de, ecs_de);
350
- di.rendement_generation_stockage_depensier = rgrsReseauUrbain(de, ecs_de);
357
+ const rgSaisi = di.rendement_generation_stockage;
358
+ let rgReseau = rgrsReseauUrbain(de, ecs_de);
359
+
360
+ /**
361
+ * Type 72 (réseau non isolé) exporté avec 0.9 : le logiciel n'a pas transmis
362
+ * isolation_equipement_reseau = installation_non_isoles au moteur de référence, qui applique alors
363
+ * 0.9 (Tribu Calcul_installation_ECS.cs, cas reseau_de_chaleur). On reproduit la valeur du DPE.
364
+ */
365
+ if (bug_for_bug_compat && de.enum_type_generateur_ecs_id === '72' && rgSaisi === 0.9) {
366
+ rgReseau = 0.9;
367
+ console.warn(
368
+ `Correction rendement_generation_stockage pour le générateur ECS ${de.description} (0.9 saisi pour un réseau de chaleur non isolé)`
369
+ );
370
+ }
371
+
372
+ di.rendement_generation_stockage = rgReseau;
373
+ di.rendement_generation_stockage_depensier = rgReseau;
351
374
 
352
375
  Iecs = 1 / di.rendement_generation_stockage;
353
376
  Iecs_dep = 1 / di.rendement_generation_stockage_depensier;
@@ -118,17 +118,32 @@ describe('rgrsReseauUrbain - rendement d’un réseau de chaleur', () => {
118
118
  expect(rgrsReseauUrbain({ enum_type_generateur_ecs_id: '73' }, {})).toBe(0.9);
119
119
  });
120
120
 
121
- test('réseau marqué isolé au niveau de l’installation : 0.9', () => {
121
+ test('type 72 (réseau non isolé) : 0.75, même si le réseau de distribution est isolé', () => {
122
+ // reseau_distribution_isole porte sur la distribution ECS, pas sur l'équipement du réseau de
123
+ // chaleur : le type 72 impose 0.75 (Calcul_installation_ECS.cs, installation_non_isoles).
122
124
  expect(
123
125
  rgrsReseauUrbain({ enum_type_generateur_ecs_id: '72' }, { reseau_distribution_isole: 1 })
124
- ).toBe(0.9);
126
+ ).toBe(0.75);
125
127
  });
126
128
 
127
- test('réseau non isolé par défaut : 0.75', () => {
129
+ test('type 72 (réseau non isolé), distribution non isolée : 0.75', () => {
128
130
  expect(
129
131
  rgrsReseauUrbain({ enum_type_generateur_ecs_id: '72' }, { reseau_distribution_isole: 0 })
130
132
  ).toBe(0.75);
131
133
  });
134
+
135
+ test('autre générateur assimilé réseau de chaleur, installation marquée isolée : 0.9', () => {
136
+ expect(
137
+ rgrsReseauUrbain({ enum_type_generateur_ecs_id: '84' }, { reseau_distribution_isole: 1 })
138
+ ).toBe(0.9);
139
+ });
140
+
141
+ test('autre générateur assimilé réseau de chaleur, non isolé par défaut : 0.75', () => {
142
+ expect(
143
+ rgrsReseauUrbain({ enum_type_generateur_ecs_id: '84' }, { reseau_distribution_isole: 0 })
144
+ ).toBe(0.75);
145
+ expect(rgrsReseauUrbain({ enum_type_generateur_ecs_id: '84' }, undefined)).toBe(0.75);
146
+ });
132
147
  });
133
148
 
134
149
  /**
@@ -758,6 +773,54 @@ describe('calc_gen_ecs - consommation par générateur', () => {
758
773
  warnSpy.mockRestore();
759
774
  });
760
775
 
776
+ /**
777
+ * Type 72 exporté à 0.9 : le logiciel n'a pas transmis « non isolé » au moteur de référence
778
+ * (Tribu Calcul_installation_ECS.cs : 0.9 hors installation_non_isoles). En bug_for_bug_compat,
779
+ * on reproduit la valeur saisie.
780
+ */
781
+ test.each([0, 1])(
782
+ 'bug_for_bug_compat : type 72 avec 0.9 saisi (distribution isolée = %s) -> 0.9 conservé',
783
+ (iso) => {
784
+ state.bug = true;
785
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
786
+ ecs_de.reseau_distribution_isole = iso;
787
+ const g = {
788
+ donnee_entree: {
789
+ usage_generateur: 'ecs',
790
+ type_stockage_ecs: "abscence de stockage d'ecs (production instantanée)",
791
+ volume_stockage: 0,
792
+ type_energie: 'réseau de chauffage urbain',
793
+ enum_type_generateur_ecs_id: '72'
794
+ },
795
+ donnee_intermediaire: { rendement_generation_stockage: 0.9 }
796
+ };
797
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'immeuble');
798
+
799
+ expect(g.donnee_intermediaire.rendement_generation_stockage).toBe(0.9);
800
+ expect(g.donnee_intermediaire.rendement_generation_stockage_depensier).toBe(0.9);
801
+ // valeur de référence de régression : 100 / (0.9 * 0.9)
802
+ expect(g.donnee_intermediaire.conso_ecs).toBeCloseTo(123.45679012345678, 9);
803
+ warnSpy.mockRestore();
804
+ }
805
+ );
806
+
807
+ test('sans bug_for_bug_compat : type 72 avec 0.9 saisi -> 0.75 (méthode)', () => {
808
+ ecs_de.reseau_distribution_isole = 1;
809
+ const g = {
810
+ donnee_entree: {
811
+ usage_generateur: 'ecs',
812
+ type_stockage_ecs: "abscence de stockage d'ecs (production instantanée)",
813
+ volume_stockage: 0,
814
+ type_energie: 'réseau de chauffage urbain',
815
+ enum_type_generateur_ecs_id: '72'
816
+ },
817
+ donnee_intermediaire: { rendement_generation_stockage: 0.9 }
818
+ };
819
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'immeuble');
820
+
821
+ expect(g.donnee_intermediaire.rendement_generation_stockage).toBe(0.75);
822
+ });
823
+
761
824
  test('usage chauffage + ecs : type de générateur limité aux générateurs mixtes', () => {
762
825
  const g = gen({
763
826
  type_energie: 'électricité',
@@ -839,8 +902,9 @@ describe('calc_gen_ecs - consommation par générateur', () => {
839
902
  };
840
903
  calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'immeuble');
841
904
 
842
- // Le réseau reste isolé (0.9) car reseau_distribution_isole = 1
843
- expect(g.donnee_intermediaire.rendement_generation_stockage).toBe(0.9);
905
+ // Type 72 (réseau non isolé) : 0.75 conservé malgré reseau_distribution_isole = 1, l'avertissement
906
+ // d'incohérence reste émis.
907
+ expect(g.donnee_intermediaire.rendement_generation_stockage).toBe(0.75);
844
908
  expect(warnSpy).toHaveBeenCalled();
845
909
  warnSpy.mockRestore();
846
910
  });
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.43`** · branche `main` · généré le 2026-10-10
373
+ > **Version `1.8.45`** · branche `main` · généré le 2026-10-10
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>61 969</strong><br/><sub>DPE conformes</sub></td>
381
- <td align="center"><strong>68,87 %</strong><br/><sub>réussite globale</sub></td>
380
+ <td align="center"><strong>62 119</strong><br/><sub>DPE conformes</sub></td>
381
+ <td align="center"><strong>69,03 %</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,24 %** | `█████████░░░░░░░░░░░` | 4 624 / 9 999 |
388
- | 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,55 %** | `███████████████████░` | 9 255 / 10 000 |
389
- | 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,55 %** | `██████████████████░░` | 8 948 / 9 992 |
387
+ | 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,42 %** | `█████████░░░░░░░░░░░` | 4 642 / 9 999 |
388
+ | 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,56 %** | `███████████████████░` | 9 256 / 10 000 |
389
+ | 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,71 %** | `██████████████████░░` | 8 964 / 9 992 |
390
390
  | 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,66 %** | `██████████████████░░` | 8 865 / 9 999 |
391
- | 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,22 %** | `█████████████████░░░` | 8 420 / 9 998 |
392
- | 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,61 %** | `██████████████░░░░░░` | 7 161 / 10 000 |
393
- | 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **66,19 %** | `█████████████░░░░░░░` | 6 618 / 9 999 |
394
- | 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **53,74 %** | `███████████░░░░░░░░░` | 5 372 / 9 997 |
395
- | 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,06 %** | `█████░░░░░░░░░░░░░░░` | 2 706 / 10 000 |
391
+ | 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,23 %** | `█████████████████░░░` | 8 421 / 9 998 |
392
+ | 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,70 %** | `██████████████░░░░░░` | 7 170 / 10 000 |
393
+ | 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **66,40 %** | `█████████████░░░░░░░` | 6 639 / 9 999 |
394
+ | 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **54,14 %** | `███████████░░░░░░░░░` | 5 412 / 9 997 |
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 |
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>4,86 ms</strong><br/><sub>moyenne</sub></td>
407
- <td align="center"><strong>4,24 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>549,1 ms</strong><br/><sub>max</sub></td>
406
+ <td align="center"><strong>5,18 ms</strong><br/><sub>moyenne</sub></td>
407
+ <td align="center"><strong>4,51 ms</strong><br/><sub>médiane</sub></td>
408
+ <td align="center"><strong>0,11 ms</strong><br/><sub>min</sub></td>
409
+ <td align="center"><strong>468,0 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,32 ms | 3,87 ms | 0,21 ms | 28,9 ms | 8,02 ms | 10,8 ms |
416
- | **Appartement · chauffage individuel (2025)** | 3,79 ms | 3,46 ms | 1,50 ms | 34,3 ms | 6,55 ms | 8,57 ms |
417
- | **Logement individuel (2025)** | 4,75 ms | 4,21 ms | 1,61 ms | 34,4 ms | 8,90 ms | 12,2 ms |
418
- | **Maison individuelle (2025)** | 5,40 ms | 4,87 ms | 1,76 ms | 36,9 ms | 9,53 ms | 12,7 ms |
419
- | **Immeuble · chauffage individuel** | 6,22 ms | 5,32 ms | 0,14 ms | 115,4 ms | 12,5 ms | 19,3 ms |
420
- | **Appartement · chauffage collectif (2025)** | 4,08 ms | 3,65 ms | 1,48 ms | 34,4 ms | 6,76 ms | 8,58 ms |
421
- | **Immeuble · chauffage collectif** | 5,30 ms | 4,49 ms | 0,12 ms | 107,4 ms | 9,83 ms | 17,6 ms |
422
- | **Immeuble · chauffage mixte** | 4,78 ms | 3,93 ms | 0,22 ms | 549,1 ms | 9,87 ms | 15,9 ms |
423
- | **Individuel généré depuis l'immeuble (2026)** | 5,07 ms | 4,39 ms | 1,65 ms | 42,9 ms | 9,57 ms | 14,4 ms |
415
+ | **Généraliste** | 4,87 ms | 4,40 ms | 0,22 ms | 50,4 ms | 8,77 ms | 12,5 ms |
416
+ | **Appartement · chauffage individuel (2025)** | 4,08 ms | 3,72 ms | 1,48 ms | 33,5 ms | 7,05 ms | 9,43 ms |
417
+ | **Logement individuel (2025)** | 4,71 ms | 4,20 ms | 1,54 ms | 35,6 ms | 8,74 ms | 11,8 ms |
418
+ | **Maison individuelle (2025)** | 5,56 ms | 4,98 ms | 1,88 ms | 47,7 ms | 10,1 ms | 12,9 ms |
419
+ | **Immeuble · chauffage individuel** | 6,18 ms | 5,30 ms | 0,11 ms | 78,2 ms | 12,5 ms | 19,5 ms |
420
+ | **Appartement · chauffage collectif (2025)** | 5,27 ms | 4,59 ms | 1,62 ms | 45,8 ms | 11,3 ms | 18,1 ms |
421
+ | **Immeuble · chauffage collectif** | 5,45 ms | 4,63 ms | 0,12 ms | 98,6 ms | 10,0 ms | 18,0 ms |
422
+ | **Immeuble · chauffage mixte** | 5,05 ms | 4,14 ms | 0,24 ms | 468,0 ms | 10,5 ms | 16,9 ms |
423
+ | **Individuel généré depuis l'immeuble (2026)** | 5,44 ms | 4,66 ms | 1,71 ms | 43,7 ms | 10,2 ms | 15,6 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
@@ -582,47 +582,142 @@ export default function calc_conso(
582
582
  ret.sortie_par_energie_collection = {};
583
583
  ret.sortie_par_energie_collection.sortie_par_energie = energie_ids.reduce((acc, energie_id) => {
584
584
  const type_energie = enums.type_energie[energie_id];
585
- let vt_en, fr_en;
586
- if (type_energie === 'électricité') {
587
- vt_en = vt;
588
- fr_en = fr;
589
- } else {
590
- vt_en = [];
591
- fr_en = [];
592
- }
593
585
  const gen_ch_en = gen_ch.filter(
594
586
  (gen_ch) => gen_ch.donnee_entree.enum_type_energie_id === energie_id
595
587
  );
596
588
  const gen_ecs_en = gen_ecs.filter(
597
589
  (gen_ecs) => gen_ecs.donnee_entree.enum_type_energie_id === energie_id
598
590
  );
599
- let conso_en = calc_conso_pond(
591
+
592
+ if (type_energie === 'électricité') {
593
+ acc.push({
594
+ ...sortieElectricite(
595
+ Sh,
596
+ zc_id,
597
+ vt,
598
+ gen_ch,
599
+ gen_ecs,
600
+ gen_ch_en,
601
+ gen_ecs_en,
602
+ fr,
603
+ ecs,
604
+ prorataECS,
605
+ prorataChauffage,
606
+ facteursAc
607
+ ),
608
+ enum_type_energie_id: energie_id
609
+ });
610
+ return acc;
611
+ }
612
+
613
+ const conso_en = calc_conso_pond(
600
614
  Sh,
601
615
  zc_id,
602
- vt_en,
616
+ [],
603
617
  gen_ch_en,
604
618
  gen_ecs_en,
605
- fr_en,
619
+ [],
606
620
  '',
607
621
  null,
608
622
  prorataECS,
609
623
  prorataChauffage,
610
- type_energie === 'électricité' ? ecs : []
624
+ []
611
625
  );
612
- conso_en = {
626
+ acc.push({
613
627
  conso_ch: conso_en._ch,
614
628
  conso_ecs: conso_en._ecs,
615
- conso_5_usages: conso_en._5_usages,
629
+ // Énergie non électrique : chauffage + ECS uniquement, l'éclairage, le froid et les
630
+ // auxiliaires étant électriques (portés par la ligne électricité)
631
+ conso_5_usages: conso_en._ch + conso_en._ecs,
616
632
  emission_ges_ch: conso_en._ch * coef_ges[getCoefKey(type_energie, 'ch')],
617
633
  emission_ges_ecs: conso_en._ecs * coef_ges[getCoefKey(type_energie, 'ecs')],
618
- emission_ges_5_usages: conso_en._5_usages * coef_ges[type_energie] // TODO elec
619
- };
620
- conso_en.enum_type_energie_id = energie_id;
621
- return acc.concat(conso_en);
634
+ enum_type_energie_id: energie_id
635
+ });
636
+ const ligne = acc[acc.length - 1];
637
+ ligne.emission_ges_5_usages = ligne.emission_ges_ch + ligne.emission_ges_ecs;
638
+ return acc;
622
639
  }, []);
623
640
  return ret;
624
641
  }
625
642
 
643
+ /**
644
+ * Ligne « électricité » de sortie_par_energie (issue #34).
645
+ *
646
+ * - Chauffage et ECS : générateurs électriques uniquement.
647
+ * - Auxiliaires (génération et distribution ch/ecs, ventilation) : toujours électriques, quelle
648
+ * que soit l'énergie du générateur (§15, §16.2 : « consommation annuelle d'électricité pour les
649
+ * auxiliaires »), donc rattachés à l'électricité pour tous les générateurs.
650
+ * - Éclairage et froid électrique (un réseau de froid relève d'une autre énergie).
651
+ * - Émissions : coefficient GES propre à chaque usage (ch 0,079, ecs 0,065, fr 0,064,
652
+ * éclairage 0,069, auxiliaires 0,064), aucun coefficient « électricité » global n'existant.
653
+ * - facteursAc : consommations nettes de l'autoconsommation PV par usage (arrêté DPE habitation
654
+ * §3.a, méthode §16.2) ; vide pour le calcul brut servant au taux de couverture PV.
655
+ *
656
+ * @return {{conso_ch: number, conso_ecs: number, conso_5_usages: number, emission_ges_ch: number, emission_ges_ecs: number, emission_ges_5_usages: number}}
657
+ */
658
+ export function sortieElectricite(
659
+ Sh,
660
+ zc_id,
661
+ vt,
662
+ gen_ch,
663
+ gen_ecs,
664
+ gen_ch_elec,
665
+ gen_ecs_elec,
666
+ fr,
667
+ ecs,
668
+ prorataECS,
669
+ prorataChauffage,
670
+ facteursAc = {}
671
+ ) {
672
+ const fr_elec = fr.filter((f) => getTypeEnergie(f.donnee_entree, 'fr') === 'électricité fr');
673
+ const postes = (coef) => {
674
+ const usages = calc_conso_pond(
675
+ Sh,
676
+ zc_id,
677
+ [],
678
+ gen_ch_elec,
679
+ gen_ecs_elec,
680
+ [],
681
+ '',
682
+ coef,
683
+ prorataECS,
684
+ prorataChauffage,
685
+ [],
686
+ facteursAc
687
+ );
688
+ const communs = calc_conso_pond(
689
+ Sh,
690
+ zc_id,
691
+ vt,
692
+ gen_ch,
693
+ gen_ecs,
694
+ fr_elec,
695
+ '',
696
+ coef,
697
+ prorataECS,
698
+ prorataChauffage,
699
+ ecs,
700
+ facteursAc
701
+ );
702
+ return {
703
+ ch: usages._ch,
704
+ ecs: usages._ecs,
705
+ total:
706
+ usages._ch + usages._ecs + communs._fr + communs._totale_auxiliaire + communs._eclairage
707
+ };
708
+ };
709
+ const conso = postes(null);
710
+ const ges = postes(coef_ges);
711
+ return {
712
+ conso_ch: conso.ch,
713
+ conso_ecs: conso.ecs,
714
+ conso_5_usages: conso.total,
715
+ emission_ges_ch: ges.ch,
716
+ emission_ges_ecs: ges.ecs,
717
+ emission_ges_5_usages: ges.total
718
+ };
719
+ }
720
+
626
721
  export function classe_bilan_dpe(ep_conso_5_usages_m2, zc_id, ca_id, Sh) {
627
722
  const ca = enums.classe_altitude[ca_id];
628
723
 
package/conso.spec.js CHANGED
@@ -622,6 +622,91 @@ describe('calc_conso - agrégation des consommations', () => {
622
622
  });
623
623
  });
624
624
 
625
+ /**
626
+ * Ligne « électricité » de sortie_par_energie (issue #34).
627
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §15, §16.2 ; arrêté DPE habitation du 31/03/2021 §3.a, §4.a
628
+ */
629
+ describe('sortie_par_energie - ligne électricité (issue #34)', () => {
630
+ const elecDe = (res) =>
631
+ res.sortie_par_energie_collection.sortie_par_energie.find(
632
+ (e) => e.enum_type_energie_id === '1'
633
+ );
634
+
635
+ /** Chaudière gaz avec auxiliaires de génération et de distribution (électriques). */
636
+ const chGaz = () => [
637
+ installCh({ cle_repartition_ch: 1 }, [
638
+ genCh('2', {
639
+ conso_ch: 10000,
640
+ conso_ch_depensier: 12000,
641
+ conso_auxiliaire_generation_ch: 100,
642
+ conso_auxiliaire_distribution_ch: 50
643
+ })
644
+ ])
645
+ ];
646
+
647
+ test('chaudière gaz : auxiliaires rattachés à l’électricité, GES = Σ postes × coefficient par usage', () => {
648
+ const res = calc_conso(100, 1, 1, [], chGaz(), [], [], 1, 1, DATE_DPE, coef_ep);
649
+ const elec = elecDe(res);
650
+
651
+ // Pas de chauffage électrique ; éclairage = 1 × Sh = 100 ; auxiliaires = 100 + 50
652
+ expect(elec.conso_ch).toBe(0);
653
+ expect(elec.conso_5_usages).toBeCloseTo(100 + 150, 9);
654
+ // 100 × 0,069 (éclairage) + 150 × 0,064 (auxiliaires)
655
+ expect(elec.emission_ges_5_usages).toBeCloseTo(100 * 0.069 + 150 * 0.064, 9);
656
+ // La ligne gaz ne porte plus les auxiliaires
657
+ const gaz = res.sortie_par_energie_collection.sortie_par_energie.find(
658
+ (e) => e.enum_type_energie_id === '2'
659
+ );
660
+ expect(gaz.conso_5_usages).toBeCloseTo(10000, 9);
661
+ });
662
+
663
+ test('somme des lignes par énergie = totaux 5 usages (EF et GES)', () => {
664
+ const ecs = [installEcs({}, [genEcs('1', { conso_ecs: 1000, conso_ecs_depensier: 1100 })])];
665
+ const vt = [{ donnee_entree: {}, donnee_intermediaire: { conso_auxiliaire_ventilation: 40 } }];
666
+ const res = calc_conso(100, 1, 1, vt, chGaz(), ecs, [], 1, 1, DATE_DPE, coef_ep);
667
+ const lignes = res.sortie_par_energie_collection.sortie_par_energie;
668
+ const somme = (k) => lignes.reduce((acc, l) => acc + l[k], 0);
669
+
670
+ expect(somme('conso_5_usages')).toBeCloseTo(res.ef_conso.conso_5_usages, 9);
671
+ expect(somme('emission_ges_5_usages')).toBeCloseTo(res.emission_ges.emission_ges_5_usages, 9);
672
+ // ECS électrique au coefficient ECS (0,065)
673
+ expect(elecDe(res).emission_ges_ecs).toBeCloseTo(1000 * 0.065, 9);
674
+ });
675
+
676
+ test('froid : électrique compté dans la ligne électricité, réseau de froid exclu', () => {
677
+ const frElec = [
678
+ { donnee_entree: { enum_type_energie_id: '1' }, donnee_intermediaire: { conso_fr: 200 } }
679
+ ];
680
+ const frReseau = [
681
+ { donnee_entree: { enum_type_energie_id: '3' }, donnee_intermediaire: { conso_fr: 200 } }
682
+ ];
683
+ const avecElec = elecDe(calc_conso(100, 1, 1, [], [], [], frElec, 1, 1, DATE_DPE, coef_ep));
684
+ const avecReseau = elecDe(calc_conso(100, 1, 1, [], [], [], frReseau, 1, 1, DATE_DPE, coef_ep));
685
+
686
+ expect(avecElec.conso_5_usages).toBeCloseTo(100 + 200, 9);
687
+ expect(avecElec.emission_ges_5_usages).toBeCloseTo(100 * 0.069 + 200 * 0.064, 9);
688
+ expect(avecReseau.conso_5_usages).toBeCloseTo(100, 9);
689
+ });
690
+
691
+ test('autoconsommation PV : ligne électricité nette, usage par usage', () => {
692
+ const facteursAc = { eclairage: 0.5, auxiliaire_generation_ch: 0.8 };
693
+ const net = elecDe(
694
+ calc_conso(100, 1, 1, [], chGaz(), [], [], 1, 1, DATE_DPE, coef_ep, 1, 1, facteursAc)
695
+ );
696
+
697
+ // éclairage 100 × 0,5 ; génération 100 × 0,8 ; distribution 50 inchangée
698
+ expect(net.conso_5_usages).toBeCloseTo(50 + 80 + 50, 9);
699
+ expect(net.emission_ges_5_usages).toBeCloseTo(50 * 0.069 + (80 + 50) * 0.064, 9);
700
+ });
701
+
702
+ test('aucune émission NaN sur la ligne électricité', () => {
703
+ const res = calc_conso(100, 1, 1, [], chGaz(), [], [], 1, 1, DATE_DPE, coef_ep);
704
+ for (const ligne of res.sortie_par_energie_collection.sortie_par_energie) {
705
+ expect(Number.isFinite(ligne.emission_ges_5_usages)).toBe(true);
706
+ }
707
+ });
708
+ });
709
+
625
710
  /**
626
711
  * Barème des prix des énergies selon la date d'établissement du DPE.
627
712
  * Barème 2021 : Annexe 7 de l'arrêté du 31/03/2021.
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.44';
36
+ const LIB_VERSION = '1.8.46';
37
37
 
38
38
  const dpeSanitizerService = new DpeSanitizerService();
39
39
 
@@ -651,6 +651,8 @@ export function calcul_3cl(inputDpe, options) {
651
651
  facteursAc
652
652
  );
653
653
  conso.emission_ges = consoNette.emission_ges;
654
+ // Ligne électricité de sortie_par_energie nette de l'autoconsommation (issue #34)
655
+ conso.sortie_par_energie_collection = consoNette.sortie_par_energie_collection;
654
656
  conso.cout = consoNette.cout;
655
657
  }
656
658
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.8.44",
3
+ "version": "1.8.46",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {