@open3cl/engine 1.7.1 → 1.7.3
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/13.2_generateur_combustion.js +2 -10
- package/3.1_b.js +45 -33
- package/3.2.1_mur.js +19 -10
- package/3.2.1_mur.spec.js +105 -0
- package/3.3_baie_vitree.js +32 -10
- package/3.4_pont_thermique.js +21 -13
- package/3.4_pont_thermique.spec.js +1 -1
- package/3_deperdition.js +6 -1
- package/3_deperdition.spec.js +47 -0
- package/README.md +40 -10
- package/engine.js +1 -1
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.js +22 -13
- package/ficheTechnique.js +133 -53
- package/ficheTechnique.spec.js +46 -14
- package/formula.js +251 -0
- package/formula.spec.js +99 -0
- package/package.json +5 -6
- package/utils.js +195 -44
- package/utils.spec.js +69 -0
|
@@ -4,7 +4,7 @@ import { updateGenerateurBouilleurs } from './13.2_generateur_combustion_bouille
|
|
|
4
4
|
import { updateGenerateurChaudieres } from './13.2_generateur_combustion_chaudiere.js';
|
|
5
5
|
import { updateGenerateurPacs } from './13.2_generateur_pac.js';
|
|
6
6
|
import getFicheTechnique from './ficheTechnique.js';
|
|
7
|
-
import {
|
|
7
|
+
import { evaluateFormula as evaluateTvFormula } from './formula.js';
|
|
8
8
|
|
|
9
9
|
function criterePn(Pn, matcher) {
|
|
10
10
|
let critere_list = tvColumnLines('generateur_combustion', 'critere_pn', matcher);
|
|
@@ -38,15 +38,7 @@ const F_tab = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
function evaluateFormula(formulaOrValue, pn, E, F) {
|
|
41
|
-
|
|
42
|
-
if (!isNaN(formulaOrValue)) {
|
|
43
|
-
return Number(formulaOrValue);
|
|
44
|
-
}
|
|
45
|
-
const variables = new Map();
|
|
46
|
-
variables.set('Pn', pnVariable);
|
|
47
|
-
variables.set('E', E);
|
|
48
|
-
variables.set('F', F);
|
|
49
|
-
return evaluate(formulaOrValue, variables);
|
|
41
|
+
return evaluateTvFormula(formulaOrValue, pn, E, F);
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
/**
|
package/3.1_b.js
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
import enums from './enums.js';
|
|
2
2
|
import { tv, requestInput, requestInputID, bug_for_bug_compat } from './utils.js';
|
|
3
3
|
|
|
4
|
+
const RANGES = [0.25, 0.5, 0.75, 1, 1.25, 2, 2.5, 3, 3.5, 4, 6, 8, 10, 25, 50];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Identifiants de type d'adjacence pour lesquels le ratio aiu/aue est pris en compte.
|
|
8
|
+
*/
|
|
9
|
+
const ADJACENCE_AIU_AUE_IDS = new Set([
|
|
10
|
+
'8',
|
|
11
|
+
'9',
|
|
12
|
+
'11',
|
|
13
|
+
'12',
|
|
14
|
+
'13',
|
|
15
|
+
'14',
|
|
16
|
+
'15',
|
|
17
|
+
'16',
|
|
18
|
+
'17',
|
|
19
|
+
'18',
|
|
20
|
+
'19',
|
|
21
|
+
'21'
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const TYPE_ADJACENCE_IDS = Object.keys(enums.type_adjacence);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Formate une borne du ratio aiu/aue au format des tables de valeurs (`0,25`, `1,00`…).
|
|
28
|
+
*
|
|
29
|
+
* `toLocaleString(undefined, { minimumFractionDigits: 2, … })` construisait un
|
|
30
|
+
* `Intl.NumberFormat` à chaque appel (~29 µs), ce qui représentait l'essentiel du
|
|
31
|
+
* temps CPU de `b()`. `toFixed(2)` produit exactement les mêmes chaînes pour toutes
|
|
32
|
+
* les valeurs de `RANGES`, en ~0,2 µs, et sans dépendre de la locale de la machine —
|
|
33
|
+
* le `.replace('.', ',')` qui suivait était d'ailleurs un no-op sur une machine en
|
|
34
|
+
* locale française et une vraie conversion ailleurs.
|
|
35
|
+
*
|
|
36
|
+
* @param borne {number}
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function formatBorneAiuAue(borne) {
|
|
40
|
+
return borne.toFixed(2).replace('.', ',');
|
|
41
|
+
}
|
|
42
|
+
|
|
4
43
|
export function findRanges(inputNumber) {
|
|
5
|
-
const ranges =
|
|
44
|
+
const ranges = RANGES;
|
|
6
45
|
const result = [];
|
|
7
46
|
|
|
8
47
|
if (inputNumber < ranges[0]) {
|
|
@@ -28,7 +67,7 @@ export function findRanges(inputNumber) {
|
|
|
28
67
|
export default function b(di, de, du, zc_id) {
|
|
29
68
|
const zc = enums.zone_climatique[zc_id].slice(0, 2);
|
|
30
69
|
|
|
31
|
-
du.enum_type_adjacence_id =
|
|
70
|
+
du.enum_type_adjacence_id = TYPE_ADJACENCE_IDS;
|
|
32
71
|
const matcher = {
|
|
33
72
|
enum_type_adjacence_id: requestInputID(de, du, 'type_adjacence')
|
|
34
73
|
};
|
|
@@ -37,11 +76,7 @@ export default function b(di, de, du, zc_id) {
|
|
|
37
76
|
matcher.zone_climatique = zc;
|
|
38
77
|
matcher.enum_cfg_isolation_lnc_id = requestInputID(de, du, 'cfg_isolation_lnc');
|
|
39
78
|
/* du.enum_cfg_isolation_lnc_id = ['6', '7', '8', '9', '10', '11'] */
|
|
40
|
-
} else if (
|
|
41
|
-
['8', '9', '11', '12', '13', '14', '15', '16', '17', '18', '19', '21'].includes(
|
|
42
|
-
de.enum_type_adjacence_id.toString()
|
|
43
|
-
)
|
|
44
|
-
) {
|
|
79
|
+
} else if (ADJACENCE_AIU_AUE_IDS.has(de.enum_type_adjacence_id.toString())) {
|
|
45
80
|
if (de.surface_aue === 0) {
|
|
46
81
|
// cf page 10
|
|
47
82
|
// NOTE: bizarre de regarder aue pour un local chauffé non accessible
|
|
@@ -96,36 +131,13 @@ export default function b(di, de, du, zc_id) {
|
|
|
96
131
|
// Si le rapport des surfaces est égale à la borne inférieure, utilisation de la borne supérieure
|
|
97
132
|
// et prise en compte de l'égalité
|
|
98
133
|
if (ranges[0] === de.surface_aiu / de.surface_aue) {
|
|
99
|
-
matcher.aiu_aue_max =
|
|
100
|
-
'≤ ' +
|
|
101
|
-
ranges[0]
|
|
102
|
-
.toLocaleString(undefined, {
|
|
103
|
-
minimumFractionDigits: 2,
|
|
104
|
-
maximumFractionDigits: 2,
|
|
105
|
-
useGrouping: false
|
|
106
|
-
})
|
|
107
|
-
.replace('.', ',');
|
|
134
|
+
matcher.aiu_aue_max = '≤ ' + formatBorneAiuAue(ranges[0]);
|
|
108
135
|
} else {
|
|
109
|
-
matcher.aiu_aue_min =
|
|
110
|
-
ranges[0]
|
|
111
|
-
.toLocaleString(undefined, {
|
|
112
|
-
minimumFractionDigits: 2,
|
|
113
|
-
maximumFractionDigits: 2,
|
|
114
|
-
useGrouping: false
|
|
115
|
-
})
|
|
116
|
-
.replace('.', ',') + ' <';
|
|
136
|
+
matcher.aiu_aue_min = formatBorneAiuAue(ranges[0]) + ' <';
|
|
117
137
|
}
|
|
118
138
|
}
|
|
119
139
|
if (ranges[1]) {
|
|
120
|
-
matcher.aiu_aue_max =
|
|
121
|
-
'≤ ' +
|
|
122
|
-
ranges[1]
|
|
123
|
-
.toLocaleString(undefined, {
|
|
124
|
-
minimumFractionDigits: 2,
|
|
125
|
-
maximumFractionDigits: 2,
|
|
126
|
-
useGrouping: false
|
|
127
|
-
})
|
|
128
|
-
.replace('.', ',');
|
|
140
|
+
matcher.aiu_aue_max = '≤ ' + formatBorneAiuAue(ranges[1]);
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
}
|
package/3.2.1_mur.js
CHANGED
|
@@ -122,7 +122,7 @@ function tv_umur(di, de, du, pc_id, zc, effetJoule) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function calc_umur0(di, de, du) {
|
|
125
|
+
function calc_umur0(di, de, du, versionDpe) {
|
|
126
126
|
const umur0_avant = du.umur0_avant;
|
|
127
127
|
const umur_avant = du.umur_avant;
|
|
128
128
|
|
|
@@ -166,8 +166,17 @@ function calc_umur0(di, de, du) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Le doublage NE doit PAS être cumulé à une isolation ITE ou ITI.
|
|
171
|
+
* Il doit uniquement être pris en compte en l'absence d'isolation ITE/ITI (ex: ITR seule, non isolé, inconnu).
|
|
172
|
+
* Types avec ITE ou ITI : 3 (iti), 4 (ite), 6 (iti+ite), 7 (iti+itr), 8 (ite+itr)
|
|
173
|
+
* @see https://github.com/Open3CL/engine/issues/146
|
|
174
|
+
*/
|
|
175
|
+
const type_isolation = parseInt(de.enum_type_isolation_id) || 1;
|
|
176
|
+
const hasIteOrIti = versionDpe >= 2.4 && [3, 4, 6, 7, 8].includes(type_isolation);
|
|
177
|
+
|
|
178
|
+
// Si présence d'un doublage et pas d'isolation ITE/ITI
|
|
179
|
+
if (type_doublage > 2 && !hasIteOrIti) {
|
|
171
180
|
let umur0Doublage;
|
|
172
181
|
|
|
173
182
|
// 3 - doublage indéterminé ou lame d'air inf 15 mm
|
|
@@ -212,7 +221,7 @@ function calc_umur0(di, de, du) {
|
|
|
212
221
|
di.umur0 = Math.min(2.5, di.umur0);
|
|
213
222
|
}
|
|
214
223
|
|
|
215
|
-
export default function calc_mur(mur, zc, pc_id, effetJoule) {
|
|
224
|
+
export default function calc_mur(mur, zc, pc_id, effetJoule, versionDpe) {
|
|
216
225
|
const de = mur.donnee_entree;
|
|
217
226
|
const du = {};
|
|
218
227
|
const di = {};
|
|
@@ -228,12 +237,12 @@ export default function calc_mur(mur, zc, pc_id, effetJoule) {
|
|
|
228
237
|
const methode_saisie_u = requestInput(de, du, 'methode_saisie_u');
|
|
229
238
|
switch (methode_saisie_u) {
|
|
230
239
|
case 'non isolé':
|
|
231
|
-
calc_umur0(di, de, du);
|
|
240
|
+
calc_umur0(di, de, du, versionDpe);
|
|
232
241
|
di.umur = Math.min(di.umur0, 2.5);
|
|
233
242
|
break;
|
|
234
243
|
case 'epaisseur isolation saisie justifiée par mesure ou observation':
|
|
235
244
|
case 'epaisseur isolation saisie justifiée à partir des documents justificatifs autorisés': {
|
|
236
|
-
calc_umur0(di, de, du);
|
|
245
|
+
calc_umur0(di, de, du, versionDpe);
|
|
237
246
|
const epaisseurIsolation = requestInput(de, du, 'epaisseur_isolation', 'int') * 0.01;
|
|
238
247
|
|
|
239
248
|
if (epaisseurIsolation) {
|
|
@@ -251,7 +260,7 @@ export default function calc_mur(mur, zc, pc_id, effetJoule) {
|
|
|
251
260
|
}
|
|
252
261
|
case "resistance isolation saisie justifiée observation de l'isolant installé et mesure de son épaisseur":
|
|
253
262
|
case 'resistance isolation saisie justifiée à partir des documents justificatifs autorisés': {
|
|
254
|
-
calc_umur0(di, de, du);
|
|
263
|
+
calc_umur0(di, de, du, versionDpe);
|
|
255
264
|
const resistanceIsolation = requestInput(de, du, 'resistance_isolation', 'float');
|
|
256
265
|
|
|
257
266
|
if (resistanceIsolation) {
|
|
@@ -268,19 +277,19 @@ export default function calc_mur(mur, zc, pc_id, effetJoule) {
|
|
|
268
277
|
break;
|
|
269
278
|
}
|
|
270
279
|
case 'isolation inconnue (table forfaitaire)':
|
|
271
|
-
calc_umur0(di, de, du);
|
|
280
|
+
calc_umur0(di, de, du, versionDpe);
|
|
272
281
|
tv_umur(di, de, du, pc_id, zc, effetJoule);
|
|
273
282
|
di.umur = Math.min(di.umur, di.umur0);
|
|
274
283
|
break;
|
|
275
284
|
case "année d'isolation différente de l'année de construction saisie justifiée (table forfaitaire)": {
|
|
276
|
-
calc_umur0(di, de, du);
|
|
285
|
+
calc_umur0(di, de, du, versionDpe);
|
|
277
286
|
const pi_id = requestInputID(de, du, 'periode_isolation') || pc_id;
|
|
278
287
|
tv_umur(di, de, du, pi_id, zc, effetJoule);
|
|
279
288
|
di.umur = Math.min(di.umur, di.umur0);
|
|
280
289
|
break;
|
|
281
290
|
}
|
|
282
291
|
case 'année de construction saisie (table forfaitaire)': {
|
|
283
|
-
calc_umur0(di, de, du);
|
|
292
|
+
calc_umur0(di, de, du, versionDpe);
|
|
284
293
|
// Si l'année d'isolation est connue, il faut l'utiliser et pas l'année de construction
|
|
285
294
|
let pi_id = de.enum_periode_isolation_id || pc_id;
|
|
286
295
|
if (!de.enum_periode_isolation_id) {
|
package/3.2.1_mur.spec.js
CHANGED
|
@@ -819,3 +819,108 @@ describe('calc_mur - méthode de saisie de U inconnue', () => {
|
|
|
819
819
|
warn.mockRestore();
|
|
820
820
|
});
|
|
821
821
|
});
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Le doublage n'est plus cumulé à une isolation ITE ou ITI à partir de la version 2.4 du DPE.
|
|
825
|
+
* Les DPE antérieurs (2 à 2.3) ont été établis avec le cumul : on conserve ce comportement.
|
|
826
|
+
* @see https://github.com/Open3CL/engine/issues/146
|
|
827
|
+
*/
|
|
828
|
+
describe('calc_mur - doublage et isolation ITE/ITI (#146)', () => {
|
|
829
|
+
/** Umur0 issu de la table forfaitaire mockée. */
|
|
830
|
+
const UMUR0_TABLE = 2;
|
|
831
|
+
/** Umur0 avec doublage type 4 ou 5 cumulé : 1 / (1/2 + 0,21). */
|
|
832
|
+
const UMUR0_AVEC_DOUBLAGE = 1.4084507042253522;
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* @param enum_type_isolation_id {string}
|
|
836
|
+
* @param enum_type_doublage_id {string}
|
|
837
|
+
* @return {object}
|
|
838
|
+
*/
|
|
839
|
+
const murIsoleAvecDoublage = (enum_type_isolation_id, enum_type_doublage_id = '5') => ({
|
|
840
|
+
donnee_entree: {
|
|
841
|
+
methode_saisie_u: 'epaisseur isolation saisie justifiée par mesure ou observation',
|
|
842
|
+
methode_saisie_u0:
|
|
843
|
+
'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
|
|
844
|
+
enum_materiaux_structure_mur_id: '5',
|
|
845
|
+
epaisseur_structure: 30,
|
|
846
|
+
epaisseur_isolation: 10,
|
|
847
|
+
enum_type_doublage_id,
|
|
848
|
+
enum_type_isolation_id
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
describe('DPE en version 2.4 et supérieure', () => {
|
|
853
|
+
test.each([
|
|
854
|
+
['iti', '3'],
|
|
855
|
+
['ite', '4'],
|
|
856
|
+
['iti+ite', '6'],
|
|
857
|
+
['iti+itr', '7'],
|
|
858
|
+
['ite+itr', '8']
|
|
859
|
+
])("isolation %s : le doublage n'est pas cumulé", (_label, typeIsolation) => {
|
|
860
|
+
const mur = murIsoleAvecDoublage(typeIsolation);
|
|
861
|
+
|
|
862
|
+
calc_mur(mur, 'h1a', '1', '0', 2.4);
|
|
863
|
+
|
|
864
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_TABLE, 9);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test.each([
|
|
868
|
+
['inconnu', '1'],
|
|
869
|
+
['non isolé', '2'],
|
|
870
|
+
['itr', '5'],
|
|
871
|
+
["isolé mais type d'isolation inconnu", '9']
|
|
872
|
+
])('isolation %s : le doublage est cumulé', (_label, typeIsolation) => {
|
|
873
|
+
const mur = murIsoleAvecDoublage(typeIsolation);
|
|
874
|
+
|
|
875
|
+
calc_mur(mur, 'h1a', '1', '0', 2.4);
|
|
876
|
+
|
|
877
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_AVEC_DOUBLAGE, 9);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test('type_isolation absent : le doublage est cumulé', () => {
|
|
881
|
+
const mur = murIsoleAvecDoublage(undefined);
|
|
882
|
+
|
|
883
|
+
calc_mur(mur, 'h1a', '1', '0', 2.5);
|
|
884
|
+
|
|
885
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_AVEC_DOUBLAGE, 9);
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
describe('DPE antérieurs à la version 2.4', () => {
|
|
890
|
+
test.each([[2], [2.1], [2.2], [2.3]])(
|
|
891
|
+
'version %s : le doublage reste cumulé à une isolation ITI',
|
|
892
|
+
(versionDpe) => {
|
|
893
|
+
const mur = murIsoleAvecDoublage('3');
|
|
894
|
+
|
|
895
|
+
calc_mur(mur, 'h1a', '1', '0', versionDpe);
|
|
896
|
+
|
|
897
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_AVEC_DOUBLAGE, 9);
|
|
898
|
+
}
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
test('version absente : le doublage reste cumulé à une isolation ITE', () => {
|
|
902
|
+
const mur = murIsoleAvecDoublage('4');
|
|
903
|
+
|
|
904
|
+
calc_mur(mur, 'h1a', '1', '0');
|
|
905
|
+
|
|
906
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_AVEC_DOUBLAGE, 9);
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test.each([
|
|
911
|
+
['isolation inconnue (table forfaitaire)'],
|
|
912
|
+
[
|
|
913
|
+
"année d'isolation différente de l'année de construction saisie justifiée (table forfaitaire)"
|
|
914
|
+
],
|
|
915
|
+
['année de construction saisie (table forfaitaire)'],
|
|
916
|
+
['resistance isolation saisie justifiée à partir des documents justificatifs autorisés']
|
|
917
|
+
])('methode_saisie_u "%s" : la version du DPE est prise en compte', (methode_saisie_u) => {
|
|
918
|
+
const mur = murIsoleAvecDoublage('3');
|
|
919
|
+
mur.donnee_entree.methode_saisie_u = methode_saisie_u;
|
|
920
|
+
mur.donnee_entree.resistance_isolation = 2.5;
|
|
921
|
+
|
|
922
|
+
calc_mur(mur, 'h1a', '1', '0', 2.4);
|
|
923
|
+
|
|
924
|
+
expect(mur.donnee_intermediaire.umur0).toBeCloseTo(UMUR0_TABLE, 9);
|
|
925
|
+
});
|
|
926
|
+
});
|
package/3.3_baie_vitree.js
CHANGED
|
@@ -23,6 +23,37 @@ function tv_ug(di, de, du) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Valeurs de Ug tabulées dans `uw`, indexées par (type de baie, matériau de menuiserie).
|
|
28
|
+
*
|
|
29
|
+
* Le scan des 723 lignes de `tvs.uw` — avec un `split('|')` par ligne — était refait
|
|
30
|
+
* pour chaque baie vitrée de chaque DPE, alors que la table est statique.
|
|
31
|
+
*
|
|
32
|
+
* Le tableau est partagé et `getRange()` le trie sur place : le tri par défaut de
|
|
33
|
+
* `Array#sort` étant déterministe, la première mise en cache le laisse trié et les
|
|
34
|
+
* appels suivants produisent exactement le même résultat qu'avec un tableau reconstruit.
|
|
35
|
+
*/
|
|
36
|
+
const uwUgValuesCache = new Map();
|
|
37
|
+
|
|
38
|
+
function uwUgValues(enum_type_baie_id, enum_type_materiaux_menuiserie_id) {
|
|
39
|
+
const cacheKey = `${enum_type_baie_id}${enum_type_materiaux_menuiserie_id}`;
|
|
40
|
+
let values = uwUgValuesCache.get(cacheKey);
|
|
41
|
+
if (values === undefined) {
|
|
42
|
+
values = tvs.uw
|
|
43
|
+
.filter((row) => {
|
|
44
|
+
return (
|
|
45
|
+
row.enum_type_baie_id.split('|').includes(enum_type_baie_id) &&
|
|
46
|
+
row.enum_type_materiaux_menuiserie_id
|
|
47
|
+
.split('|')
|
|
48
|
+
.includes(enum_type_materiaux_menuiserie_id)
|
|
49
|
+
);
|
|
50
|
+
})
|
|
51
|
+
.map((row) => parseFloat(row.ug));
|
|
52
|
+
uwUgValuesCache.set(cacheKey, values);
|
|
53
|
+
}
|
|
54
|
+
return values;
|
|
55
|
+
}
|
|
56
|
+
|
|
26
57
|
function tv_uw(di, de) {
|
|
27
58
|
const enum_type_baie_id = de.enum_type_baie_id;
|
|
28
59
|
const matcher = { enum_type_baie_id };
|
|
@@ -46,16 +77,7 @@ function tv_uw(di, de) {
|
|
|
46
77
|
matcher.enum_type_materiaux_menuiserie_id = enum_type_materiaux_menuiserie_id;
|
|
47
78
|
|
|
48
79
|
// Récupération de toutes les valeurs de Ug présentes dans les tables Uw pour le type de baie et matériaux de la baie vitrée
|
|
49
|
-
const ugValues =
|
|
50
|
-
.filter((row) => {
|
|
51
|
-
return (
|
|
52
|
-
row.enum_type_baie_id.split('|').includes(enum_type_baie_id) &&
|
|
53
|
-
row.enum_type_materiaux_menuiserie_id
|
|
54
|
-
.split('|')
|
|
55
|
-
.includes(enum_type_materiaux_menuiserie_id)
|
|
56
|
-
);
|
|
57
|
-
})
|
|
58
|
-
.map((row) => parseFloat(row.ug));
|
|
80
|
+
const ugValues = uwUgValues(enum_type_baie_id, enum_type_materiaux_menuiserie_id);
|
|
59
81
|
|
|
60
82
|
/**
|
|
61
83
|
* 3.3.2 Coefficients Uw des fenêtres / portes-fenêtres
|
package/3.4_pont_thermique.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import enums from './enums.js';
|
|
2
2
|
import { isNil } from 'lodash-es';
|
|
3
|
-
import { tv, requestInput,
|
|
3
|
+
import { tv, requestInput, cleanReference, bug_for_bug_compat } from './utils.js';
|
|
4
4
|
|
|
5
5
|
function defaultValue(type_liaison, pt_di, de) {
|
|
6
6
|
if (pt_di.k === 0) {
|
|
@@ -115,10 +115,20 @@ function tv_k(pt_di, di, de, du, pc_id, logement) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Références du pont thermique nettoyées une seule fois : elles sont confrontées à toutes les
|
|
120
|
+
* parois de plusieurs listes ci-dessous, et `compareReferences` les renettoyait à chaque
|
|
121
|
+
* comparaison. Les affectations de `de.reference_1` / `de.reference_2` ont toutes eu lieu
|
|
122
|
+
* au-dessus, dans le bloc de récupération par description.
|
|
123
|
+
*/
|
|
124
|
+
const cleaned_reference_1 = cleanReference(de.reference_1);
|
|
125
|
+
const cleaned_reference_2 = cleanReference(de.reference_2);
|
|
126
|
+
|
|
118
127
|
let mur = mur_list.find((mur, murIndex) => {
|
|
128
|
+
const cleaned_mur_reference = cleanReference(mur.donnee_entree.reference);
|
|
119
129
|
if (
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
cleaned_mur_reference === cleaned_reference_1 ||
|
|
131
|
+
cleaned_mur_reference === cleaned_reference_2
|
|
122
132
|
) {
|
|
123
133
|
return true;
|
|
124
134
|
}
|
|
@@ -190,11 +200,10 @@ function tv_k(pt_di, di, de, du, pc_id, logement) {
|
|
|
190
200
|
case 'plancher bas / mur':
|
|
191
201
|
case 'plancher haut lourd / mur': {
|
|
192
202
|
const plancher_list = ph_list.concat(pb_list);
|
|
193
|
-
const plancher = plancher_list.find(
|
|
194
|
-
(plancher)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
203
|
+
const plancher = plancher_list.find((plancher) => {
|
|
204
|
+
const cleaned = cleanReference(plancher.donnee_entree.reference);
|
|
205
|
+
return cleaned === cleaned_reference_1 || cleaned === cleaned_reference_2;
|
|
206
|
+
});
|
|
198
207
|
if (!plancher) {
|
|
199
208
|
di.k = defaultValue(type_liaison, pt_di, de);
|
|
200
209
|
console.error(
|
|
@@ -331,11 +340,10 @@ function tv_k(pt_di, di, de, du, pc_id, logement) {
|
|
|
331
340
|
}
|
|
332
341
|
|
|
333
342
|
const menuiserie_list = bv_list.concat(porte_list);
|
|
334
|
-
const menuiserie = menuiserie_list.find(
|
|
335
|
-
(men)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
);
|
|
343
|
+
const menuiserie = menuiserie_list.find((men) => {
|
|
344
|
+
const cleaned = cleanReference(men.donnee_entree.reference);
|
|
345
|
+
return cleaned === cleaned_reference_1 || cleaned === cleaned_reference_2;
|
|
346
|
+
});
|
|
339
347
|
if (!menuiserie) {
|
|
340
348
|
di.k = defaultValue(type_liaison, pt_di, de);
|
|
341
349
|
console.error(
|
|
@@ -30,7 +30,7 @@ vi.mock('./utils.js', () => ({
|
|
|
30
30
|
set_bug_for_bug_compat: vi.fn(),
|
|
31
31
|
tv: vi.fn(),
|
|
32
32
|
requestInput: (de, du, field) => de[field],
|
|
33
|
-
|
|
33
|
+
cleanReference: (reference) => reference,
|
|
34
34
|
get bug_for_bug_compat() {
|
|
35
35
|
return state.bug;
|
|
36
36
|
}
|
package/3_deperdition.js
CHANGED
|
@@ -224,7 +224,12 @@ export default function calc_deperdition(cg, zc, th, effetJoule, dpe, Sh) {
|
|
|
224
224
|
const declaredPontsThermiques = structuredClone(pt_list);
|
|
225
225
|
const vt_list = logement.ventilation_collection.ventilation || [];
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Version de la méthode appliquée au DPE. Absente des DPE partiels : les calculs qui en
|
|
229
|
+
* dépendent retombent alors sur le comportement historique.
|
|
230
|
+
*/
|
|
231
|
+
const versionDpe = parseFloat(dpe.administratif?.enum_version_id);
|
|
232
|
+
mur_list.forEach((mur) => calc_mur(mur, zc, pc, effetJoule, versionDpe));
|
|
228
233
|
pb_list.forEach((pb) => calc_pb(pb, zc, pc, effetJoule, pb_list));
|
|
229
234
|
ph_list.forEach((ph) => calc_ph(ph, zc, pc, effetJoule));
|
|
230
235
|
bv_list.forEach((bv) => calc_bv(bv, zc));
|
package/3_deperdition.spec.js
CHANGED
|
@@ -125,6 +125,7 @@ describe('Upt - déperdition linéique des ponts thermiques', () => {
|
|
|
125
125
|
|
|
126
126
|
/** Fabrique un DPE minimal avec une enveloppe pré-calculée. */
|
|
127
127
|
function makeDpe({
|
|
128
|
+
enum_version_id = '2.4',
|
|
128
129
|
mur = [],
|
|
129
130
|
pb = [],
|
|
130
131
|
ph = [],
|
|
@@ -141,6 +142,7 @@ function makeDpe({
|
|
|
141
142
|
}
|
|
142
143
|
return {
|
|
143
144
|
numero_dpe: 'TEST',
|
|
145
|
+
administratif: enum_version_id === null ? undefined : { enum_version_id },
|
|
144
146
|
logement: {
|
|
145
147
|
sortie: { deperdition },
|
|
146
148
|
enveloppe: {
|
|
@@ -239,6 +241,51 @@ describe("calc_deperdition - agrégation des déperditions de l'enveloppe", () =
|
|
|
239
241
|
expect(calc_ventilation).toHaveBeenCalledTimes(1);
|
|
240
242
|
});
|
|
241
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Certains calculs de Umur dépendent de la version de la méthode appliquée au DPE
|
|
246
|
+
* (cumul du doublage avec une isolation ITE/ITI avant la version 2.4).
|
|
247
|
+
* @see https://github.com/Open3CL/engine/issues/146
|
|
248
|
+
*/
|
|
249
|
+
describe('version du DPE transmise au calcul des murs', () => {
|
|
250
|
+
/** @return {object[]} */
|
|
251
|
+
const murCollection = () => [
|
|
252
|
+
{
|
|
253
|
+
donnee_entree: { surface_paroi_opaque: 10, enum_type_adjacence_id: '1' },
|
|
254
|
+
donnee_intermediaire: { umur: 0.5, b: 1 }
|
|
255
|
+
}
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
test.each([
|
|
259
|
+
['2.3', 2.3],
|
|
260
|
+
['2.4', 2.4],
|
|
261
|
+
['2.5', 2.5]
|
|
262
|
+
])('enum_version_id "%s" transmis à calc_mur', (enum_version_id, expected) => {
|
|
263
|
+
const dpe = makeDpe({
|
|
264
|
+
enum_version_id,
|
|
265
|
+
mur: murCollection(),
|
|
266
|
+
ventilation: [{ donnee_intermediaire: { hvent: 0, hperm: 0 } }],
|
|
267
|
+
deperdition_mur: 5
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
calc_deperdition({ enum_periode_construction_id: '1' }, 'h1a', 'th', '0', dpe, 100);
|
|
271
|
+
|
|
272
|
+
expect(calc_mur).toHaveBeenCalledWith(expect.anything(), 'h1a', '1', '0', expected);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('bloc administratif absent : calc_mur reçoit NaN et conserve le comportement historique', () => {
|
|
276
|
+
const dpe = makeDpe({
|
|
277
|
+
enum_version_id: null,
|
|
278
|
+
mur: murCollection(),
|
|
279
|
+
ventilation: [{ donnee_intermediaire: { hvent: 0, hperm: 0 } }],
|
|
280
|
+
deperdition_mur: 5
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
calc_deperdition({ enum_periode_construction_id: '1' }, 'h1a', 'th', '0', dpe, 100);
|
|
284
|
+
|
|
285
|
+
expect(calc_mur).toHaveBeenCalledWith(expect.anything(), 'h1a', '1', '0', NaN);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
242
289
|
/**
|
|
243
290
|
* calc_Sdep (surface déperditive transmise à la ventilation) exclut les parois à coefficient
|
|
244
291
|
* b nul et les parois adjacentes à un local non déperditif. Les parois à déperdition nulle
|
package/README.md
CHANGED
|
@@ -385,32 +385,62 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
385
385
|
|
|
386
386
|
<!-- CORPUS:START -->
|
|
387
387
|
|
|
388
|
-
> **Version `1.6.2`** · branche `
|
|
388
|
+
> **Version `1.6.2`** · branche `pr-174` · généré le 2026-09-20
|
|
389
389
|
> Seuil de tolérance **5%**
|
|
390
390
|
|
|
391
391
|
<table>
|
|
392
392
|
<tr>
|
|
393
393
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
394
394
|
<td align="center"><strong>89 996</strong><br/><sub>DPE analysés</sub></td>
|
|
395
|
-
<td align="center"><strong>59
|
|
396
|
-
<td align="center"><strong>66,
|
|
395
|
+
<td align="center"><strong>59 522</strong><br/><sub>DPE conformes</sub></td>
|
|
396
|
+
<td align="center"><strong>66,14 %</strong><br/><sub>réussite globale</sub></td>
|
|
397
397
|
</tr>
|
|
398
398
|
</table>
|
|
399
399
|
|
|
400
400
|
| | Corpus | Réussite | | DPE conformes |
|
|
401
401
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
402
402
|
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **45,97 %** | `█████████░░░░░░░░░░░` | 4 597 / 10 000 |
|
|
403
|
-
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,
|
|
404
|
-
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,
|
|
405
|
-
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **87,
|
|
406
|
-
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **73,
|
|
407
|
-
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **69,
|
|
408
|
-
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,
|
|
409
|
-
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **48,
|
|
403
|
+
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **91,62 %** | `██████████████████░░` | 9 162 / 10 000 |
|
|
404
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **88,16 %** | `██████████████████░░` | 8 814 / 9 998 |
|
|
405
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **87,99 %** | `██████████████████░░` | 8 799 / 10 000 |
|
|
406
|
+
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **73,78 %** | `███████████████░░░░░` | 7 377 / 9 999 |
|
|
407
|
+
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **69,51 %** | `██████████████░░░░░░` | 6 951 / 10 000 |
|
|
408
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **62,30 %** | `████████████░░░░░░░░` | 6 229 / 9 999 |
|
|
409
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **48,38 %** | `██████████░░░░░░░░░░` | 4 838 / 10 000 |
|
|
410
410
|
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,55 %** | `██████░░░░░░░░░░░░░░` | 2 755 / 10 000 |
|
|
411
411
|
|
|
412
412
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
413
413
|
|
|
414
|
+
#### Temps d’exécution
|
|
415
|
+
|
|
416
|
+
> Durée de l’appel à `calcul_3cl` par DPE, sur 89 956 calculs.
|
|
417
|
+
> La copie défensive de l’entrée et la lecture du fichier sont exclues de la mesure.
|
|
418
|
+
|
|
419
|
+
<table>
|
|
420
|
+
<tr>
|
|
421
|
+
<td align="center"><strong>5,23 ms</strong><br/><sub>moyenne</sub></td>
|
|
422
|
+
<td align="center"><strong>4,61 ms</strong><br/><sub>médiane</sub></td>
|
|
423
|
+
<td align="center"><strong>0,10 ms</strong><br/><sub>min</sub></td>
|
|
424
|
+
<td align="center"><strong>577,0 ms</strong><br/><sub>max</sub></td>
|
|
425
|
+
</tr>
|
|
426
|
+
</table>
|
|
427
|
+
|
|
428
|
+
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
429
|
+
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
430
|
+
| **Généraliste** | 4,37 ms | 3,96 ms | 0,22 ms | 35,5 ms | 7,93 ms | 10,8 ms |
|
|
431
|
+
| **Appartement · chauffage individuel (2025)** | 3,49 ms | 3,20 ms | 1,13 ms | 30,2 ms | 6,09 ms | 7,79 ms |
|
|
432
|
+
| **Logement individuel (2025)** | 4,96 ms | 4,38 ms | 1,34 ms | 43,1 ms | 9,48 ms | 12,9 ms |
|
|
433
|
+
| **Maison individuelle (2025)** | 5,88 ms | 5,34 ms | 1,68 ms | 47,0 ms | 10,5 ms | 14,2 ms |
|
|
434
|
+
| **Immeuble · chauffage individuel** | 6,66 ms | 5,73 ms | 0,10 ms | 83,7 ms | 13,6 ms | 21,0 ms |
|
|
435
|
+
| **Appartement · chauffage collectif (2025)** | 4,09 ms | 3,75 ms | 1,26 ms | 37,7 ms | 6,88 ms | 9,51 ms |
|
|
436
|
+
| **Immeuble · chauffage collectif** | 6,04 ms | 5,23 ms | 0,15 ms | 148,9 ms | 11,5 ms | 21,3 ms |
|
|
437
|
+
| **Immeuble · chauffage mixte** | 5,79 ms | 4,77 ms | 0,18 ms | 577,0 ms | 12,4 ms | 20,2 ms |
|
|
438
|
+
| **Individuel généré depuis l'immeuble (2026)** | 5,82 ms | 5,08 ms | 1,65 ms | 71,8 ms | 11,1 ms | 16,9 ms |
|
|
439
|
+
|
|
440
|
+
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
441
|
+
dizaines de fois la médiane : c’est la médiane qui décrit le cas courant, et p95/p99 le cas
|
|
442
|
+
défavorable réel.</sub>
|
|
443
|
+
|
|
414
444
|
<!-- CORPUS:END -->
|
|
415
445
|
|
|
416
446
|
📈 Historique complet des versions : [docs/CORPUS-HISTORY.md](docs/CORPUS-HISTORY.md)
|
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.7.
|
|
36
|
+
const LIB_VERSION = '1.7.3';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|