@open3cl/engine 1.4.0 → 1.4.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/README.md CHANGED
@@ -85,7 +85,7 @@ C'est un bon moyen de détecter un éventuel problème dans le dpe ou la librair
85
85
  ```javascript
86
86
  import { calcul_3cl } from 'open3cl';
87
87
 
88
- // Exemple d'objet JSON issu d'un fichier XML DPE
88
+ // Exemple d'objet JSON (partiel) issu d'un fichier XML DPE
89
89
  const dpeData = {
90
90
  numero_dpe: '2113E1018248X',
91
91
  statut: 'ACTIF',
@@ -108,7 +108,23 @@ const dpeData = {
108
108
  }
109
109
  };
110
110
 
111
+ // Execution d'un dpe avec la librairie Open3CL avec pré-transformation / nettoyage du dpe (comportement par défaut)
111
112
  const result = calcul_3cl(dpeData);
113
+ const result = calcul_3cl(dpeData, { sanitize: true });
114
+
115
+ // Execution d'un dpe avec la librairie Open3CL sans pré-transformation / nettoyage du dpe
116
+ const result = calcul_3cl(dpeData, { sanitize: false });
117
+
118
+ // Execution d'un dpe au format xml avec la librairie Open3CL avec pré-transformation / nettoyage du dpe (comportement par défaut)
119
+ const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>');
120
+ const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>', {
121
+ sanitize: true
122
+ });
123
+
124
+ // Execution d'un dpe au format xml avec la librairie Open3CL sans pré-transformation / nettoyage du dpe (comportement par défaut)
125
+ const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>', {
126
+ sanitize: false
127
+ });
112
128
  ```
113
129
 
114
130
  ## Variables d'environnements
@@ -252,27 +268,35 @@ Résultats des tests de corpus avec le mode de compatibilité activé.
252
268
  | 1.3.20 | dpe_immeuble_chauffage_individuel.csv | 7106 | 71% | |
253
269
  | 1.3.20 | dpe_immeuble_chauffage_collectif.csv | 6083 | 61% | |
254
270
  | 1.3.20 | dpe_immeuble_chauffage_mixte.csv | 4751 | 47% | |
271
+ | <ins>**1.3.21**<ins> | <ins>**corpus_dpe.csv**<ins> | <ins>**4502**<ins> (+2) | <ins>**45%**<ins> | |
272
+ | 1.3.21 | dpe_logement_individuel_2025.csv | 8504 (+45) | 85% (+1%) | |
273
+ | 1.3.21 | dpe_maison_individuelle_2025.csv | 8652 (+14) | 86% | |
274
+ | 1.3.21 | dpe_appartement_individuel_chauffage_individuel_2025.csv | 8578 (+17) | 86% | |
275
+ | 1.3.21 | dpe_appartement_individuel_chauffage_collectif_2025.csv | 6681 | 67% | |
276
+ | 1.3.21 | dpe_immeuble_chauffage_individuel.csv | 7144 (+38) | 71% | |
277
+ | 1.3.21 | dpe_immeuble_chauffage_collectif.csv | 6144 (+61) | 61% | |
278
+ | 1.3.21 | dpe_immeuble_chauffage_mixte.csv | 4760 (+9) | 47% | |
255
279
 
256
280
  </details>
257
281
 
258
- | Version librairie | corpus | Nb en dessous du taux d'erreur | Taux de réussite | Description |
259
- | :------------------- | -------------------------------------------------------- | ------------------------------ | ----------------- | ----------------------------- |
260
- | <ins>**1.3.21**<ins> | <ins>**corpus_dpe.csv**<ins> | <ins>**4502**<ins> (+2) | <ins>**45%**<ins> | |
261
- | 1.3.21 | dpe_logement_individuel_2025.csv | 8504 (+45) | 85% (+1%) | |
262
- | 1.3.21 | dpe_maison_individuelle_2025.csv | 8652 (+14) | 86% | |
263
- | 1.3.21 | dpe_appartement_individuel_chauffage_individuel_2025.csv | 8578 (+17) | 86% | |
264
- | 1.3.21 | dpe_appartement_individuel_chauffage_collectif_2025.csv | 6681 | 67% | |
265
- | 1.3.21 | dpe_immeuble_chauffage_individuel.csv | 7144 (+38) | 71% | |
266
- | 1.3.21 | dpe_immeuble_chauffage_collectif.csv | 6144 (+61) | 61% | |
267
- | 1.3.21 | dpe_immeuble_chauffage_mixte.csv | 4760 (+9) | 47% | |
268
- | <ins>**1.3.25**<ins> | <ins>**corpus_dpe.csv**<ins> | <ins>**4515**<ins> (+2) | <ins>**45%**<ins> | |
269
- | 1.3.25 | dpe_logement_individuel_2025.csv | 8512 (+8) | 85% | |
270
- | 1.3.25 | dpe_maison_individuelle_2025.csv | 8674 (+22) | 87% (+1%) | |
271
- | 1.3.25 | dpe_appartement_individuel_chauffage_individuel_2025.csv | 8581 (+3) | 86% | |
272
- | 1.3.25 | dpe_appartement_individuel_chauffage_collectif_2025.csv | 6680 (-1) | 67% | Dpe erronés enlevés du corpus |
273
- | 1.3.25 | dpe_immeuble_chauffage_individuel.csv | 7142 (-2) | 71% | Dpe erronés enlevés du corpus |
274
- | 1.3.25 | dpe_immeuble_chauffage_collectif.csv | 6144 | 61% | |
275
- | 1.3.25 | dpe_immeuble_chauffage_mixte.csv | 4758 (-2) | 47% | Dpe erronés enlevés du corpus |
282
+ | Version librairie | corpus | Nb en dessous du taux d'erreur | Taux de réussite | Description |
283
+ | :------------------- | -------------------------------------------------------- | ------------------------------ | ----------------- | ------------------------------------ |
284
+ | <ins>**1.3.25**<ins> | <ins>**corpus_dpe.csv**<ins> | <ins>**4515**<ins> (+2) | <ins>**45%**<ins> | |
285
+ | 1.3.25 | dpe_logement_individuel_2025.csv | 8512 (+8) | 85% | |
286
+ | 1.3.25 | dpe_maison_individuelle_2025.csv | 8674 (+22) | 87% (+1%) | |
287
+ | 1.3.25 | dpe_appartement_individuel_chauffage_individuel_2025.csv | 8581 (+3) | 86% | |
288
+ | 1.3.25 | dpe_appartement_individuel_chauffage_collectif_2025.csv | 6680 (-1) | 67% | Dpe erronés enlevés du corpus |
289
+ | 1.3.25 | dpe_immeuble_chauffage_individuel.csv | 7142 (-2) | 71% | Dpe erronés enlevés du corpus |
290
+ | 1.3.25 | dpe_immeuble_chauffage_collectif.csv | 6144 | 61% | |
291
+ | 1.3.25 | dpe_immeuble_chauffage_mixte.csv | 4758 (-2) | 47% | Dpe erronés enlevés du corpus |
292
+ | <ins>**1.4.0**<ins> | <ins>**corpus_dpe.csv**<ins> | <ins>**4515**<ins> | <ins>**45%**<ins> | Maj coeff electricité (janvier 2026) |
293
+ | 1.4.0 | dpe_logement_individuel_2025.csv | 8512 | 85% | Maj coeff electricité (janvier 2026) |
294
+ | 1.4.0 | dpe_maison_individuelle_2025.csv | 8674 | 87% | Maj coeff electricité (janvier 2026) |
295
+ | 1.4.0 | dpe_appartement_individuel_chauffage_individuel_2025.csv | 8581 | 86% | Maj coeff electricité (janvier 2026) |
296
+ | 1.4.0 | dpe_appartement_individuel_chauffage_collectif_2025.csv | 6680 | 67% | Maj coeff electricité (janvier 2026) |
297
+ | 1.4.0 | dpe_immeuble_chauffage_individuel.csv | 7142 | 71% | Maj coeff electricité (janvier 2026) |
298
+ | 1.4.0 | dpe_immeuble_chauffage_collectif.csv | 6144 | 61% | Maj coeff electricité (janvier 2026) |
299
+ | 1.4.0 | dpe_immeuble_chauffage_mixte.csv | 4758 | 47% | Maj coeff electricité (janvier 2026) |
276
300
 
277
301
  ## Roadmap
278
302
 
@@ -308,7 +332,7 @@ Nous accueillons les contributions avec plaisir ! Si vous souhaitez améliorer O
308
332
 
309
333
  ## Licence
310
334
 
311
- Distribué sous la license `GPL-3.0 license`. Lire le fichier `LICENSE` pour plus d'informations.
335
+ Distribué sous la license `MIT`. Lire le fichier `LICENSE` pour plus d'informations.
312
336
 
313
337
  <p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
314
338
 
@@ -0,0 +1,90 @@
1
+ import { ObjectUtil } from './core/util/infrastructure/object-util.js';
2
+
3
+ const nodesToMap = [
4
+ 'mur',
5
+ 'plancher_bas',
6
+ 'plancher_haut',
7
+ 'baie_vitree',
8
+ 'porte',
9
+ 'pont_thermique',
10
+ 'ventilation',
11
+ 'installation_ecs',
12
+ 'generateur_ecs',
13
+ 'climatisation',
14
+ 'installation_chauffage',
15
+ 'generateur_chauffage',
16
+ 'emetteur_chauffage',
17
+ 'sortie_par_energie'
18
+ ];
19
+
20
+ /**
21
+ * Transform single nodes in {@link nodesToMap} into array of nodes.
22
+ * Transform string number into digits
23
+ * These transformations should be done inside the open3cl library
24
+ *
25
+ * @example
26
+ * // Will transform
27
+ * "plancher_haut_collection": {
28
+ * "plancher_haut": {"id": 1}
29
+ * }
30
+ * // Into
31
+ * "plancher_haut_collection": {
32
+ * "plancher_haut": [{"id": 1}]
33
+ * }
34
+ *
35
+ * @example
36
+ * // Will transform
37
+ * "surface_paroi_opaque": "40.94"
38
+ * // Into
39
+ * "surface_paroi_opaque": 40.94
40
+ */
41
+ export default class DpeSanitizerService {
42
+ /**
43
+ * @param dpe {FullDpe}
44
+ * @return {FullDpe}
45
+ */
46
+ execute(dpe) {
47
+ return ObjectUtil.deepObjectTransform(
48
+ dpe,
49
+ (key) => key,
50
+ (val, key) => {
51
+ if (this.#needTransform(key, val)) {
52
+ return [val];
53
+ }
54
+
55
+ if (this.#isEnum(key)) {
56
+ return val;
57
+ }
58
+
59
+ if (this.#isUndefinedVal(val)) {
60
+ return '';
61
+ }
62
+
63
+ if (this.#isEmptyArray(val)) {
64
+ return val;
65
+ }
66
+
67
+ if (Number.isNaN(Number(val))) {
68
+ return val;
69
+ }
70
+ return Number(val);
71
+ }
72
+ );
73
+ }
74
+
75
+ #isEnum(key) {
76
+ return key.startsWith('enum_') || key.startsWith('original_enum');
77
+ }
78
+
79
+ #needTransform(key, val) {
80
+ return typeof val === 'object' && !Array.isArray(val) && nodesToMap.includes(key);
81
+ }
82
+
83
+ #isUndefinedVal(val) {
84
+ return val === '' || val === null;
85
+ }
86
+
87
+ #isEmptyArray(val) {
88
+ return Array.isArray(val) && val.length === 0;
89
+ }
90
+ }
package/engine.js CHANGED
@@ -19,13 +19,16 @@ import {
19
19
  collectionCanBeEmpty,
20
20
  containsAnySubstring,
21
21
  isEffetJoule,
22
- sanitize_dpe
22
+ xmlParser
23
23
  } from './utils.js';
24
24
  import { Inertie } from './7_inertie.js';
25
25
  import getFicheTechnique from './ficheTechnique.js';
26
26
  import { ProductionENR } from './16.2_production_enr.js';
27
+ import DpeSanitizerService from './dpe-sanitizer.service.js';
27
28
 
28
- const LIB_VERSION = '1.4.0';
29
+ const LIB_VERSION = '1.4.1';
30
+
31
+ const dpeSanitizerService = new DpeSanitizerService();
29
32
 
30
33
  function calc_th(map_id) {
31
34
  const map = enums.methode_application_dpe_log[map_id];
@@ -44,11 +47,26 @@ export function getVersion() {
44
47
  }
45
48
 
46
49
  /**
47
- * @param dpe {FullDpe}
50
+ * Run the engine with a full dpe xml content
51
+ * @param dpeXmlContent {string} A full dpe xml content
52
+ * @param options {{sanitize: boolean}?}
53
+ * @return {FullDpe}
54
+ */
55
+ export function calcul_3cl_xml(dpeXmlContent, options) {
56
+ /** @type {{dpe: FullDpe}} **/
57
+ const xmlDpe = xmlParser.parse(dpeXmlContent);
58
+ return calcul_3cl(xmlDpe.dpe, options);
59
+ }
60
+
61
+ /**
62
+ * Run the engine with a javascript plain object dpe
63
+ * @param inputDpe {FullDpe} A full plain object dpe
64
+ * @param options {{sanitize: boolean}?}
48
65
  * @return {FullDpe}
49
66
  */
50
- export function calcul_3cl(dpe) {
51
- sanitize_dpe(dpe);
67
+ export function calcul_3cl(inputDpe, options) {
68
+ if (!options) options = { sanitize: true };
69
+ const dpe = options.sanitize ? dpeSanitizerService.execute(inputDpe) : inputDpe;
52
70
  const modele = enums.modele_dpe[dpe.administratif.enum_modele_dpe_id];
53
71
  const dateDpe = dpe.administratif.date_etablissement_dpe;
54
72
  if (modele !== 'dpe 3cl 2021 méthode logement') {
package/index.js CHANGED
@@ -1,4 +1,10 @@
1
- import { calcul_3cl, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion } from './engine.js';
2
- export { calcul_3cl, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
1
+ import {
2
+ calcul_3cl,
3
+ calcul_3cl_xml,
4
+ get_classe_ges_dpe,
5
+ get_conso_coeff_1_9_2026,
6
+ getVersion
7
+ } from './engine.js';
8
+ export { calcul_3cl, calcul_3cl_xml, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
3
9
  import { Umur, Uph, Upb, Uporte, Ubv, Upt } from './3_deperdition.js';
4
10
  export { Umur, Uph, Upb, Uporte, Ubv, Upt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {
package/utils.js CHANGED
@@ -1,6 +1,38 @@
1
1
  import enums from './enums.js';
2
2
  import tvs from './tv.js';
3
- import { set, has } from 'lodash-es';
3
+ import { set } from 'lodash-es';
4
+ import { XMLParser } from 'fast-xml-parser';
5
+
6
+ export const xmlParser = new XMLParser({
7
+ // We want to make sure collections of length 1 are still parsed as arrays
8
+ isArray: (name) => {
9
+ const collectionNames = [
10
+ 'mur',
11
+ 'plancher_bas',
12
+ 'plancher_haut',
13
+ 'baie_vitree',
14
+ 'porte',
15
+ 'pont_thermique',
16
+ 'ventilation',
17
+ 'installation_ecs',
18
+ 'generateur_ecs',
19
+ 'climatisation',
20
+ 'installation_chauffage',
21
+ 'generateur_chauffage',
22
+ 'emetteur_chauffage',
23
+ 'sortie_par_energie'
24
+ ];
25
+ if (collectionNames.includes(name)) return true;
26
+ },
27
+ tagValueProcessor: (tagName, val) => {
28
+ if (tagName.startsWith('enum_')) {
29
+ // Preserve value as string for tags starting with "enum_"
30
+ return null;
31
+ }
32
+ if (Number.isNaN(Number(val))) return val;
33
+ return Number(val);
34
+ }
35
+ });
4
36
 
5
37
  export let bug_for_bug_compat = false;
6
38
  export function set_bug_for_bug_compat() {
@@ -285,44 +317,12 @@ export function removeKeyFromJSON(jsonObj, keyToRemove, skipKeys) {
285
317
  }
286
318
  }
287
319
 
288
- export function useEnumAsString(jsonObj) {
289
- for (const key in jsonObj) {
290
- if (jsonObj.hasOwnProperty(key)) {
291
- if (key.startsWith('enum_')) {
292
- if (jsonObj[key] !== null) jsonObj[key] = jsonObj[key].toString();
293
- } else if (typeof jsonObj[key] === 'object') {
294
- useEnumAsString(jsonObj[key]);
295
- }
296
- }
297
- }
298
- }
299
-
300
320
  export function clean_dpe(dpe_in) {
301
321
  // skip generateur_[ecs|chauffage] because some input data is contained in donnee_intermediaire (e.g. pn, qp0, ...)
302
322
  removeKeyFromJSON(dpe_in, 'donnee_intermediaire', ['generateur_ecs', 'generateur_chauffage']);
303
323
  set(dpe_in, 'logement.sortie', null);
304
324
  }
305
325
 
306
- export function sanitize_dpe(dpe_in) {
307
- const collection_paths = [
308
- 'logement.enveloppe.plancher_bas_collection.plancher_bas',
309
- 'logement.enveloppe.plancher_haut_collection.plancher_haut',
310
- 'logement.ventilation_collection.ventilation',
311
- 'logement.climatisation_collection.climatisation',
312
- 'logement.enveloppe.baie_vitree_collection.baie_vitree',
313
- 'logement.enveloppe.porte_collection.porte',
314
- 'logement.enveloppe.pont_thermique_collection.pont_thermique'
315
- ];
316
- for (const path of collection_paths) {
317
- if (!has(dpe_in, path)) {
318
- set(dpe_in, path, []);
319
- }
320
- }
321
- if (use_enum_as_string) {
322
- useEnumAsString(dpe_in);
323
- }
324
- }
325
-
326
326
  /**
327
327
  * Retrieve a number describing a thickness from the description
328
328
  * @param description string in which to get the number