@open3cl/engine 1.8.46 → 1.8.47

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.
@@ -256,8 +256,18 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
256
256
 
257
257
  de.tv_generateur_combustion_id = Number(row.tv_generateur_combustion_id);
258
258
 
259
- const E = E_tab[de.presence_ventouse];
260
- const F = F_tab[de.presence_ventouse];
259
+ /**
260
+ * Les coefficients (E, F) dépendent de la présence d'un ventilateur ou autre dispositif de
261
+ * circulation d'air dans le circuit de combustion, et non de la ventouse (qui n'intervient que
262
+ * dans Cper). Le XML ne transporte pas cette donnée : elle est lue dans la fiche technique du
263
+ * générateur. Sans fiche technique, la ventouse est conservée comme approximation (choix
264
+ * Open3CL, la méthode ne prévoyant pas de valeur par défaut).
265
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2
266
+ * @see : Tribu Calcul_generateur.cs (ventilateur_combustion)
267
+ */
268
+ const indexEF = de.ventilateurCombustion ?? de.presence_ventouse;
269
+ const E = E_tab[indexEF];
270
+ const F = F_tab[indexEF];
261
271
 
262
272
  let ratioVirtualisation = de.ratio_virtualisation || 1;
263
273
 
@@ -341,7 +351,7 @@ export function updateGenerateurCombustion(dpe, de, type) {
341
351
  updateGenerateurBouilleurs(dpe, de, type);
342
352
  updateGenerateurChaudieres(dpe, de, type);
343
353
  updateGenerateurPacs(dpe, de, type);
344
- addInfosFromFichesTechniques(dpe, de);
354
+ addInfosFromFichesTechniques(dpe, de, type);
345
355
  }
346
356
 
347
357
  /**
@@ -349,12 +359,14 @@ export function updateGenerateurCombustion(dpe, de, type) {
349
359
  *
350
360
  * @param dpe {FullDpe}
351
361
  * @param de {Donnee_entree}
362
+ * @param type {'ch'|'ecs'}
352
363
  */
353
- function addInfosFromFichesTechniques(dpe, de) {
364
+ function addInfosFromFichesTechniques(dpe, de, type) {
354
365
  // Récupération de la présence ou non d'un système de ventilation
366
+ // Fiches techniques : catégorie 7 pour le chauffage, catégorie 8 pour l'ECS
355
367
  const ficheTechnique = getFicheTechnique(
356
368
  dpe,
357
- '8',
369
+ type === 'ch' ? '7' : '8',
358
370
  'Présence ventilateur / dispositif circulation air dans circuit combustion',
359
371
  [de.description]
360
372
  )?.valeur;
@@ -362,6 +374,11 @@ function addInfosFromFichesTechniques(dpe, de) {
362
374
  if (ficheTechnique && ficheTechnique === 'oui') {
363
375
  de.presenceVentilateur = 1;
364
376
  }
377
+
378
+ // Présence d'un ventilateur dans le circuit de combustion (choix des coefficients E, F de qp0)
379
+ if (ficheTechnique === 'oui' || ficheTechnique === 'non') {
380
+ de.ventilateurCombustion = ficheTechnique === 'oui' ? 1 : 0;
381
+ }
365
382
  }
366
383
 
367
384
  /**
@@ -833,6 +833,108 @@ describe('updateGenerateurCombustion - orchestration des substitutions', () => {
833
833
  updateGenerateurCombustion({}, de, 'ch');
834
834
  expect(de.presenceVentilateur).toBeUndefined();
835
835
  });
836
+
837
+ /**
838
+ * Issue #35 : la fiche technique d'un générateur de chauffage est en catégorie 7, celle d'un
839
+ * générateur ECS en catégorie 8.
840
+ */
841
+ test('générateur de chauffage : fiche technique recherchée en catégorie 7 (#35)', () => {
842
+ getFicheTechnique.mockReturnValue(undefined);
843
+ const dpe = {};
844
+ updateGenerateurCombustion(dpe, { description: 'chaudière' }, 'ch');
845
+ expect(getFicheTechnique).toHaveBeenCalledWith(
846
+ dpe,
847
+ '7',
848
+ 'Présence ventilateur / dispositif circulation air dans circuit combustion',
849
+ ['chaudière']
850
+ );
851
+ });
852
+
853
+ test('générateur ECS : fiche technique recherchée en catégorie 8 (#35)', () => {
854
+ getFicheTechnique.mockReturnValue(undefined);
855
+ const dpe = {};
856
+ updateGenerateurCombustion(dpe, { description: 'chauffe-eau' }, 'ecs');
857
+ expect(getFicheTechnique).toHaveBeenCalledWith(
858
+ dpe,
859
+ '8',
860
+ 'Présence ventilateur / dispositif circulation air dans circuit combustion',
861
+ ['chauffe-eau']
862
+ );
863
+ });
864
+
865
+ test('ventilateur de combustion renseigné à 1 si la fiche indique « oui » (#35)', () => {
866
+ getFicheTechnique.mockReturnValue({ valeur: 'oui' });
867
+ const de = { description: 'gen' };
868
+ updateGenerateurCombustion({}, de, 'ch');
869
+ expect(de.ventilateurCombustion).toBe(1);
870
+ });
871
+
872
+ test('ventilateur de combustion renseigné à 0 si la fiche indique « non » (#35)', () => {
873
+ getFicheTechnique.mockReturnValue({ valeur: 'non' });
874
+ const de = { description: 'gen' };
875
+ updateGenerateurCombustion({}, de, 'ch');
876
+ expect(de.ventilateurCombustion).toBe(0);
877
+ });
878
+
879
+ test('ventilateur de combustion non renseigné sans fiche ou valeur inconnue (#35)', () => {
880
+ getFicheTechnique.mockReturnValue(undefined);
881
+ const deSansFiche = { description: 'gen' };
882
+ updateGenerateurCombustion({}, deSansFiche, 'ch');
883
+ expect(deSansFiche.ventilateurCombustion).toBeUndefined();
884
+
885
+ getFicheTechnique.mockReturnValue({ valeur: 'inconnu' });
886
+ const deInconnu = { description: 'gen' };
887
+ updateGenerateurCombustion({}, deInconnu, 'ch');
888
+ expect(deInconnu.ventilateurCombustion).toBeUndefined();
889
+ });
890
+ });
891
+
892
+ /**
893
+ * Issue #35 : les coefficients (E, F) de la formule qp0 dépendent de la présence d'un ventilateur
894
+ * dans le circuit de combustion (fiche technique), la ventouse n'étant qu'un repli.
895
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2
896
+ */
897
+ describe('tv_generateur_combustion - coefficients (E, F) selon le ventilateur de combustion (#35)', () => {
898
+ const ROW_EF = { ...ROW_DEFAUT, rpn: 'E*40', qp0_perc: 'Pn*(E+F*log10(Pn))/100' };
899
+
900
+ test('ventouse mais pas de ventilateur : couple (E, F) sans ventilateur (2,5 ; -0,8)', () => {
901
+ tv.mockReturnValue({ ...ROW_EF });
902
+ const di = { pn: 24000 };
903
+ const de = {
904
+ enum_type_generateur_ch_id: '93',
905
+ ratio_virtualisation: 1,
906
+ presence_ventouse: 1,
907
+ ventilateurCombustion: 0
908
+ };
909
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
910
+ // E = 2.5 => rpn = 2.5*40/100 = 1
911
+ expect(di.rpn).toBeCloseTo(1, 9);
912
+ // qp0 = 24000 * (2.5 - 0.8 * log10(24)) / 100 -- valeur du DPE 2459E3195669T
913
+ expect(di.qp0).toBeCloseTo(334.99944159137164, 9);
914
+ });
915
+
916
+ test('ventilateur sans ventouse : couple (E, F) avec ventilateur (1,75 ; -0,55)', () => {
917
+ tv.mockReturnValue({ ...ROW_EF });
918
+ const di = { pn: 24000 };
919
+ const de = {
920
+ enum_type_generateur_ch_id: '93',
921
+ ratio_virtualisation: 1,
922
+ presence_ventouse: 0,
923
+ ventilateurCombustion: 1
924
+ };
925
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
926
+ expect(di.rpn).toBeCloseTo(0.7, 9);
927
+ // qp0 = 24000 * (1.75 - 0.55 * log10(24)) / 100 -- valeur de référence de régression
928
+ expect(di.qp0).toBeCloseTo(237.81211609406802, 9);
929
+ });
930
+
931
+ test('sans information de ventilateur : repli sur la présence de ventouse', () => {
932
+ tv.mockReturnValue({ ...ROW_EF });
933
+ const di = { pn: 24000 };
934
+ const de = { enum_type_generateur_ch_id: '93', ratio_virtualisation: 1, presence_ventouse: 1 };
935
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
936
+ expect(di.qp0).toBeCloseTo(237.81211609406802, 9);
937
+ });
836
938
  });
837
939
 
838
940
  /**
package/README.md CHANGED
@@ -370,7 +370,7 @@ 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.45`** · branche `main` · généré le 2026-10-10
373
+ > **Version `1.8.46`** · branche `main` · généré le 2026-10-11
374
374
  > Seuil de tolérance **5%**
375
375
 
376
376
  <table>
@@ -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,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>
406
+ <td align="center"><strong>5,63 ms</strong><br/><sub>moyenne</sub></td>
407
+ <td align="center"><strong>5,00 ms</strong><br/><sub>médiane</sub></td>
408
+ <td align="center"><strong>0,14 ms</strong><br/><sub>min</sub></td>
409
+ <td align="center"><strong>601,2 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,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 |
415
+ | **Généraliste** | 5,37 ms | 4,86 ms | 0,26 ms | 97,3 ms | 9,51 ms | 14,0 ms |
416
+ | **Appartement · chauffage individuel (2025)** | 4,70 ms | 4,36 ms | 1,56 ms | 36,9 ms | 7,96 ms | 11,1 ms |
417
+ | **Logement individuel (2025)** | 4,99 ms | 4,45 ms | 1,68 ms | 43,5 ms | 9,03 ms | 12,5 ms |
418
+ | **Maison individuelle (2025)** | 6,20 ms | 5,63 ms | 1,90 ms | 54,9 ms | 10,8 ms | 14,3 ms |
419
+ | **Immeuble · chauffage individuel** | 6,48 ms | 5,52 ms | 0,14 ms | 89,1 ms | 13,0 ms | 21,1 ms |
420
+ | **Appartement · chauffage collectif (2025)** | 5,12 ms | 4,90 ms | 1,79 ms | 42,1 ms | 8,10 ms | 12,6 ms |
421
+ | **Immeuble · chauffage collectif** | 6,73 ms | 5,99 ms | 0,20 ms | 171,0 ms | 12,4 ms | 21,7 ms |
422
+ | **Immeuble · chauffage mixte** | 5,17 ms | 4,24 ms | 0,19 ms | 601,2 ms | 10,5 ms | 16,5 ms |
423
+ | **Individuel généré depuis l'immeuble (2026)** | 5,87 ms | 5,04 ms | 1,69 ms | 48,6 ms | 11,0 ms | 16,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/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.46';
36
+ const LIB_VERSION = '1.8.47';
37
37
 
38
38
  const dpeSanitizerService = new DpeSanitizerService();
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.8.46",
3
+ "version": "1.8.47",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {