@open3cl/engine
Version:
Open Source 3CL-DPE engine
522 lines (465 loc) • 14.4 kB
JavaScript
import enums from './enums.js';
import tvs from './tv.js';
import { set, has } from 'lodash-es';
export let bug_for_bug_compat = false;
export function set_bug_for_bug_compat() {
bug_for_bug_compat = true;
}
export let use_enum_as_string = false;
export function set_use_enum_as_string() {
use_enum_as_string = true;
}
export let tv_match_new_version = false;
export function set_tv_match_optimized_version() {
tv_match_new_version = true;
}
export function unset_tv_match_optimized_version() {
tv_match_new_version = false;
}
export const Tbase = {
'inférieur à 400m': {
h1: -9.5,
h2: -6.5,
h3: -3.5
},
'400-800m': {
h1: -11.5,
h2: -8.5,
h3: -5.5
},
'supérieur à 800m': {
h1: -13.5,
h2: -10.5,
h3: -7.5
}
};
export const Njj = {
Janvier: 31,
Février: 28,
Mars: 31,
Avril: 30,
Mai: 31,
Juin: 30,
Juillet: 31,
Aout: 31,
Septembre: 30,
Octobre: 31,
Novembre: 30,
Décembre: 24
};
// sum all Njj values
export const Njj_sum = Object.values(Njj).reduce((acc, val) => acc + val, 0);
/** @type {string[]} **/
export const mois_liste = [
'Janvier',
'Février',
'Mars',
'Avril',
'Mai',
'Juin',
'Juillet',
'Aout',
'Septembre',
'Octobre',
'Novembre',
'Décembre'
];
export function add_references(enveloppe) {
let i = 0;
for (const mur of enveloppe.mur_collection.mur || []) {
if (!mur.donnee_entree.reference) {
mur.donnee_entree.reference = `mur_${i}`;
}
i += 1;
}
i = 0;
for (const ph of enveloppe.plancher_haut_collection.plancher_haut || []) {
if (!ph.donnee_entree.reference) {
ph.donnee_entree.reference = `plancher_haut_${i}`;
}
i += 1;
}
i = 0;
for (const pb of enveloppe.plancher_bas_collection.plancher_bas || []) {
if (!pb.donnee_entree.reference) {
pb.donnee_entree.reference = `plancher_bas_${i}`;
}
i += 1;
}
i = 0;
for (const bv of enveloppe.baie_vitree_collection.baie_vitree || []) {
if (!bv.donnee_entree.reference) {
bv.donnee_entree.reference = `baie_vitree_${i}`;
}
i += 1;
}
i = 0;
for (const porte of enveloppe.porte_collection.porte || []) {
if (!porte.donnee_entree.reference) {
porte.donnee_entree.reference = `porte_${i}`;
}
i += 1;
}
}
export function requestInputID(de, du, field, type) {
// enums
const enum_name = `enum_${field}_id`;
if (type) du[enum_name] = type;
else du[enum_name] = Object.keys(enums[field]);
return de[enum_name];
}
export function requestInput(de, du, field, type) {
if (enums.hasOwnProperty(field)) {
// enums
const enum_name = `enum_${field}_id`;
if (type) du[enum_name] = type;
else du[enum_name] = Object.keys(enums[field]);
return enums[field][de[enum_name]];
} else {
// not enums
if (!type) {
console.error(`requestInput: type is not defined for ${field}`);
return null;
}
du[field] = type;
return de[field];
}
}
export function getKeyByValue(object, value) {
return Object.keys(object).find((key) => object[key] === value);
}
export function tvColumnIDs(filePath, field) {
const list = tvs[filePath];
const enum_name = `enum_${field}_id`;
let ids = list.map((row) => row[enum_name]);
// remove undefineds
ids = ids.filter((id) => id);
// split each by | and get uniques
const unique_ids = [];
for (const id of ids) {
const values = id.split('|');
for (const value of values) {
if (!unique_ids.includes(value)) unique_ids.push(value);
}
}
// sort like numbers
return unique_ids;
}
export function tvColumnLines(filePath, column, matcher) {
const list = tvs[filePath];
// find lines in list that match matcher
const lines = [];
for (const row of list) {
let match = true;
for (const key in matcher) {
if (tvMatch(row, key, matcher) === false) {
match = false;
break;
}
}
if (match) lines.push(row[column]);
}
return lines;
}
function tvMatch(row, key, matcher) {
if (tv_match_new_version) {
return tvMatchOptimized(row, key, matcher);
}
if (!row.hasOwnProperty(key)) {
// for empty csv columns
// for q4pa_conv
return false;
}
let match_value = String(matcher[key]).toLowerCase();
// If the match value starts with ^, ends with $ and contains + then we escape the +
// Ex: ^iti+ite$ becomes ^iti\\+ite$
if (/^\^(.*)\+(.*)\$$/g.test(match_value)) {
match_value = match_value.replace('+', '\\+');
}
if (key.startsWith('enum_')) match_value = `^${String(matcher[key]).toLowerCase()}$`;
if (row[key].includes('|')) {
const values = row[key].split('|');
if (!values.some((v) => v.toLowerCase().match(String(match_value)))) {
return false;
}
} else if (Number.isInteger(matcher[key]) && ['≥', '≤'].some((char) => row[key].includes(char))) {
return eval(match_value + row[key].replace('≥', '>=').replace('≤', '<='));
} else if (!row[key].toLowerCase().match(String(match_value))) {
return false;
}
return true;
}
function tvMatchOptimized(row, key, matcher) {
if (!row[key]) {
// for empty csv columns
// for q4pa_conv
return false;
}
let row_value = row[key].toLowerCase();
let match_value = String(matcher[key]).toLowerCase();
if (row_value === match_value) {
return true;
}
if (match_value.startsWith('^')) {
const match_value_no_regex = match_value.replace('^', '').replace('$', '');
if (row_value === match_value_no_regex) {
return true;
}
}
if (isNaN(matcher[key]) && row_value.includes(match_value)) {
return true;
}
if (row_value.includes('|')) {
return row_value.split('|').includes(match_value);
}
if (Number.isInteger(matcher[key]) && ['≥', '≤'].some((char) => row[key].includes(char))) {
return eval(match_value + row[key].replace('≥', '>=').replace('≤', '<='));
}
return row_value.includes(match_value);
}
export function tv(filePath, matcher) {
const list = tvs[filePath];
let match_count = 0;
let max_match_count = 0;
let match = null;
for (const row of list) {
match_count = 0;
for (const key in matcher) {
if (tvMatch(row, key, matcher)) match_count += 1;
}
// if match_count is same as matcher, we are done
if (match_count === Object.keys(matcher).length) return row;
/* if (filePath === 'q4pa_conv') console.warn(match_count) */
if (match_count > max_match_count) {
max_match_count = match_count;
match = row;
}
}
/* if (filePath === 'pont_thermique') { */
/* console.warn(matcher) */
/* console.warn(match) */
/* } */
return match;
}
export function removeKeyFromJSON(jsonObj, keyToRemove, skipKeys) {
for (const key in jsonObj) {
if (skipKeys.includes(key)) continue;
if (jsonObj.hasOwnProperty(key)) {
if (key === keyToRemove) {
delete jsonObj[key];
} else if (typeof jsonObj[key] === 'object') {
removeKeyFromJSON(jsonObj[key], 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
* @returns {number} if found, 0 otherwise
*/
export function getThicknessFromDescription(description) {
if (!description) {
return 0;
}
const matching = description.match(/(\d+) cm/);
return matching && matching.length > 1 ? Number.parseFloat(matching[1]) : 0;
}
/**
* Return true si la collection $type peut être vide
* - Pas de pont thermique ou pas de pont thermique de type enum_type_liaison_id
* - Déperdition pour $type === 0 (donne probablement sur un autre local chauffé)
* @param logement {Logement}
* @param type {string}
* @param enum_type_liaison_id {number}
* @returns {boolean}
*/
export function collectionCanBeEmpty(logement, type, enum_type_liaison_id) {
const pontsThermiques = logement.enveloppe.pont_thermique_collection.pont_thermique || [];
const pontsThermiquesWithLiaison = pontsThermiques.filter(
(pontThermique) => pontThermique.donnee_entree.enum_type_liaison_id === enum_type_liaison_id
);
const deperdition = logement.sortie.deperdition[`deperdition_${type}`];
return pontsThermiquesWithLiaison.length === 0 && deperdition === 0;
}
/**
* Retrieve a number describing a volume from the description
* @param description string in which to get the number
* @returns {number} if found, 0 otherwise
*/
export function getVolumeStockageFromDescription(description) {
if (!description) {
return 0;
}
const matching = description.split('contenance ballon ');
return matching && matching.length > 1 ? parseInt(matching[1], 10) : 0;
}
/**
* Remove space and accented characters
* @param reference {string}
* return {string}
*/
export function cleanReference(reference) {
if (reference) {
return reference
.toString()
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s+/g, '');
}
return reference;
}
/**
* Return true if references are the same (without spaces and accented characters)
* @param reference1 {string}
* @param reference2 {string}
* return {boolean}
*/
export function compareReferences(reference1, reference2) {
return cleanReference(reference1) === cleanReference(reference2);
}
/**
* Vérification si le chauffage est par effet joule
* 3.2 Calcul des U des parois opaques
* On considère qu’un logement est chauffé par effet joule lorsque la chaleur est fournie par une résistance électrique.
* @param instal_ch {InstallationChauffageItem[]}
* @returns {string} 1 if effet joule, 0 otherwise
*/
export function isEffetJoule(instal_ch) {
const { surfaceEffetJoule, surfaceTotale } = instal_ch.reduce(
(acc, item) => {
const generatorIds = item.generateur_chauffage_collection.generateur_chauffage.reduce(
(acc, generateur) => {
return [...acc, generateur.donnee_entree.enum_type_generateur_ch_id];
},
[]
);
/**
* enum_type_generateur_ch_id
* 98 - convecteur électrique nfc, nf** et nf***
* 99 - panneau rayonnant électrique nfc, nf** et nf***
* 100 - radiateur électrique nfc, nf** et nf***
* 101 - autres émetteurs à effet joule
* 102 - plancher ou plafond rayonnant électrique avec régulation terminale
* 103 - plancher ou plafond rayonnant électrique sans régulation terminale
* 104 - radiateur électrique à accumulation
* 105 - convecteur bi-jonction
* 106 - chaudière électrique
* @type {boolean}
*/
const isEffetJoule =
generatorIds.filter((value) =>
['98', '99', '100', '101', '102', '103', '104', '105', '106'].includes(value)
).length > 0;
if (isEffetJoule) {
acc.surfaceEffetJoule += item.donnee_entree.surface_chauffee;
}
acc.surfaceTotale += item.donnee_entree.surface_chauffee;
return acc;
},
{ surfaceEffetJoule: 0, surfaceTotale: 0 }
);
// Si la surface chauffée par une résistance électrique est majoritaire => effet_joule = 1
return surfaceEffetJoule / surfaceTotale >= 0.5 ? '1' : '0';
}
/**
* Vérification si on trouve au moins une des chaînes de caractères substrings dans mainString
* @param mainString chaîne dans laquelle vérifier l'existence des substrings
* @param substrings chaîne à chercher dans la chaine principale
* @returns {boolean}
*/
export function containsAnySubstring(mainString, substrings) {
return substrings.some((substring) =>
mainString.toString().toLowerCase().includes(substring.toLowerCase())
);
}
/**
* Conversion des expressions du type `70 < Pn <= 400` en (70 < Pn) && (Pn <= 400)
* @param expression {string}
* @returns {string}
*/
export function convertExpression(expression) {
if (!expression) {
return expression;
}
// Gère les expressions combinées comme `70 < Pn <= 400`
expression = expression.replace(
/(\d+)\s*<\s*([a-zA-Z_$][\w]*)\s*<=\s*(\d+)/g,
'($1 < $2) && ($2 <= $3)'
);
return expression;
}
/**
* Retourne les valeurs qui encadrent inputNumber dans ranges
* Si inputNumber est présent dans ranges, inputNumber est retourné comme borne 1 et borne 2
*
* @param inputNumber {float}
* @param ranges {number[]}
* @returns {*[]}
*/
export function getRange(inputNumber, ranges) {
const result = [];
ranges.sort();
if (inputNumber <= ranges[0]) {
result.push(ranges[0]);
result.push(ranges[1]);
return result;
}
if (inputNumber >= ranges[ranges.length - 1]) {
result.push(ranges[ranges.length - 2]);
result.push(ranges[ranges.length - 1]);
}
if (ranges.includes(inputNumber)) {
result.push(inputNumber);
result.push(inputNumber);
} else {
ranges.find((range, index) => {
if (inputNumber < range) {
if (index > 0) {
result.push(ranges[index - 1]);
} else {
result.push(ranges[index]);
}
result.push(ranges[index]);
return true;
}
});
}
return result;
}