@open3cl/engine 1.10.0 → 1.11.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/15_conso_aux.js CHANGED
@@ -368,6 +368,24 @@ export function conso_aux_distribution_ecs(
368
368
  ) {
369
369
  const typeInstallation = parseInt(de.enum_type_installation_id);
370
370
 
371
+ const ratioVirtu = parseFloat(de.ratio_virtualisation);
372
+ let Sh_immeuble_ratio = Sh_immeuble;
373
+ let Sh_install = de.surface_habitable || Sh_logement;
374
+ let Niv_inst_ecs = de.nombre_niveau_installation_ecs || 1;
375
+
376
+ if (!(Sh_immeuble > Sh_logement) && ratioVirtu > 0 && ratioVirtu < 1) {
377
+ Sh_immeuble_ratio = Sh_logement / ratioVirtu;
378
+ Sh_install = Sh_logement;
379
+ Niv_inst_ecs = nombre_niveau_immeuble || Niv_inst_ecs;
380
+ }
381
+
382
+ // Etape 3: Lb - longueur par défaut du bouclage ECS (m)
383
+ // Sh est la surface habitable des logements desservis par l'installation d'ECS
384
+ // Pour l'appartement, on utilise la surface à l'échelle de l'immeuble
385
+ const Sh = (Sh_install / Sh_logement) * Sh_immeuble_ratio;
386
+ const Lb = 4 * Math.pow(Sh / Niv_inst_ecs, 0.5) + 6 * (Niv_inst_ecs - 0.5);
387
+ di.lb = Lb;
388
+
371
389
  if (typeInstallation === 1) {
372
390
  di.conso_auxiliaire_distribution_ecs = 0;
373
391
  return;
@@ -402,24 +420,6 @@ export function conso_aux_distribution_ecs(
402
420
  const ca = enums.classe_altitude[caId];
403
421
  const zc = enums.zone_climatique[zcId];
404
422
 
405
- const ratioVirtu = parseFloat(de.ratio_virtualisation);
406
- let Sh_immeuble_ratio = Sh_immeuble;
407
- let Sh_install = de.surface_habitable || Sh_logement;
408
- let Niv_inst_ecs = de.nombre_niveau_installation_ecs || 1;
409
-
410
- if (!(Sh_immeuble > Sh_logement) && ratioVirtu > 0 && ratioVirtu < 1) {
411
- Sh_immeuble_ratio = Sh_logement / ratioVirtu;
412
- Sh_install = Sh_logement;
413
- Niv_inst_ecs = nombre_niveau_immeuble || Niv_inst_ecs;
414
- }
415
-
416
- // Etape 3: Lb - longueur par défaut du bouclage ECS (m)
417
- // Sh est la surface habitable des logements desservis par l'installation d'ECS
418
- // Pour l'appartement, on utilise la surface à l'échelle de l'immeuble
419
- const Sh = (Sh_install / Sh_logement) * Sh_immeuble_ratio;
420
- const Lb = 4 * Math.pow(Sh / Niv_inst_ecs, 0.5) + 6 * (Niv_inst_ecs - 0.5);
421
- di.lb = Lb;
422
-
423
423
  // Etape 4: DeltaPb (kPa) - perte de charge dans le bouclage
424
424
  const DeltaPb = 0.2 * Lb + 10;
425
425
 
@@ -874,7 +874,7 @@ describe("conso_aux_distribution_ecs - auxiliaires de distribution d'ECS", () =>
874
874
  ])('%s : pas de longueur de bouclage lb', (_libelle, de) => {
875
875
  const di = { besoin_ecs: 1000 };
876
876
  conso_aux_distribution_ecs({}, de, di, 50, 2000, 1, 1, 30);
877
- expect(di.lb).toBeUndefined();
877
+ expect(di.lb).toBeCloseTo(181.88543819998318);
878
878
  });
879
879
 
880
880
  test('plus de niveaux desservis : bouclage plus court, donc consommation plus faible', () => {
package/9_chauffage.js CHANGED
@@ -41,6 +41,20 @@ export function isEmetteurAppointIndividuel(instChDe, idx) {
41
41
  );
42
42
  }
43
43
 
44
+ /**
45
+ * Longueur par défaut du réseau de l'installation de chauffage (m), même formule que la
46
+ * longueur de bouclage de l'installation ECS :
47
+ * Lb = 4 × (Sh / Niv)^0,5 + 6 × (Niv − 0,5)
48
+ * @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §15.2.3
49
+ * @param {number} surfaceInstallation surface chauffée par l'installation (m²)
50
+ * @param {number} nombreNiveau nombre de niveaux desservis par l'installation
51
+ * @returns {number}
52
+ */
53
+ export function calc_lb_installation_ch(surfaceInstallation, nombreNiveau) {
54
+ const niv = Number(nombreNiveau) || 1;
55
+ return 4 * Math.pow(surfaceInstallation / niv, 0.5) + 6 * (niv - 0.5);
56
+ }
57
+
44
58
  export default function calc_chauffage(
45
59
  dpe,
46
60
  ch,
@@ -67,6 +81,7 @@ export default function calc_chauffage(
67
81
 
68
82
  di.besoin_ch = bch;
69
83
  di.besoin_ch_depensier = bch_dep;
84
+ di.lb = calc_lb_installation_ch(de.surface_chauffee || Sh, de.nombre_niveau_installation_ch);
70
85
 
71
86
  const em_ch = ch.emetteur_chauffage_collection.emetteur_chauffage;
72
87
  em_ch.forEach((em_ch, idx) => {
@@ -62,7 +62,8 @@ const {
62
62
  generateursPrincipauxCascade,
63
63
  ratioDimensionnement,
64
64
  reseauCirculateurUnique,
65
- isEmetteurAppointIndividuel
65
+ isEmetteurAppointIndividuel,
66
+ calc_lb_installation_ch
66
67
  } = await import('./9_chauffage.js');
67
68
  const { requestInput, tv, tvColumnIDs } = await import('./utils.js');
68
69
  const { calc_emetteur_ch } = await import('./9_emetteur_ch.js');
@@ -185,6 +186,8 @@ describe('calc_chauffage - cas nominal (1 générateur, 1 émetteur)', () => {
185
186
  expect(ch.donnee_intermediaire).toStrictEqual({
186
187
  besoin_ch: 1000,
187
188
  besoin_ch_depensier: 1200,
189
+ // 4 × (100 / 1)^0,5 + 6 × (1 − 0,5) = 43
190
+ lb: 43,
188
191
  conso_ch: 100,
189
192
  conso_ch_depensier: 120
190
193
  });
@@ -237,6 +240,53 @@ describe('calc_chauffage - cas nominal (1 générateur, 1 émetteur)', () => {
237
240
  });
238
241
  });
239
242
 
243
+ /**
244
+ * Longueur du réseau de l'installation de chauffage, même formule que la longueur de
245
+ * bouclage ECS : Lb = 4 × (Sh / Niv)^0,5 + 6 × (Niv − 0,5)
246
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §15.2.3
247
+ */
248
+ describe('calc_lb_installation_ch - longueur du réseau de chauffage', () => {
249
+ test.each([
250
+ ['1 niveau, 100 m²', 100, 1, 43],
251
+ ['1 niveau, 50 m²', 50, 1, 31.284271247461902],
252
+ ['3 niveaux, 100 m²', 100, 3, 38.09401076758503]
253
+ ])('%s', (_libelle, surface, niveaux, attendu) => {
254
+ // valeurs de référence de régression
255
+ expect(calc_lb_installation_ch(surface, niveaux)).toBeCloseTo(attendu, 9);
256
+ });
257
+
258
+ test.each([undefined, 0, null])('nombre de niveaux %s : repli sur 1 niveau', (niveaux) => {
259
+ expect(calc_lb_installation_ch(100, niveaux)).toBe(43);
260
+ });
261
+
262
+ test('nombre de niveaux saisi en chaîne', () => {
263
+ expect(calc_lb_installation_ch(100, '3')).toBeCloseTo(38.09401076758503, 9);
264
+ });
265
+ });
266
+
267
+ describe('calc_chauffage - longueur lb dans la donnée intermédiaire de l’installation', () => {
268
+ test('surface chauffée et nombre de niveaux de l’installation utilisés', () => {
269
+ const ch = installation([generateur()], [emetteur('1')], {
270
+ surface_chauffee: 100,
271
+ nombre_niveau_installation_ch: 3
272
+ });
273
+ appel(ch, { Sh: 500 });
274
+ expect(ch.donnee_intermediaire.lb).toBeCloseTo(38.09401076758503, 9);
275
+ });
276
+
277
+ test('surface chauffée absente : repli sur la surface habitable Sh', () => {
278
+ const ch = installation([generateur()], [emetteur('1')], { surface_chauffee: 0 });
279
+ appel(ch, { Sh: 50 });
280
+ expect(ch.donnee_intermediaire.lb).toBeCloseTo(31.284271247461902, 9);
281
+ });
282
+
283
+ test('aucun impact sur le besoin transmis au générateur', () => {
284
+ const ch = installation([generateur()], [emetteur('1')], { nombre_niveau_installation_ch: 3 });
285
+ appel(ch);
286
+ expect(vi.mocked(calc_generateur_ch).mock.calls[0][5]).toBe(1000);
287
+ });
288
+ });
289
+
240
290
  describe('calc_chauffage - mise à l’échelle du besoin selon la surface', () => {
241
291
  test('besoin ajusté au prorata surface_chauffee / Sh quand elles diffèrent', () => {
242
292
  const ch = installation([generateur()], [emetteur('1')]);
package/README.md CHANGED
@@ -383,7 +383,7 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
383
383
 
384
384
  <!-- CORPUS:START -->
385
385
 
386
- > **Version `1.9.1`** · branche `main` · généré le 2026-10-26
386
+ > **Version `1.10.0`** · branche `main` · généré le 2026-10-29
387
387
  > Seuil de tolérance **5%**
388
388
 
389
389
  <table>
@@ -416,24 +416,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
416
416
 
417
417
  <table>
418
418
  <tr>
419
- <td align="center"><strong>6,66 ms</strong><br/><sub>moyenne</sub></td>
420
- <td align="center"><strong>5,79 ms</strong><br/><sub>médiane</sub></td>
421
- <td align="center"><strong>0,13 ms</strong><br/><sub>min</sub></td>
422
- <td align="center"><strong>911,0 ms</strong><br/><sub>max</sub></td>
419
+ <td align="center"><strong>5,64 ms</strong><br/><sub>moyenne</sub></td>
420
+ <td align="center"><strong>4,87 ms</strong><br/><sub>médiane</sub></td>
421
+ <td align="center"><strong>0,11 ms</strong><br/><sub>min</sub></td>
422
+ <td align="center"><strong>957,0 ms</strong><br/><sub>max</sub></td>
423
423
  </tr>
424
424
  </table>
425
425
 
426
426
  | Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
427
427
  | :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
428
- | **Appartement · chauffage individuel (2025)** | 5,04 ms | 4,55 ms | 1,68 ms | 58,6 ms | 8,75 ms | 11,6 ms |
429
- | **Appartement · chauffage collectif (2025)** | 5,96 ms | 5,55 ms | 2,00 ms | 56,7 ms | 9,64 ms | 12,0 ms |
430
- | **Logement individuel (2025)** | 6,17 ms | 5,46 ms | 1,75 ms | 60,4 ms | 11,5 ms | 16,1 ms |
431
- | **Maison individuelle (2025)** | 6,97 ms | 6,28 ms | 2,02 ms | 59,3 ms | 12,3 ms | 16,7 ms |
432
- | **Maison individuelle (2026)** | 7,06 ms | 6,30 ms | 2,00 ms | 45,5 ms | 12,6 ms | 17,6 ms |
433
- | **Immeuble · chauffage collectif** | 6,94 ms | 5,88 ms | 0,14 ms | 144,5 ms | 12,8 ms | 22,1 ms |
434
- | **Immeuble · chauffage individuel** | 7,88 ms | 6,68 ms | 0,13 ms | 172,2 ms | 15,7 ms | 25,4 ms |
435
- | **Immeuble · chauffage mixte** | 6,74 ms | 5,47 ms | 0,22 ms | 911,0 ms | 14,1 ms | 22,5 ms |
436
- | **Individuel généré depuis l'immeuble (2026)** | 7,21 ms | 5,97 ms | 1,81 ms | 91,1 ms | 13,7 ms | 22,1 ms |
428
+ | **Appartement · chauffage individuel (2025)** | 4,00 ms | 3,58 ms | 1,42 ms | 34,6 ms | 6,99 ms | 9,30 ms |
429
+ | **Appartement · chauffage collectif (2025)** | 4,46 ms | 4,04 ms | 1,62 ms | 42,4 ms | 7,33 ms | 9,33 ms |
430
+ | **Logement individuel (2025)** | 5,20 ms | 4,57 ms | 1,74 ms | 56,0 ms | 9,72 ms | 13,5 ms |
431
+ | **Maison individuelle (2025)** | 6,01 ms | 5,41 ms | 2,04 ms | 51,8 ms | 10,7 ms | 14,3 ms |
432
+ | **Maison individuelle (2026)** | 6,16 ms | 5,47 ms | 2,09 ms | 46,7 ms | 11,1 ms | 15,1 ms |
433
+ | **Immeuble · chauffage collectif** | 5,81 ms | 4,91 ms | 0,14 ms | 126,7 ms | 10,9 ms | 17,7 ms |
434
+ | **Immeuble · chauffage individuel** | 7,07 ms | 5,99 ms | 0,11 ms | 123,4 ms | 14,5 ms | 22,6 ms |
435
+ | **Immeuble · chauffage mixte** | 5,73 ms | 4,63 ms | 0,20 ms | 957,0 ms | 11,9 ms | 19,7 ms |
436
+ | **Individuel généré depuis l'immeuble (2026)** | 6,33 ms | 5,26 ms | 1,79 ms | 79,2 ms | 12,0 ms | 19,1 ms |
437
437
 
438
438
  <sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
439
439
  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
@@ -36,7 +36,7 @@ import DpeSanitizerService from './dpe-sanitizer.service.js';
36
36
  import { reconstituerRdimDpeGenereImmeuble } from './17.2_rdim_dpe_genere_immeuble.js';
37
37
  import { reconstituerRdimEcsIndividuelleUnique } from './11_rdim_ecs_individuelle_unique.js';
38
38
 
39
- const LIB_VERSION = '1.10.0';
39
+ const LIB_VERSION = '1.11.0';
40
40
 
41
41
  const dpeSanitizerService = new DpeSanitizerService();
42
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {