@open3cl/engine 1.8.10 → 1.8.11

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/3.2.1_mur.js CHANGED
@@ -8,9 +8,78 @@ import {
8
8
  getThicknessFromDescription
9
9
  } from './utils.js';
10
10
  import b from './3.1_b.js';
11
+ import tvs from './tv.js';
11
12
 
12
13
  const scriptName = new URL(import.meta.url).pathname.split('/').pop();
13
14
 
15
+ /**
16
+ * Ramène une épaisseur de structure sur une colonne de la table Umur0 du matériau déclaré.
17
+ *
18
+ * 3.2.1.2 Calcul des Umur0 : la valeur est lue dans le tableau du matériau de structure déclaré.
19
+ * Lorsque l'épaisseur saisie n'est pas une colonne de ce tableau (ex : 16 cm alors que la table
20
+ * propose 15 et 20), la recherche ne doit jamais basculer vers la ligne d'un autre matériau
21
+ * (cf. issue #138).
22
+ *
23
+ * La méthode ne précise pas la règle d'arrondi pour Umur0. Convention retenue (pratique des
24
+ * logiciels) : colonne la plus proche, et en cas d'égalité la colonne inférieure (la plus
25
+ * défavorable thermiquement). Les bornes ouvertes « ≤ x » / « ≥ x » sont ramenées à x.
26
+ *
27
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.2.1.2
28
+ *
29
+ * @param enumMateriauxStructureMurId {string|number}
30
+ * @param epaisseur {number|string}
31
+ * @returns {number|string} épaisseur d'une colonne de la table, ou la valeur initiale si la table
32
+ * ne propose pas d'épaisseur pour ce matériau ou si l'épaisseur n'est pas numérique
33
+ */
34
+ export function epaisseurStructureTable(enumMateriauxStructureMurId, epaisseur) {
35
+ const ep = typeof epaisseur === 'number' ? epaisseur : Number(epaisseur);
36
+ if (!Number.isFinite(ep) || ep <= 0) return epaisseur;
37
+
38
+ const colonnes = tvs.umur0
39
+ .filter(
40
+ (row) =>
41
+ String(row.enum_materiaux_structure_mur_id) === String(enumMateriauxStructureMurId) &&
42
+ row.epaisseur_structure
43
+ )
44
+ .map((row) => {
45
+ const valeur = String(row.epaisseur_structure);
46
+ return {
47
+ valeur: parseFloat(valeur.replace(/[≤≥\s]/g, '')),
48
+ inf: valeur.includes('≤'),
49
+ sup: valeur.includes('≥')
50
+ };
51
+ })
52
+ .filter((colonne) => Number.isFinite(colonne.valeur));
53
+
54
+ if (colonnes.length === 0) return epaisseur;
55
+
56
+ const colonneOuverte = colonnes.find(
57
+ (c) => c.valeur === ep || (c.inf && ep <= c.valeur) || (c.sup && ep >= c.valeur)
58
+ );
59
+ if (colonneOuverte) return colonneOuverte.valeur;
60
+
61
+ return colonnes.reduce((best, c) => {
62
+ const d = Math.abs(c.valeur - ep);
63
+ const dBest = Math.abs(best.valeur - ep);
64
+ return d < dBest || (d === dBest && c.valeur < best.valeur) ? c : best;
65
+ }).valeur;
66
+ }
67
+
68
+ /**
69
+ * Copie du matcher umur0 dont l'épaisseur est ramenée sur une colonne de la table du matériau.
70
+ * @param matcher {{enum_materiaux_structure_mur_id: string, epaisseur_structure?: number}}
71
+ */
72
+ function matcherEpaisseurTable(matcher) {
73
+ if (matcher.epaisseur_structure === undefined) return matcher;
74
+ return {
75
+ ...matcher,
76
+ epaisseur_structure: epaisseurStructureTable(
77
+ matcher.enum_materiaux_structure_mur_id,
78
+ matcher.epaisseur_structure
79
+ )
80
+ };
81
+ }
82
+
14
83
  function tv_umur0(di, de, du) {
15
84
  const matcher = {
16
85
  enum_materiaux_structure_mur_id: de.enum_materiaux_structure_mur_id
@@ -52,7 +121,7 @@ function tv_umur0(di, de, du) {
52
121
  }
53
122
  }
54
123
  }
55
- let row = tv('umur0', matcher);
124
+ let row = tv('umur0', matcherEpaisseurTable(matcher));
56
125
 
57
126
  if (bug_for_bug_compat) {
58
127
  if (
@@ -63,7 +132,7 @@ function tv_umur0(di, de, du) {
63
132
  'Mauvais valeur de umur0 trouvé pour une epaisseur > 80, la valeur va être recalculée avec une epaisseur / 10. Il arrive que la valeur soit en millimètres dans le dpe'
64
133
  );
65
134
  matcher.epaisseur_structure /= 10;
66
- row = tv('umur0', matcher);
135
+ row = tv('umur0', matcherEpaisseurTable(matcher));
67
136
  if (Number(du.tv_umur0_id_avant) !== Number(row.tv_umur0_id)) {
68
137
  row = undefined;
69
138
  }
package/3.2.1_mur.spec.js CHANGED
@@ -23,6 +23,25 @@ vi.mock('./3.1_b.js', () => ({
23
23
  default: vi.fn()
24
24
  }));
25
25
 
26
+ /**
27
+ * Table `umur0` réduite (mockée) pour `epaisseurStructureTable` : matériau 26 (ossature bois avec
28
+ * isolant en remplissage < 2001) et matériau 2 (bornes ouvertes « ≤ » / « ≥ »).
29
+ */
30
+ vi.mock('./tv.js', () => ({
31
+ default: {
32
+ umur0: [
33
+ { tv_umur0_id: '1', enum_materiaux_structure_mur_id: '1', umur0: '2.5' },
34
+ { tv_umur0_id: '2', enum_materiaux_structure_mur_id: '2', epaisseur_structure: '≤ 20' },
35
+ { tv_umur0_id: '3', enum_materiaux_structure_mur_id: '2', epaisseur_structure: '25' },
36
+ { tv_umur0_id: '4', enum_materiaux_structure_mur_id: '2', epaisseur_structure: '≥ 30' },
37
+ { tv_umur0_id: '144', enum_materiaux_structure_mur_id: '26', epaisseur_structure: '10' },
38
+ { tv_umur0_id: '145', enum_materiaux_structure_mur_id: '26', epaisseur_structure: '15' },
39
+ { tv_umur0_id: '146', enum_materiaux_structure_mur_id: '26', epaisseur_structure: '20' },
40
+ { tv_umur0_id: '151', enum_materiaux_structure_mur_id: '26', epaisseur_structure: '≥ 45' }
41
+ ]
42
+ }
43
+ }));
44
+
26
45
  vi.mock('./utils.js', () => ({
27
46
  set_bug_for_bug_compat: vi.fn(),
28
47
  tv: vi.fn(),
@@ -35,7 +54,7 @@ vi.mock('./utils.js', () => ({
35
54
  }
36
55
  }));
37
56
 
38
- const { default: calc_mur } = await import('./3.2.1_mur.js');
57
+ const { default: calc_mur, epaisseurStructureTable } = await import('./3.2.1_mur.js');
39
58
  const { tv, getThicknessFromDescription } = await import('./utils.js');
40
59
 
41
60
  /** Table `umur0` figée : U0 forfaitaire de la paroi nue. */
@@ -924,3 +943,66 @@ describe('calc_mur - doublage et isolation ITE/ITI (#146)', () => {
924
943
  expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_TABLE, 9);
925
944
  });
926
945
  });
946
+
947
+ /**
948
+ * 3.2.1.2 Calcul des Umur0 : l'épaisseur saisie est ramenée sur une colonne de la table du
949
+ * matériau déclaré, sans jamais basculer vers un autre matériau (issue #138).
950
+ * Convention (la méthode ne précise pas l'arrondi) : colonne la plus proche, égalité => inférieure.
951
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.2.1.2
952
+ */
953
+ describe('epaisseurStructureTable - épaisseur hors table (issue #138)', () => {
954
+ test.each([
955
+ { epaisseur: 16, attendu: 15, cas: '16 cm => colonne 15 (DPE 2673E0055842U)' },
956
+ { epaisseur: 18, attendu: 20, cas: '18 cm => colonne 20 (la plus proche)' },
957
+ { epaisseur: 12, attendu: 10, cas: '12 cm => colonne 10' },
958
+ { epaisseur: 17.5, attendu: 15, cas: '17,5 cm (égalité) => colonne inférieure 15' },
959
+ { epaisseur: 8, attendu: 10, cas: 'sous la borne basse => première colonne' },
960
+ { epaisseur: 15, attendu: 15, cas: 'colonne exacte inchangée' },
961
+ { epaisseur: 50, attendu: 45, cas: 'au-delà de « ≥ 45 » => 45' },
962
+ { epaisseur: 40, attendu: 45, cas: '40 cm => « ≥ 45 » plus proche que 20' },
963
+ { epaisseur: '16', attendu: 15, cas: 'épaisseur fournie en chaîne' }
964
+ ])('matériau 26 : $cas', ({ epaisseur, attendu }) => {
965
+ expect(epaisseurStructureTable('26', epaisseur)).toBe(attendu);
966
+ });
967
+
968
+ test.each([
969
+ { epaisseur: 12, attendu: 20, cas: '« ≤ 20 » couvre toutes les épaisseurs inférieures' },
970
+ { epaisseur: 17.5, attendu: 20, cas: 'épaisseur décimale sous « ≤ 20 »' },
971
+ { epaisseur: 22, attendu: 20, cas: '22 cm => 20 (plus proche que 25)' },
972
+ { epaisseur: 24, attendu: 25, cas: '24 cm => 25' },
973
+ { epaisseur: 98, attendu: 30, cas: '« ≥ 30 » couvre toutes les épaisseurs supérieures' }
974
+ ])('matériau 2 (bornes ouvertes) : $cas', ({ epaisseur, attendu }) => {
975
+ expect(epaisseurStructureTable('2', epaisseur)).toBe(attendu);
976
+ });
977
+
978
+ test.each([
979
+ { materiau: '1', epaisseur: 30, cas: 'matériau sans épaisseur dans la table' },
980
+ { materiau: '99', epaisseur: 30, cas: 'matériau absent de la table' },
981
+ { materiau: '26', epaisseur: 0, cas: 'épaisseur nulle' },
982
+ { materiau: '26', epaisseur: 'abc', cas: 'épaisseur non numérique' }
983
+ ])('valeur inchangée : $cas', ({ materiau, epaisseur }) => {
984
+ expect(epaisseurStructureTable(materiau, epaisseur)).toBe(epaisseur);
985
+ });
986
+
987
+ test('calc_mur : le matcher umur0 reçoit la colonne de la table du matériau déclaré', () => {
988
+ const mur = {
989
+ donnee_entree: {
990
+ methode_saisie_u: 'non isolé',
991
+ methode_saisie_u0:
992
+ 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
993
+ enum_materiaux_structure_mur_id: '26',
994
+ epaisseur_structure: 16,
995
+ enum_type_doublage_id: '2'
996
+ }
997
+ };
998
+
999
+ calc_mur(mur, 'h1a', '1', '0');
1000
+
1001
+ expect(tv).toHaveBeenCalledWith('umur0', {
1002
+ enum_materiaux_structure_mur_id: '26',
1003
+ epaisseur_structure: 15
1004
+ });
1005
+ // L'épaisseur saisie n'est pas modifiée dans les données d'entrée
1006
+ expect(mur.donnee_entree.epaisseur_structure).toBe(16);
1007
+ });
1008
+ });
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.8.10';
36
+ const LIB_VERSION = '1.8.11';
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.10",
3
+ "version": "1.8.11",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {