@open3cl/engine 1.7.3 → 1.8.0

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_besoin_ecs.js CHANGED
@@ -24,11 +24,14 @@ export function calc_besoin_ecs_j(ca, mois, zc, nadeq, depensier) {
24
24
  export default function calc_besoin_ecs(ca, zc, nadeq) {
25
25
  const ret = {
26
26
  besoin_ecs: 0,
27
- besoin_ecs_depensier: 0
27
+ besoin_ecs_depensier: 0,
28
+ besoin_ecs_par_mois: {}
28
29
  };
29
30
  for (const mois of mois_liste) {
30
- ret.besoin_ecs += calc_besoin_ecs_j(ca, mois, zc, nadeq, false);
31
+ const becs_j = calc_besoin_ecs_j(ca, mois, zc, nadeq, false);
32
+ ret.besoin_ecs += becs_j;
31
33
  ret.besoin_ecs_depensier += calc_besoin_ecs_j(ca, mois, zc, nadeq, true);
34
+ ret.besoin_ecs_par_mois[mois] = becs_j;
32
35
  }
33
36
  return ret;
34
37
  }
package/11_ecs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import enums from './enums.js';
2
2
  import calc_gen_ecs from './14_generateur_ecs.js';
3
- import { tv, requestInput } from './utils.js';
3
+ import { tv, requestInput, mois_liste, Njj } from './utils.js';
4
4
 
5
5
  function tv_rendement_distribution_ecs(di, de, du, pvc) {
6
6
  let matcher = {};
@@ -49,6 +49,89 @@ function tv_rendement_distribution_ecs(di, de, du, pvc) {
49
49
  }
50
50
  }
51
51
 
52
+ /**
53
+ * Calcul de la consommation des auxiliaires de distribution ECS - CAS IMMEUBLE
54
+ * Calcul installation par installation pour les réseaux collectifs bouclés ou avec traçage.
55
+ *
56
+ * @param {object} de - donnee_entree de l'installation ECS
57
+ * @param {number} surfaceImmeuble - surface habitable totale de l'immeuble (m²)
58
+ * @param {object} becsParMois - besoin ECS mensuel de l'immeuble {Janvier: X, Février: X, ...} [kWh]
59
+ * @returns {number} conso_auxiliaire_distribution_ecs_installation [Wh]
60
+ */
61
+ function calc_auxiliaire_distribution_ecs_immeuble(de, surfaceImmeuble, becsParMois) {
62
+ const type_installation = enums.type_installation[de.enum_type_installation_id];
63
+
64
+ // Seulement pour les installations collectives
65
+ if (!type_installation || !type_installation.includes('collective')) {
66
+ return 0;
67
+ }
68
+
69
+ const enum_bouclage_id = String(de.enum_bouclage_reseau_ecs_id);
70
+ const surface_installation = de.surface_habitable || 0;
71
+ const surface_immeuble_total = surfaceImmeuble || 1;
72
+ const ratio_surface = surface_installation / surface_immeuble_total;
73
+
74
+ // CAS 1 : réseau non bouclé → 0
75
+ if (enum_bouclage_id === '1') {
76
+ return 0;
77
+ }
78
+
79
+ // CAS 3 : traçage → 0.14 × BECS_annuel × ratio_surface [Wh]
80
+ if (enum_bouclage_id === '3') {
81
+ const becs_annuel_kwh = Object.values(becsParMois).reduce((acc, v) => acc + v, 0);
82
+ // becs_annuel est en kWh → × 1000 pour obtenir des Wh
83
+ return 0.14 * becs_annuel_kwh * 1000 * ratio_surface;
84
+ }
85
+
86
+ // CAS 2 : réseau bouclé → calcul complet 9 étapes [Wh]
87
+ if (enum_bouclage_id === '2') {
88
+ const Sh_inst = surface_installation;
89
+ const Niv_inst = Number(de.nombre_niveau_installation_ecs) || 1;
90
+
91
+ // Étape 3 : Calcul de Lb pour l'installation i
92
+ // Lb = 1.2 × (1.1 × Sh_inst/nombre_niveaux + 4 × nombre_niveaux)
93
+ const Lb = 1.2 * (1.1 * (Sh_inst / Niv_inst) + 4 * Niv_inst);
94
+
95
+ // Étape 4 : DeltaPb = 0.2 × Lb + 10
96
+ const DeltaPb = 0.2 * Lb + 10;
97
+
98
+ let Qcirc_annuel = 0;
99
+
100
+ for (const mois of mois_liste) {
101
+ const Njj_mois = Njj[mois];
102
+ const becs_immeuble_j_kwh = becsParMois[mois] || 0;
103
+
104
+ // Étape 1 : Qdwj_i [kWh]
105
+ const Qdwj_i = 0.24 * becs_immeuble_j_kwh * ratio_surface;
106
+
107
+ // Étape 2 : qdwj_i [m³/h]
108
+ // Note: la spec officielle utilise Qdwj en Wh et divise par 1000 ensuite.
109
+ // Le moteur fournit becs en kWh, donc Qdwj est en kWh → on ne divise pas par 1000.
110
+ const qdwj_i = Qdwj_i / (5.815 * 5 * Njj_mois);
111
+
112
+ // Étape 5 : Phyd,j [W]
113
+ // Phyd,j = DeltaPb [kPa] × 1000 × qdwj_i [m³/h] / 3600
114
+ const Phyd_j = (DeltaPb * 1000 * qdwj_i) / 3600;
115
+
116
+ // Étape 6 : Effcirb,j (rendement pompe)
117
+ const Effcirb_j = Phyd_j > 0 ? 0.035 * Math.pow(Phyd_j, 0.45) : 0;
118
+
119
+ // Étape 7 : Pcirb,j [W] = max(Phyd,j / Effcirb,j, 20)
120
+ const Pcirb_j = Effcirb_j > 0 ? Math.max(Phyd_j / Effcirb_j, 20) : 20;
121
+
122
+ // Étape 8 : Qcirb,j [Wh]
123
+ const Qcirb_j = (5 * Pcirb_j + 19 * 20) * Njj_mois;
124
+
125
+ Qcirc_annuel += Qcirb_j;
126
+ }
127
+
128
+ // Étape 9 : conso annuelle en Wh
129
+ return Qcirc_annuel;
130
+ }
131
+
132
+ return 0;
133
+ }
134
+
52
135
  export default function calc_ecs(
53
136
  dpe,
54
137
  ecs,
@@ -61,7 +144,8 @@ export default function calc_ecs(
61
144
  virtualisationECS,
62
145
  surfaceImmeuble,
63
146
  nombreAppartements,
64
- isImmeubleSystemEcsIndividuels
147
+ isImmeubleSystemEcsIndividuels,
148
+ becsParMois
65
149
  ) {
66
150
  const de = ecs.donnee_entree;
67
151
  const di = {};
@@ -101,6 +185,19 @@ export default function calc_ecs(
101
185
  0
102
186
  );
103
187
 
188
+ // Calcul de la consommation des auxiliaires de distribution ECS pour l'immeuble
189
+ // Résultat en Wh, converti en kWh pour cohérence avec le reste du moteur
190
+ if (th === 'immeuble' && becsParMois && Object.keys(becsParMois).length > 0) {
191
+ const conso_aux_wh = calc_auxiliaire_distribution_ecs_immeuble(
192
+ de,
193
+ surfaceImmeuble,
194
+ becsParMois
195
+ );
196
+ di.conso_auxiliaire_distribution_ecs = conso_aux_wh / 1000;
197
+ } else {
198
+ di.conso_auxiliaire_distribution_ecs = 0;
199
+ }
200
+
104
201
  ecs.donnee_intermediaire = di;
105
202
  ecs.donnee_utilisateur = du;
106
203
  }
package/11_ecs.spec.js CHANGED
@@ -21,7 +21,10 @@ vi.mock('./enums.js', () => ({
21
21
  vi.mock('./utils.js', () => ({
22
22
  set_bug_for_bug_compat: vi.fn(),
23
23
  tv: vi.fn(),
24
- requestInput: vi.fn()
24
+ requestInput: vi.fn(),
25
+ // Calendrier réduit à deux mois pour le calcul mensuel des auxiliaires de distribution
26
+ mois_liste: ['Janvier', 'Février'],
27
+ Njj: { Janvier: 31, Février: 28 }
25
28
  }));
26
29
 
27
30
  vi.mock('./14_generateur_ecs.js', () => ({
@@ -281,3 +284,138 @@ describe('calc_ecs - agrégation des consommations', () => {
281
284
  expect(ecs.donnee_intermediaire.conso_ecs_depensier).toBe(28);
282
285
  });
283
286
  });
287
+
288
+ /**
289
+ * Consommation des auxiliaires de distribution ECS - cas immeuble
290
+ * Seuls les réseaux collectifs bouclés ou avec traçage consomment de l'énergie.
291
+ */
292
+ describe('calc_ecs - auxiliaires de distribution ECS (immeuble)', () => {
293
+ /** Calcule l'ECS d'un immeuble et retourne la conso des auxiliaires de distribution [kWh]. */
294
+ function consoAuxDistribution(deExtra, becsParMois, surfaceImmeuble = 400, th = 'immeuble') {
295
+ const ecs = makeEcs({ tv_rendement_distribution_ecs_id: '5', ...deExtra });
296
+ calc_ecs(
297
+ {},
298
+ ecs,
299
+ 100,
300
+ 200,
301
+ 1,
302
+ 'ca1',
303
+ 'h1a',
304
+ th,
305
+ false,
306
+ surfaceImmeuble,
307
+ null,
308
+ false,
309
+ becsParMois
310
+ );
311
+ return ecs.donnee_intermediaire.conso_auxiliaire_distribution_ecs;
312
+ }
313
+
314
+ test.each([
315
+ ['logement autre qu’un immeuble', 'maison', { Janvier: 1000 }],
316
+ ['besoin mensuel absent', 'immeuble', undefined],
317
+ ['besoin mensuel vide', 'immeuble', {}]
318
+ ])('%s : pas de consommation', (_libelle, th, becsParMois) => {
319
+ expect(
320
+ consoAuxDistribution(
321
+ { enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2' },
322
+ becsParMois,
323
+ 400,
324
+ th
325
+ )
326
+ ).toBe(0);
327
+ });
328
+
329
+ test.each([
330
+ ['type d’installation inconnu', { enum_type_installation_id: '9' }],
331
+ ['installation individuelle', { enum_type_installation_id: '1' }],
332
+ [
333
+ 'réseau collectif non bouclé',
334
+ { enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '1' }
335
+ ],
336
+ ['type de bouclage non renseigné', { enum_type_installation_id: '2' }]
337
+ ])('%s : pas de consommation', (_libelle, de) => {
338
+ expect(consoAuxDistribution(de, { Janvier: 1000 })).toBe(0);
339
+ });
340
+
341
+ test('réseau avec traçage : 0,14 × besoin annuel × ratio de surface', () => {
342
+ const conso = consoAuxDistribution(
343
+ { enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: 3, surface_habitable: 100 },
344
+ { Janvier: 100, Février: 50 }
345
+ );
346
+
347
+ // 0,14 × 150 kWh × 1000 × (100 / 400) = 5250 Wh => 5,25 kWh
348
+ expect(conso).toBeCloseTo(5.25, 9);
349
+ });
350
+
351
+ test('réseau avec traçage sans surfaces renseignées : ratio de surface nul', () => {
352
+ const conso = consoAuxDistribution(
353
+ { enum_type_installation_id: '3', enum_bouclage_reseau_ecs_id: '3' },
354
+ { Janvier: 100 },
355
+ null
356
+ );
357
+
358
+ // surface_habitable absente => 0 ; surface de l'immeuble absente => 1
359
+ expect(conso).toBe(0);
360
+ });
361
+
362
+ test('réseau bouclé sans besoin : puissance minimale du circulateur de 20 W', () => {
363
+ const conso = consoAuxDistribution(
364
+ { enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
365
+ { Janvier: 0 }
366
+ );
367
+
368
+ // Pcirb = 20 W chaque mois : (5 × 20 + 19 × 20) × (31 + 28) jours = 28320 Wh
369
+ expect(conso).toBeCloseTo(28.32, 9);
370
+ });
371
+
372
+ test('réseau bouclé à faible besoin : puissance du circulateur ramenée au minimum de 20 W', () => {
373
+ const conso = consoAuxDistribution(
374
+ {
375
+ enum_type_installation_id: '2',
376
+ enum_bouclage_reseau_ecs_id: '2',
377
+ surface_habitable: 100,
378
+ nombre_niveau_installation_ecs: 2
379
+ },
380
+ { Janvier: 1000, Février: 1000 }
381
+ );
382
+
383
+ // Phyd / Effcirb < 20 W => Pcirb = 20 W, identique au cas sans besoin
384
+ expect(conso).toBeCloseTo(28.32, 9);
385
+ });
386
+
387
+ test('réseau bouclé : calcul complet de la consommation du circulateur', () => {
388
+ const conso = consoAuxDistribution(
389
+ {
390
+ enum_type_installation_id: '2',
391
+ enum_bouclage_reseau_ecs_id: '2',
392
+ surface_habitable: 100,
393
+ nombre_niveau_installation_ecs: '2'
394
+ },
395
+ { Janvier: 10000 }
396
+ );
397
+
398
+ // Lb = 1,2 × (1,1 × 100 / 2 + 4 × 2) = 75,6 m ; ΔPb = 0,2 × Lb + 10 = 25,12 kPa
399
+ // Janvier : Phyd ≈ 4,645 W => Pcirb ≈ 66,49 W ; Février (besoin absent) : Pcirb = 20 W
400
+ // Référence de régression (calcul des 9 étapes de la méthode) : 35526,3879475995 Wh
401
+ expect(conso).toBeCloseTo(35.526387947599495, 9);
402
+ });
403
+
404
+ test('réseau bouclé sans nombre de niveaux : un seul niveau par défaut', () => {
405
+ const avecUnNiveau = consoAuxDistribution(
406
+ {
407
+ enum_type_installation_id: '2',
408
+ enum_bouclage_reseau_ecs_id: '2',
409
+ surface_habitable: 100,
410
+ nombre_niveau_installation_ecs: 1
411
+ },
412
+ { Janvier: 10000 }
413
+ );
414
+ const sansNiveau = consoAuxDistribution(
415
+ { enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
416
+ { Janvier: 10000 }
417
+ );
418
+
419
+ expect(sansNiveau).toBe(avecUnNiveau);
420
+ });
421
+ });
package/README.md CHANGED
@@ -23,8 +23,6 @@ Implémentation open source du moteur Open3CL de l'ADEME.
23
23
  &middot;
24
24
  <a href="https://github.com/Open3CL/engine/issues/new?labels=enhancement&template=feature-request---.md">Créer une feature</a>
25
25
  &middot;
26
- <a href="https://open3cl.github.io/engine/build/">Démo en ligne</a>
27
- &middot;
28
26
  <a href="https://open3cl.github.io/engine/reports/corpus">Rapports de corpus</a>
29
27
  </p>
30
28
  </div>
@@ -57,7 +55,6 @@ Implémentation open source du moteur Open3CL de l'ADEME.
57
55
  <li><a href="#api-publique">API publique</a></li>
58
56
  <li><a href="#options-de-calcul">Options de calcul</a></li>
59
57
  <li><a href="#lire-le-résultat">Lire le résultat</a></li>
60
- <li><a href="#tester-un-dpe-sans-écrire-de-code">Tester un DPE sans écrire de code</a></li>
61
58
  </ul>
62
59
  </li>
63
60
  <li><a href="#-tests-de-corpus">Tests de corpus</a>
@@ -142,12 +139,11 @@ flowchart LR
142
139
 
143
140
  ### L'écosystème Open3CL
144
141
 
145
- | Ressource | Description |
146
- | :-------------------------------------------------------------------- | :------------------------------------------------------------- |
147
- | [open3cl.fr](https://open3cl.fr) | Le site du projet |
148
- | [Démonstrateur](https://open3cl.github.io/engine/build/) | Chargez un DPE XML et comparez les sorties du moteur, en ligne |
149
- | [Rapports de corpus](https://open3cl.github.io/engine/reports/corpus) | Le tableau de bord interactif des résultats sur DPE réels |
150
- | [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine) | Le paquet npm |
142
+ | Ressource | Description |
143
+ | :-------------------------------------------------------------------- | :-------------------------------------------------------- |
144
+ | [open3cl.fr](https://open3cl.fr) | Le site du projet |
145
+ | [Rapports de corpus](https://open3cl.github.io/engine/reports/corpus) | Le tableau de bord interactif des résultats sur DPE réels |
146
+ | [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine) | Le paquet npm |
151
147
 
152
148
  <p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
153
149
 
@@ -292,17 +288,6 @@ Le DPE renvoyé est l'objet d'entrée, enrichi de `logement.sortie` :
292
288
  | `sortie.confort_ete` / `qualite_isolation` | Indicateurs de confort d'été et de qualité d'isolation |
293
289
  | `sortie.production_electricite` | Production photovoltaïque |
294
290
 
295
- ### Tester un DPE sans écrire de code
296
-
297
- Le [démonstrateur en ligne](https://open3cl.github.io/engine/build/) accepte un DPE au format XML, l'envoie au moteur et
298
- affiche **côte à côte** les valeurs du DPE d'origine, celles calculées par Open3CL, et le différentiel.
299
-
300
- C'est le moyen le plus rapide de qualifier un écart : problème dans le DPE d'origine, ou bug dans la librairie ?
301
-
302
- <p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
303
-
304
- ---
305
-
306
291
  ## 🧪 Tests de corpus
307
292
 
308
293
  > Un corpus, c'est une liste de numéros de DPE réels. Le moteur les rejoue **tous**, compare ses sorties à celles du
@@ -385,28 +370,28 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
385
370
 
386
371
  <!-- CORPUS:START -->
387
372
 
388
- > **Version `1.6.2`** · branche `pr-174` · généré le 2026-09-20
373
+ > **Version `1.6.2`** · branche `main` · généré le 2026-09-18
389
374
  > Seuil de tolérance **5%**
390
375
 
391
376
  <table>
392
377
  <tr>
393
378
  <td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
394
379
  <td align="center"><strong>89 996</strong><br/><sub>DPE analysés</sub></td>
395
- <td align="center"><strong>59 522</strong><br/><sub>DPE conformes</sub></td>
396
- <td align="center"><strong>66,14 %</strong><br/><sub>réussite globale</sub></td>
380
+ <td align="center"><strong>59 497</strong><br/><sub>DPE conformes</sub></td>
381
+ <td align="center"><strong>66,11 %</strong><br/><sub>réussite globale</sub></td>
397
382
  </tr>
398
383
  </table>
399
384
 
400
385
  | | Corpus | Réussite | | DPE conformes |
401
386
  | :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
402
387
  | 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **45,97 %** | `█████████░░░░░░░░░░░` | 4 597 / 10 000 |
403
- | 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,62 %** | `██████████████████░░` | 9 162 / 10 000 |
404
- | 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,16 %** | `██████████████████░░` | 8 814 / 9 998 |
405
- | 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **87,99 %** | `██████████████████░░` | 8 799 / 10 000 |
406
- | 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **73,78 %** | `███████████████░░░░░` | 7 377 / 9 999 |
407
- | 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **69,51 %** | `██████████████░░░░░░` | 6 951 / 10 000 |
408
- | 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,30 %** | `████████████░░░░░░░░` | 6 229 / 9 999 |
409
- | 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **48,38 %** | `██████████░░░░░░░░░░` | 4 838 / 10 000 |
388
+ | 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,55 %** | `██████████████████░░` | 9 155 / 10 000 |
389
+ | 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,14 %** | `██████████████████░░` | 8 812 / 9 998 |
390
+ | 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **87,93 %** | `██████████████████░░` | 8 793 / 10 000 |
391
+ | 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **73,76 %** | `███████████████░░░░░` | 7 375 / 9 999 |
392
+ | 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **69,47 %** | `██████████████░░░░░░` | 6 947 / 10 000 |
393
+ | 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,27 %** | `████████████░░░░░░░░` | 6 226 / 9 999 |
394
+ | 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **48,37 %** | `██████████░░░░░░░░░░` | 4 837 / 10 000 |
410
395
  | 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,55 %** | `██████░░░░░░░░░░░░░░` | 2 755 / 10 000 |
411
396
 
412
397
  <sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
@@ -418,24 +403,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
418
403
 
419
404
  <table>
420
405
  <tr>
421
- <td align="center"><strong>5,23 ms</strong><br/><sub>moyenne</sub></td>
422
- <td align="center"><strong>4,61 ms</strong><br/><sub>médiane</sub></td>
423
- <td align="center"><strong>0,10 ms</strong><br/><sub>min</sub></td>
424
- <td align="center"><strong>577,0 ms</strong><br/><sub>max</sub></td>
406
+ <td align="center"><strong>5,61 ms</strong><br/><sub>moyenne</sub></td>
407
+ <td align="center"><strong>5,03 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>705,8 ms</strong><br/><sub>max</sub></td>
425
410
  </tr>
426
411
  </table>
427
412
 
428
413
  | Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
429
414
  | :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
430
- | **Généraliste** | 4,37 ms | 3,96 ms | 0,22 ms | 35,5 ms | 7,93 ms | 10,8 ms |
431
- | **Appartement · chauffage individuel (2025)** | 3,49 ms | 3,20 ms | 1,13 ms | 30,2 ms | 6,09 ms | 7,79 ms |
432
- | **Logement individuel (2025)** | 4,96 ms | 4,38 ms | 1,34 ms | 43,1 ms | 9,48 ms | 12,9 ms |
433
- | **Maison individuelle (2025)** | 5,88 ms | 5,34 ms | 1,68 ms | 47,0 ms | 10,5 ms | 14,2 ms |
434
- | **Immeuble · chauffage individuel** | 6,66 ms | 5,73 ms | 0,10 ms | 83,7 ms | 13,6 ms | 21,0 ms |
435
- | **Appartement · chauffage collectif (2025)** | 4,09 ms | 3,75 ms | 1,26 ms | 37,7 ms | 6,88 ms | 9,51 ms |
436
- | **Immeuble · chauffage collectif** | 6,04 ms | 5,23 ms | 0,15 ms | 148,9 ms | 11,5 ms | 21,3 ms |
437
- | **Immeuble · chauffage mixte** | 5,79 ms | 4,77 ms | 0,18 ms | 577,0 ms | 12,4 ms | 20,2 ms |
438
- | **Individuel généré depuis l'immeuble (2026)** | 5,82 ms | 5,08 ms | 1,65 ms | 71,8 ms | 11,1 ms | 16,9 ms |
415
+ | **Généraliste** | 4,50 ms | 4,09 ms | 0,20 ms | 34,8 ms | 8,12 ms | 11,3 ms |
416
+ | **Appartement · chauffage individuel (2025)** | 3,71 ms | 3,46 ms | 1,32 ms | 26,1 ms | 6,22 ms | 7,99 ms |
417
+ | **Logement individuel (2025)** | 5,30 ms | 4,84 ms | 1,52 ms | 42,5 ms | 9,73 ms | 13,2 ms |
418
+ | **Maison individuelle (2025)** | 6,54 ms | 6,02 ms | 1,71 ms | 54,5 ms | 11,6 ms | 16,4 ms |
419
+ | **Immeuble · chauffage individuel** | 7,17 ms | 6,22 ms | 0,11 ms | 137,7 ms | 14,3 ms | 22,3 ms |
420
+ | **Appartement · chauffage collectif (2025)** | 3,98 ms | 3,76 ms | 1,22 ms | 37,6 ms | 6,35 ms | 8,00 ms |
421
+ | **Immeuble · chauffage collectif** | 6,40 ms | 5,58 ms | 0,15 ms | 101,0 ms | 12,1 ms | 21,2 ms |
422
+ | **Immeuble · chauffage mixte** | 6,01 ms | 5,01 ms | 0,22 ms | 705,8 ms | 12,4 ms | 19,8 ms |
423
+ | **Individuel généré depuis l'immeuble (2026)** | 6,89 ms | 6,27 ms | 1,52 ms | 65,1 ms | 13,0 ms | 19,7 ms |
439
424
 
440
425
  <sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
441
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
@@ -249,6 +249,7 @@ export default function calc_conso(
249
249
  null,
250
250
  prorataECS,
251
251
  prorataChauffage,
252
+ ecs,
252
253
  conso_aux_distribution_ecs_total
253
254
  ),
254
255
  ep_conso: calc_conso_pond(
@@ -262,6 +263,7 @@ export default function calc_conso(
262
263
  coeffEp,
263
264
  prorataECS,
264
265
  prorataChauffage,
266
+ ecs,
265
267
  conso_aux_distribution_ecs_total
266
268
  ),
267
269
  emission_ges: calc_conso_pond(
@@ -275,6 +277,7 @@ export default function calc_conso(
275
277
  coef_ges,
276
278
  prorataECS,
277
279
  prorataChauffage,
280
+ ecs,
278
281
  conso_aux_distribution_ecs_total
279
282
  ),
280
283
  cout: calc_conso_pond(
@@ -288,6 +291,7 @@ export default function calc_conso(
288
291
  coef_cout,
289
292
  prorataECS,
290
293
  prorataChauffage,
294
+ ecs,
291
295
  conso_aux_distribution_ecs_total
292
296
  )
293
297
  };
@@ -343,7 +347,8 @@ export default function calc_conso(
343
347
  '',
344
348
  null,
345
349
  prorataECS,
346
- prorataChauffage
350
+ prorataChauffage,
351
+ type_energie === 'électricité' ? ecs : []
347
352
  );
348
353
  conso_en = {
349
354
  conso_ch: conso_en._ch,
@@ -474,7 +479,7 @@ function calc_conso_pond(
474
479
  coef,
475
480
  prorataECS,
476
481
  prorataChauffage,
477
- conso_aux_distribution_ecs_total
482
+ ecs_installations
478
483
  ) {
479
484
  const ret = {};
480
485
  ret.auxiliaire_ventilation = vt_list.reduce((acc, vt) => {
@@ -538,11 +543,10 @@ function calc_conso_pond(
538
543
  return acc + getConso(coef, 'électricité auxiliaire', conso);
539
544
  }, 0);
540
545
 
541
- ret.auxiliaire_distribution_ecs = getConso(
542
- coef,
543
- 'électricité auxiliaire',
544
- conso_aux_distribution_ecs_total || 0
545
- );
546
+ ret.auxiliaire_distribution_ecs = ecs_installations.reduce((acc, inst) => {
547
+ const conso = inst.donnee_intermediaire.conso_auxiliaire_distribution_ecs || 0;
548
+ return acc + getConso(coef, 'électricité auxiliaire', conso);
549
+ }, 0);
546
550
 
547
551
  ret.ecs = getEcsConso(gen_ecs, 'conso_ecs', coef, prorataECS, prefix);
548
552
 
package/engine.js CHANGED
@@ -33,7 +33,7 @@ import getFicheTechnique from './ficheTechnique.js';
33
33
  import { ProductionENR } from './16.2_production_enr.js';
34
34
  import DpeSanitizerService from './dpe-sanitizer.service.js';
35
35
 
36
- const LIB_VERSION = '1.7.3';
36
+ const LIB_VERSION = '1.8.0';
37
37
 
38
38
  const dpeSanitizerService = new DpeSanitizerService();
39
39
 
@@ -437,7 +437,8 @@ export function calcul_3cl(inputDpe, options) {
437
437
  virtualisationECS,
438
438
  dpe.logement.caracteristique_generale.surface_habitable_immeuble,
439
439
  dpe.logement.caracteristique_generale.nombre_appartement,
440
- isImmeubleSystemEcsIndividuels
440
+ isImmeubleSystemEcsIndividuels,
441
+ apport_et_besoin.besoin_ecs_par_mois || {}
441
442
  );
442
443
 
443
444
  if (!dpeGenereImmeuble) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.7.3",
3
+ "version": "1.8.0",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -28,8 +28,9 @@
28
28
  "prepare": "husky install",
29
29
  "assets:sync": "node scripts/synchronize_assets.js",
30
30
  "version:update": "node scripts/update_version.js",
31
+ "reports:charts": "node scripts/generate_corpus_history_chart.js",
31
32
  "reports:preview": "npm run http-server dist/reports/corpus -o",
32
- "reports:readme": "node scripts/generate_corpus_readme.js"
33
+ "reports:readme": "node scripts/generate_corpus_readme.js && npm run reports:charts"
33
34
  },
34
35
  "repository": {
35
36
  "type": "git",