Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ C'est un bon moyen de détecter un éventuel problème dans le dpe ou la librair
```javascript
import { calcul_3cl } from 'open3cl';

// Exemple d'objet JSON issu d'un fichier XML DPE
// Exemple d'objet JSON (partiel) issu d'un fichier XML DPE
const dpeData = {
numero_dpe: '2113E1018248X',
statut: 'ACTIF',
Expand All @@ -108,7 +108,23 @@ const dpeData = {
}
};

// Execution d'un dpe avec la librairie Open3CL avec pré-transformation / nettoyage du dpe (comportement par défaut)
const result = calcul_3cl(dpeData);
const result = calcul_3cl(dpeData, { sanitize: true });

// Execution d'un dpe avec la librairie Open3CL sans pré-transformation / nettoyage du dpe
const result = calcul_3cl(dpeData, { sanitize: false });

// Execution d'un dpe au format xml avec la librairie Open3CL avec pré-transformation / nettoyage du dpe (comportement par défaut)
const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>');
const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>', {
sanitize: true
});

// Execution d'un dpe au format xml avec la librairie Open3CL sans pré-transformation / nettoyage du dpe (comportement par défaut)
const result = calcul_3cl_xml('<xml><dpe><numero_dpe>2113E1018248X</numero_dpe></dpe</xml>', {
sanitize: false
});
```

## Variables d'environnements
Expand Down Expand Up @@ -316,7 +332,7 @@ Nous accueillons les contributions avec plaisir ! Si vous souhaitez améliorer O

## Licence

Distribué sous la license `GPL-3.0 license`. Lire le fichier `LICENSE` pour plus d'informations.
Distribué sous la license `MIT`. Lire le fichier `LICENSE` pour plus d'informations.

<p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>

Expand Down
2 changes: 1 addition & 1 deletion dist/reports/corpus/corpus_list_main.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"dpe_appartement_individuel_chauffage_individuel_2025.csv",
"dpe_appartement_individuel_chauffage_collectif_2025.csv"
],
"branches": ["main", "fix_issue_132"]
"branches": ["main"]
}
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
calcul_3cl,
calcul_3cl_xml,
get_classe_ges_dpe,
get_conso_coeff_1_9_2026,
getVersion
} from './src/index.js';
export { calcul_3cl, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
export { calcul_3cl, calcul_3cl_xml, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
import { Umur, Uph, Upb, Uporte, Ubv, Upt, calc_deperdition } from './src/3_deperdition.js';
export { Umur, Uph, Upb, Uporte, Ubv, Upt, calc_deperdition };
90 changes: 90 additions & 0 deletions src/dpe-sanitizer.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ObjectUtil } from './core/util/infrastructure/object-util.js';

const nodesToMap = [
'mur',
'plancher_bas',
'plancher_haut',
'baie_vitree',
'porte',
'pont_thermique',
'ventilation',
'installation_ecs',
'generateur_ecs',
'climatisation',
'installation_chauffage',
'generateur_chauffage',
'emetteur_chauffage',
'sortie_par_energie'
];

/**
* Transform single nodes in {@link nodesToMap} into array of nodes.
* Transform string number into digits
* These transformations should be done inside the open3cl library
*
* @example
* // Will transform
* "plancher_haut_collection": {
* "plancher_haut": {"id": 1}
* }
* // Into
* "plancher_haut_collection": {
* "plancher_haut": [{"id": 1}]
* }
*
* @example
* // Will transform
* "surface_paroi_opaque": "40.94"
* // Into
* "surface_paroi_opaque": 40.94
*/
export default class DpeSanitizerService {
/**
* @param dpe {FullDpe}
* @return {FullDpe}
*/
execute(dpe) {
return ObjectUtil.deepObjectTransform(
dpe,
(key) => key,
(val, key) => {
if (this.#needTransform(key, val)) {
return [val];
}

if (this.#isEnum(key)) {
return val;
}

if (this.#isUndefinedVal(val)) {
return '';
}

if (this.#isEmptyArray(val)) {
return val;
}

if (Number.isNaN(Number(val))) {
return val;
}
return Number(val);
}
);
}

#isEnum(key) {
return key.startsWith('enum_') || key.startsWith('original_enum');
}

#needTransform(key, val) {
return typeof val === 'object' && !Array.isArray(val) && nodesToMap.includes(key);
}

#isUndefinedVal(val) {
return val === '' || val === null;
}

#isEmptyArray(val) {
return Array.isArray(val) && val.length === 0;
}
}
26 changes: 22 additions & 4 deletions src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ import {
collectionCanBeEmpty,
containsAnySubstring,
isEffetJoule,
sanitize_dpe
xmlParser
} from './utils.js';
import { Inertie } from './7_inertie.js';
import getFicheTechnique from './ficheTechnique.js';
import { ProductionENR } from './16.2_production_enr.js';
import DpeSanitizerService from './dpe-sanitizer.service.js';

const LIB_VERSION = 'OPEN3CL_VERSION';

const dpeSanitizerService = new DpeSanitizerService();

function calc_th(map_id) {
const map = enums.methode_application_dpe_log[map_id];
if (map.includes('maison')) return 'maison';
Expand All @@ -44,11 +47,26 @@ export function getVersion() {
}

/**
* @param dpe {FullDpe}
* Run the engine with a full dpe xml content
* @param dpeXmlContent {string} A full dpe xml content
* @param options {{sanitize: boolean}?}
* @return {FullDpe}
*/
export function calcul_3cl_xml(dpeXmlContent, options) {
/** @type {{dpe: FullDpe}} **/
const xmlDpe = xmlParser.parse(dpeXmlContent);
return calcul_3cl(xmlDpe.dpe, options);
}

/**
* Run the engine with a javascript plain object dpe
* @param inputDpe {FullDpe} A full plain object dpe
* @param options {{sanitize: boolean}?}
* @return {FullDpe}
*/
export function calcul_3cl(dpe) {
sanitize_dpe(dpe);
export function calcul_3cl(inputDpe, options) {
if (!options) options = { sanitize: true };
const dpe = options.sanitize ? dpeSanitizerService.execute(inputDpe) : inputDpe;
const modele = enums.modele_dpe[dpe.administratif.enum_modele_dpe_id];
const dateDpe = dpe.administratif.date_etablissement_dpe;
if (modele !== 'dpe 3cl 2021 méthode logement') {
Expand Down
10 changes: 8 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { calcul_3cl, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion } from './engine.js';
export { calcul_3cl, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
import {
calcul_3cl,
calcul_3cl_xml,
get_classe_ges_dpe,
get_conso_coeff_1_9_2026,
getVersion
} from './engine.js';
export { calcul_3cl, calcul_3cl_xml, get_classe_ges_dpe, get_conso_coeff_1_9_2026, getVersion };
import { Umur, Uph, Upb, Uporte, Ubv, Upt } from './3_deperdition.js';
export { Umur, Uph, Upb, Uporte, Ubv, Upt };
66 changes: 33 additions & 33 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import enums from './enums.js';
import tvs from './tv.js';
import { set, has } from 'lodash-es';
import { set } from 'lodash-es';
import { XMLParser } from 'fast-xml-parser';

export const xmlParser = new XMLParser({
// We want to make sure collections of length 1 are still parsed as arrays
isArray: (name) => {
const collectionNames = [
'mur',
'plancher_bas',
'plancher_haut',
'baie_vitree',
'porte',
'pont_thermique',
'ventilation',
'installation_ecs',
'generateur_ecs',
'climatisation',
'installation_chauffage',
'generateur_chauffage',
'emetteur_chauffage',
'sortie_par_energie'
];
if (collectionNames.includes(name)) return true;
},
tagValueProcessor: (tagName, val) => {
if (tagName.startsWith('enum_')) {
// Preserve value as string for tags starting with "enum_"
return null;
}
if (Number.isNaN(Number(val))) return val;
return Number(val);
}
});

export let bug_for_bug_compat = false;
export function set_bug_for_bug_compat() {
Expand Down Expand Up @@ -285,44 +317,12 @@ export function removeKeyFromJSON(jsonObj, keyToRemove, skipKeys) {
}
}

export function useEnumAsString(jsonObj) {
for (const key in jsonObj) {
if (jsonObj.hasOwnProperty(key)) {
if (key.startsWith('enum_')) {
if (jsonObj[key] !== null) jsonObj[key] = jsonObj[key].toString();
} else if (typeof jsonObj[key] === 'object') {
useEnumAsString(jsonObj[key]);
}
}
}
}

export function clean_dpe(dpe_in) {
// skip generateur_[ecs|chauffage] because some input data is contained in donnee_intermediaire (e.g. pn, qp0, ...)
removeKeyFromJSON(dpe_in, 'donnee_intermediaire', ['generateur_ecs', 'generateur_chauffage']);
set(dpe_in, 'logement.sortie', null);
}

export function sanitize_dpe(dpe_in) {
const collection_paths = [
'logement.enveloppe.plancher_bas_collection.plancher_bas',
'logement.enveloppe.plancher_haut_collection.plancher_haut',
'logement.ventilation_collection.ventilation',
'logement.climatisation_collection.climatisation',
'logement.enveloppe.baie_vitree_collection.baie_vitree',
'logement.enveloppe.porte_collection.porte',
'logement.enveloppe.pont_thermique_collection.pont_thermique'
];
for (const path of collection_paths) {
if (!has(dpe_in, path)) {
set(dpe_in, path, []);
}
}
if (use_enum_as_string) {
useEnumAsString(dpe_in);
}
}

/**
* Retrieve a number describing a thickness from the description
* @param description string in which to get the number
Expand Down
37 changes: 1 addition & 36 deletions test/test-helpers.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
import { XMLParser } from 'fast-xml-parser';
import fs from 'node:fs';

const xmlParser = new XMLParser({
// We want to make sure collections of length 1 are still parsed as arrays
isArray: (name) => {
const collectionNames = [
'mur',
'plancher_bas',
'plancher_haut',
'baie_vitree',
'porte',
'pont_thermique',
'ventilation',
'installation_ecs',
'generateur_ecs',
'climatisation',
'installation_chauffage',
'generateur_chauffage',
'emetteur_chauffage',
'sortie_par_energie'
];
if (collectionNames.includes(name)) return true;
},
tagValueProcessor: (tagName, val) => {
if (tagName.startsWith('enum_')) {
// Preserve value as string for tags starting with "enum_"
return null;
}
if (Number.isNaN(Number(val))) return val;
return Number(val);
}
});

export function parseXml(data) {
return xmlParser.parse(data).dpe;
}
import { xmlParser } from '../src/utils.js';

export async function getAdemeFileJsonOrDownload(dpeCode) {
if (!process.env.ADEME_CLIENT_ID || !process.env.ADEME_CLIENT_SECRET) {
throw new Error(`ADEME_CLIENT_ID and ADEME_CLIENT_SECRET environment variables are not set !`);

Check failure on line 6 in test/test-helpers.js

View workflow job for this annotation

GitHub Actions / Build

test/open3cl-coeff-1-9.spec.js > Open3cl misc unit tests > should calculate primary conso with coeff 1.9 (since janvier 2026) for dpe: 2464E1799476F

Error: ADEME_CLIENT_ID and ADEME_CLIENT_SECRET environment variables are not set ! ❯ getAdemeFileJsonOrDownload test/test-helpers.js:6:11 ❯ test/open3cl-coeff-1-9.spec.js:67:54

Check failure on line 6 in test/test-helpers.js

View workflow job for this annotation

GitHub Actions / Build

test/open3cl-coeff-1-9.spec.js > Open3cl misc unit tests > should calculate primary conso with coeff 1.9 (since janvier 2026) for dpe: 2528E1249844E

Error: ADEME_CLIENT_ID and ADEME_CLIENT_SECRET environment variables are not set ! ❯ getAdemeFileJsonOrDownload test/test-helpers.js:6:11 ❯ test/open3cl-coeff-1-9.spec.js:47:54

Check failure on line 6 in test/test-helpers.js

View workflow job for this annotation

GitHub Actions / Build

test/open3cl-coeff-1-9.spec.js > Open3cl misc unit tests > should calculate primary conso with coeff 1.9 (since janvier 2026) for dpe: 2513E1166911W

Error: ADEME_CLIENT_ID and ADEME_CLIENT_SECRET environment variables are not set ! ❯ getAdemeFileJsonOrDownload test/test-helpers.js:6:11 ❯ test/open3cl-coeff-1-9.spec.js:27:54

Check failure on line 6 in test/test-helpers.js

View workflow job for this annotation

GitHub Actions / Build

test/open3cl-coeff-1-9.spec.js > Open3cl misc unit tests > should calculate primary conso with coeff 1.9 (since janvier 2026) for dpe: 2369E2791083C

Error: ADEME_CLIENT_ID and ADEME_CLIENT_SECRET environment variables are not set ! ❯ getAdemeFileJsonOrDownload test/test-helpers.js:6:11 ❯ test/open3cl-coeff-1-9.spec.js:7:54
}
const dpeJsonFilePath = `test/fixtures/${dpeCode}.json`;
const dpeXmlFilePath = `test/fixtures/${dpeCode}.xml`;
Expand Down
Loading