@open3cl/engine 1.9.0 → 1.9.1

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/4_ventilation.js CHANGED
@@ -38,6 +38,16 @@ export function isLigneQ4paMemePeriode(rowQ4paConv, cg) {
38
38
  .includes(String(cg.enum_periode_construction_id));
39
39
  }
40
40
 
41
+ /**
42
+ * Vrai si la ligne de la table `q4pa_conv` considère les surfaces isolées (`isolation_surfaces` = 1)
43
+ * alors que le recalcul à partir des parois les donne non isolées (`isolation_surfaces` = 0).
44
+ */
45
+ export function isLigneQ4paSurfacesIsolees(rowQ4paConv, isolationSurfacesRecalculee) {
46
+ return (
47
+ String(rowQ4paConv.isolation_surfaces) === '1' && String(isolationSurfacesRecalculee) === '0'
48
+ );
49
+ }
50
+
41
51
  function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) {
42
52
  const surfaces = mur_list.concat(ph_list);
43
53
  const surface_isolee = surfaces.reduce((acc, s) => {
@@ -108,10 +118,18 @@ function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) {
108
118
  * 1948, surfaces isolées », 2, alors que toutes les parois sont non isolées → ligne 1, 4,6).
109
119
  * Si la ligne saisie correspond à la période de construction du DPE, on la reprend en entier
110
120
  * (isolation et joints) pour reproduire le calcul du logiciel.
121
+ *
122
+ * Sens unique : la reprise ne s'applique que si la ligne indique des surfaces isolées alors que
123
+ * le recalcul les donne non isolées. Dans le sens inverse (ligne « non isolées », recalcul
124
+ * « isolées »), le DPE a pu être modifié par l'appelant (scénario d'isolation des parois) :
125
+ * la valeur recalculée est utilisée.
111
126
  * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4
112
127
  */
113
128
  if (rowQ4paConv.isolation_surfaces !== isolation_surfaces) {
114
- if (isLigneQ4paMemePeriode(rowQ4paConv, cg)) {
129
+ if (
130
+ isLigneQ4paSurfacesIsolees(rowQ4paConv, isolation_surfaces) &&
131
+ isLigneQ4paMemePeriode(rowQ4paConv, cg)
132
+ ) {
115
133
  console.error(
116
134
  `Calcul de hperm pour la ventilation ${de.description}. Le DPE considère isolation_surfaces = ${rowQ4paConv.isolation_surfaces} \n alors que ce devrait être isolation_surfaces = ${isolation_surfaces}. La ligne q4pa_conv ${de.tv_q4pa_conv_id} du DPE est conservée.`
117
135
  );
@@ -23,7 +23,8 @@ const {
23
23
  default: calc_ventilation,
24
24
  calc_hperm,
25
25
  presenceJointNonRenseignee,
26
- isLigneQ4paMemePeriode
26
+ isLigneQ4paMemePeriode,
27
+ isLigneQ4paSurfacesIsolees
27
28
  } = await import('./4_ventilation.js');
28
29
  const { tv } = await import('./utils.js');
29
30
  const { default: calc_pvent } = await import('./5_conso_ventilation.js');
@@ -522,25 +523,88 @@ describe('calc_ventilation - compatibilité bug_for_bug_compat activée', () =>
522
523
  errorSpy.mockRestore();
523
524
  });
524
525
 
525
- test('ligne 12 (non isolé, avec joints) reprise en entier, malgré des baies sans joint', async () => {
526
+ test('ligne isolée de la même période, joints absents de la ligne : joints « 0 » repris', async () => {
527
+ const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_10);
528
+ vi.spyOn(console, 'error').mockImplementation(() => {});
529
+ const baieAvecJoint = [{ donnee_entree: { surface_totale_baie: 10, presence_joint: 1 } }];
530
+
531
+ calc_ventilation_bug(vtLigne(10), CG, 'th', 80, 100, murNonIsole, [], [], baieAvecJoint);
532
+
533
+ // la ligne reprise prime sur les joints recalculés ('1')
534
+ expect(tvBug).toHaveBeenLastCalledWith(
535
+ 'q4pa_conv',
536
+ expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' })
537
+ );
538
+ });
539
+
540
+ test('ligne isolée de la même période avec joints : joints « 1 » repris malgré des baies sans joint', async () => {
526
541
  const { calc_ventilation_bug, tvBug } = await chargerAvecBug({
527
- q4pa_conv: '2.5',
528
- tv_q4pa_conv_id: '12',
529
- enum_periode_construction_id: '1',
530
- isolation_surfaces: '0',
542
+ ...ROW_10,
543
+ tv_q4pa_conv_id: '11',
531
544
  presence_joints_menuiserie: '1'
532
545
  });
533
546
  vi.spyOn(console, 'error').mockImplementation(() => {});
534
- const { murs } = surfacesIsolantes();
535
547
 
536
- calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baieSansJoint);
548
+ calc_ventilation_bug(vtLigne(11), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint);
537
549
 
538
550
  expect(tvBug).toHaveBeenLastCalledWith(
539
551
  'q4pa_conv',
540
- expect.objectContaining({ isolation_surfaces: '0', presence_joints_menuiserie: '1' })
552
+ expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '1' })
541
553
  );
542
554
  });
543
555
 
556
+ /**
557
+ * Sens unique : ligne « non isolées » alors que les parois sont isolées (cas d'un scénario de
558
+ * rénovation où l'appelant isole les murs du DPE). La ligne n'est pas reprise : l'isolation et
559
+ * les joints sont recalculés comme avant #312.
560
+ */
561
+ describe('ligne non isolée alors que les parois sont isolées (sens inverse)', () => {
562
+ const ROW_12 = {
563
+ q4pa_conv: '2.5',
564
+ tv_q4pa_conv_id: '12',
565
+ enum_periode_construction_id: '1',
566
+ isolation_surfaces: '0',
567
+ presence_joints_menuiserie: '1'
568
+ };
569
+
570
+ test('isolation recalculée utilisée et écart des joints signalé', async () => {
571
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
572
+ const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12);
573
+ const { murs } = surfacesIsolantes();
574
+
575
+ calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baieSansJoint);
576
+
577
+ expect(tvBug).toHaveBeenLastCalledWith(
578
+ 'q4pa_conv',
579
+ expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' })
580
+ );
581
+ expect(errorSpy).toHaveBeenCalledWith(
582
+ expect.stringContaining('La valeur recalculée isolation_surfaces = 1 est utilisée')
583
+ );
584
+ expect(errorSpy).toHaveBeenCalledWith(
585
+ expect.stringContaining(
586
+ 'La valeur recalculée presence_joints_menuiserie = 0 est utilisée'
587
+ )
588
+ );
589
+ expect(errorSpy).not.toHaveBeenCalledWith(expect.stringContaining('est conservée'));
590
+ errorSpy.mockRestore();
591
+ });
592
+
593
+ test('presence_joint non exporté : seuls les joints de la ligne du DPE sont repris', async () => {
594
+ vi.spyOn(console, 'error').mockImplementation(() => {});
595
+ const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12);
596
+ const { murs } = surfacesIsolantes();
597
+ const baies = [{ donnee_entree: { surface_totale_baie: 10 } }];
598
+
599
+ calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baies);
600
+
601
+ expect(tvBug).toHaveBeenLastCalledWith(
602
+ 'q4pa_conv',
603
+ expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '1' })
604
+ );
605
+ });
606
+ });
607
+
544
608
  test('ligne d’une autre période : la valeur recalculée est conservée', async () => {
545
609
  const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
546
610
  const { calc_ventilation_bug, tvBug } = await chargerAvecBug({
@@ -612,3 +676,19 @@ describe('isLigneQ4paMemePeriode', () => {
612
676
  expect(isLigneQ4paMemePeriode(null, { enum_periode_construction_id: '1' })).toBe(false);
613
677
  });
614
678
  });
679
+
680
+ describe('isLigneQ4paSurfacesIsolees', () => {
681
+ test('ligne « surfaces isolées » et recalcul « non isolées » : vrai', () => {
682
+ expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '1' }, '0')).toBe(true);
683
+ expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: 1 }, 0)).toBe(true);
684
+ });
685
+
686
+ test('ligne « non isolées » et recalcul « isolées » : faux (sens inverse)', () => {
687
+ expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '0' }, '1')).toBe(false);
688
+ });
689
+
690
+ test('valeurs concordantes ou ligne sans critère : faux', () => {
691
+ expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '1' }, '1')).toBe(false);
692
+ expect(isLigneQ4paSurfacesIsolees({}, '0')).toBe(false);
693
+ });
694
+ });
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.9.0';
38
+ const LIB_VERSION = '1.9.1';
39
39
 
40
40
  const dpeSanitizerService = new DpeSanitizerService();
41
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {