@open3cl/engine 1.7.2 → 1.8.0
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/11_besoin_ecs.js +5 -2
- package/11_ecs.js +99 -2
- package/11_ecs.spec.js +139 -1
- package/3.2.1_mur.js +19 -10
- package/3.2.1_mur.spec.js +105 -0
- package/3_deperdition.js +6 -1
- package/3_deperdition.spec.js +47 -0
- package/README.md +5 -20
- package/conso.js +11 -7
- package/engine.js +3 -2
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.js +22 -13
- package/package.json +3 -2
package/11_besoin_ecs.js
CHANGED
|
@@ -24,11 +24,14 @@ export function calc_besoin_ecs_j(ca, mois, zc, nadeq, depensier) {
|
|
|
24
24
|
export default function calc_besoin_ecs(ca, zc, nadeq) {
|
|
25
25
|
const ret = {
|
|
26
26
|
besoin_ecs: 0,
|
|
27
|
-
besoin_ecs_depensier: 0
|
|
27
|
+
besoin_ecs_depensier: 0,
|
|
28
|
+
besoin_ecs_par_mois: {}
|
|
28
29
|
};
|
|
29
30
|
for (const mois of mois_liste) {
|
|
30
|
-
|
|
31
|
+
const becs_j = calc_besoin_ecs_j(ca, mois, zc, nadeq, false);
|
|
32
|
+
ret.besoin_ecs += becs_j;
|
|
31
33
|
ret.besoin_ecs_depensier += calc_besoin_ecs_j(ca, mois, zc, nadeq, true);
|
|
34
|
+
ret.besoin_ecs_par_mois[mois] = becs_j;
|
|
32
35
|
}
|
|
33
36
|
return ret;
|
|
34
37
|
}
|
package/11_ecs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import enums from './enums.js';
|
|
2
2
|
import calc_gen_ecs from './14_generateur_ecs.js';
|
|
3
|
-
import { tv, requestInput } from './utils.js';
|
|
3
|
+
import { tv, requestInput, mois_liste, Njj } from './utils.js';
|
|
4
4
|
|
|
5
5
|
function tv_rendement_distribution_ecs(di, de, du, pvc) {
|
|
6
6
|
let matcher = {};
|
|
@@ -49,6 +49,89 @@ function tv_rendement_distribution_ecs(di, de, du, pvc) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Calcul de la consommation des auxiliaires de distribution ECS - CAS IMMEUBLE
|
|
54
|
+
* Calcul installation par installation pour les réseaux collectifs bouclés ou avec traçage.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} de - donnee_entree de l'installation ECS
|
|
57
|
+
* @param {number} surfaceImmeuble - surface habitable totale de l'immeuble (m²)
|
|
58
|
+
* @param {object} becsParMois - besoin ECS mensuel de l'immeuble {Janvier: X, Février: X, ...} [kWh]
|
|
59
|
+
* @returns {number} conso_auxiliaire_distribution_ecs_installation [Wh]
|
|
60
|
+
*/
|
|
61
|
+
function calc_auxiliaire_distribution_ecs_immeuble(de, surfaceImmeuble, becsParMois) {
|
|
62
|
+
const type_installation = enums.type_installation[de.enum_type_installation_id];
|
|
63
|
+
|
|
64
|
+
// Seulement pour les installations collectives
|
|
65
|
+
if (!type_installation || !type_installation.includes('collective')) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const enum_bouclage_id = String(de.enum_bouclage_reseau_ecs_id);
|
|
70
|
+
const surface_installation = de.surface_habitable || 0;
|
|
71
|
+
const surface_immeuble_total = surfaceImmeuble || 1;
|
|
72
|
+
const ratio_surface = surface_installation / surface_immeuble_total;
|
|
73
|
+
|
|
74
|
+
// CAS 1 : réseau non bouclé → 0
|
|
75
|
+
if (enum_bouclage_id === '1') {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// CAS 3 : traçage → 0.14 × BECS_annuel × ratio_surface [Wh]
|
|
80
|
+
if (enum_bouclage_id === '3') {
|
|
81
|
+
const becs_annuel_kwh = Object.values(becsParMois).reduce((acc, v) => acc + v, 0);
|
|
82
|
+
// becs_annuel est en kWh → × 1000 pour obtenir des Wh
|
|
83
|
+
return 0.14 * becs_annuel_kwh * 1000 * ratio_surface;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// CAS 2 : réseau bouclé → calcul complet 9 étapes [Wh]
|
|
87
|
+
if (enum_bouclage_id === '2') {
|
|
88
|
+
const Sh_inst = surface_installation;
|
|
89
|
+
const Niv_inst = Number(de.nombre_niveau_installation_ecs) || 1;
|
|
90
|
+
|
|
91
|
+
// Étape 3 : Calcul de Lb pour l'installation i
|
|
92
|
+
// Lb = 1.2 × (1.1 × Sh_inst/nombre_niveaux + 4 × nombre_niveaux)
|
|
93
|
+
const Lb = 1.2 * (1.1 * (Sh_inst / Niv_inst) + 4 * Niv_inst);
|
|
94
|
+
|
|
95
|
+
// Étape 4 : DeltaPb = 0.2 × Lb + 10
|
|
96
|
+
const DeltaPb = 0.2 * Lb + 10;
|
|
97
|
+
|
|
98
|
+
let Qcirc_annuel = 0;
|
|
99
|
+
|
|
100
|
+
for (const mois of mois_liste) {
|
|
101
|
+
const Njj_mois = Njj[mois];
|
|
102
|
+
const becs_immeuble_j_kwh = becsParMois[mois] || 0;
|
|
103
|
+
|
|
104
|
+
// Étape 1 : Qdwj_i [kWh]
|
|
105
|
+
const Qdwj_i = 0.24 * becs_immeuble_j_kwh * ratio_surface;
|
|
106
|
+
|
|
107
|
+
// Étape 2 : qdwj_i [m³/h]
|
|
108
|
+
// Note: la spec officielle utilise Qdwj en Wh et divise par 1000 ensuite.
|
|
109
|
+
// Le moteur fournit becs en kWh, donc Qdwj est en kWh → on ne divise pas par 1000.
|
|
110
|
+
const qdwj_i = Qdwj_i / (5.815 * 5 * Njj_mois);
|
|
111
|
+
|
|
112
|
+
// Étape 5 : Phyd,j [W]
|
|
113
|
+
// Phyd,j = DeltaPb [kPa] × 1000 × qdwj_i [m³/h] / 3600
|
|
114
|
+
const Phyd_j = (DeltaPb * 1000 * qdwj_i) / 3600;
|
|
115
|
+
|
|
116
|
+
// Étape 6 : Effcirb,j (rendement pompe)
|
|
117
|
+
const Effcirb_j = Phyd_j > 0 ? 0.035 * Math.pow(Phyd_j, 0.45) : 0;
|
|
118
|
+
|
|
119
|
+
// Étape 7 : Pcirb,j [W] = max(Phyd,j / Effcirb,j, 20)
|
|
120
|
+
const Pcirb_j = Effcirb_j > 0 ? Math.max(Phyd_j / Effcirb_j, 20) : 20;
|
|
121
|
+
|
|
122
|
+
// Étape 8 : Qcirb,j [Wh]
|
|
123
|
+
const Qcirb_j = (5 * Pcirb_j + 19 * 20) * Njj_mois;
|
|
124
|
+
|
|
125
|
+
Qcirc_annuel += Qcirb_j;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Étape 9 : conso annuelle en Wh
|
|
129
|
+
return Qcirc_annuel;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
52
135
|
export default function calc_ecs(
|
|
53
136
|
dpe,
|
|
54
137
|
ecs,
|
|
@@ -61,7 +144,8 @@ export default function calc_ecs(
|
|
|
61
144
|
virtualisationECS,
|
|
62
145
|
surfaceImmeuble,
|
|
63
146
|
nombreAppartements,
|
|
64
|
-
isImmeubleSystemEcsIndividuels
|
|
147
|
+
isImmeubleSystemEcsIndividuels,
|
|
148
|
+
becsParMois
|
|
65
149
|
) {
|
|
66
150
|
const de = ecs.donnee_entree;
|
|
67
151
|
const di = {};
|
|
@@ -101,6 +185,19 @@ export default function calc_ecs(
|
|
|
101
185
|
0
|
|
102
186
|
);
|
|
103
187
|
|
|
188
|
+
// Calcul de la consommation des auxiliaires de distribution ECS pour l'immeuble
|
|
189
|
+
// Résultat en Wh, converti en kWh pour cohérence avec le reste du moteur
|
|
190
|
+
if (th === 'immeuble' && becsParMois && Object.keys(becsParMois).length > 0) {
|
|
191
|
+
const conso_aux_wh = calc_auxiliaire_distribution_ecs_immeuble(
|
|
192
|
+
de,
|
|
193
|
+
surfaceImmeuble,
|
|
194
|
+
becsParMois
|
|
195
|
+
);
|
|
196
|
+
di.conso_auxiliaire_distribution_ecs = conso_aux_wh / 1000;
|
|
197
|
+
} else {
|
|
198
|
+
di.conso_auxiliaire_distribution_ecs = 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
104
201
|
ecs.donnee_intermediaire = di;
|
|
105
202
|
ecs.donnee_utilisateur = du;
|
|
106
203
|
}
|
package/11_ecs.spec.js
CHANGED
|
@@ -21,7 +21,10 @@ vi.mock('./enums.js', () => ({
|
|
|
21
21
|
vi.mock('./utils.js', () => ({
|
|
22
22
|
set_bug_for_bug_compat: vi.fn(),
|
|
23
23
|
tv: vi.fn(),
|
|
24
|
-
requestInput: vi.fn()
|
|
24
|
+
requestInput: vi.fn(),
|
|
25
|
+
// Calendrier réduit à deux mois pour le calcul mensuel des auxiliaires de distribution
|
|
26
|
+
mois_liste: ['Janvier', 'Février'],
|
|
27
|
+
Njj: { Janvier: 31, Février: 28 }
|
|
25
28
|
}));
|
|
26
29
|
|
|
27
30
|
vi.mock('./14_generateur_ecs.js', () => ({
|
|
@@ -281,3 +284,138 @@ describe('calc_ecs - agrégation des consommations', () => {
|
|
|
281
284
|
expect(ecs.donnee_intermediaire.conso_ecs_depensier).toBe(28);
|
|
282
285
|
});
|
|
283
286
|
});
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Consommation des auxiliaires de distribution ECS - cas immeuble
|
|
290
|
+
* Seuls les réseaux collectifs bouclés ou avec traçage consomment de l'énergie.
|
|
291
|
+
*/
|
|
292
|
+
describe('calc_ecs - auxiliaires de distribution ECS (immeuble)', () => {
|
|
293
|
+
/** Calcule l'ECS d'un immeuble et retourne la conso des auxiliaires de distribution [kWh]. */
|
|
294
|
+
function consoAuxDistribution(deExtra, becsParMois, surfaceImmeuble = 400, th = 'immeuble') {
|
|
295
|
+
const ecs = makeEcs({ tv_rendement_distribution_ecs_id: '5', ...deExtra });
|
|
296
|
+
calc_ecs(
|
|
297
|
+
{},
|
|
298
|
+
ecs,
|
|
299
|
+
100,
|
|
300
|
+
200,
|
|
301
|
+
1,
|
|
302
|
+
'ca1',
|
|
303
|
+
'h1a',
|
|
304
|
+
th,
|
|
305
|
+
false,
|
|
306
|
+
surfaceImmeuble,
|
|
307
|
+
null,
|
|
308
|
+
false,
|
|
309
|
+
becsParMois
|
|
310
|
+
);
|
|
311
|
+
return ecs.donnee_intermediaire.conso_auxiliaire_distribution_ecs;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
test.each([
|
|
315
|
+
['logement autre qu’un immeuble', 'maison', { Janvier: 1000 }],
|
|
316
|
+
['besoin mensuel absent', 'immeuble', undefined],
|
|
317
|
+
['besoin mensuel vide', 'immeuble', {}]
|
|
318
|
+
])('%s : pas de consommation', (_libelle, th, becsParMois) => {
|
|
319
|
+
expect(
|
|
320
|
+
consoAuxDistribution(
|
|
321
|
+
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2' },
|
|
322
|
+
becsParMois,
|
|
323
|
+
400,
|
|
324
|
+
th
|
|
325
|
+
)
|
|
326
|
+
).toBe(0);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test.each([
|
|
330
|
+
['type d’installation inconnu', { enum_type_installation_id: '9' }],
|
|
331
|
+
['installation individuelle', { enum_type_installation_id: '1' }],
|
|
332
|
+
[
|
|
333
|
+
'réseau collectif non bouclé',
|
|
334
|
+
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '1' }
|
|
335
|
+
],
|
|
336
|
+
['type de bouclage non renseigné', { enum_type_installation_id: '2' }]
|
|
337
|
+
])('%s : pas de consommation', (_libelle, de) => {
|
|
338
|
+
expect(consoAuxDistribution(de, { Janvier: 1000 })).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('réseau avec traçage : 0,14 × besoin annuel × ratio de surface', () => {
|
|
342
|
+
const conso = consoAuxDistribution(
|
|
343
|
+
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: 3, surface_habitable: 100 },
|
|
344
|
+
{ Janvier: 100, Février: 50 }
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// 0,14 × 150 kWh × 1000 × (100 / 400) = 5250 Wh => 5,25 kWh
|
|
348
|
+
expect(conso).toBeCloseTo(5.25, 9);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('réseau avec traçage sans surfaces renseignées : ratio de surface nul', () => {
|
|
352
|
+
const conso = consoAuxDistribution(
|
|
353
|
+
{ enum_type_installation_id: '3', enum_bouclage_reseau_ecs_id: '3' },
|
|
354
|
+
{ Janvier: 100 },
|
|
355
|
+
null
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// surface_habitable absente => 0 ; surface de l'immeuble absente => 1
|
|
359
|
+
expect(conso).toBe(0);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test('réseau bouclé sans besoin : puissance minimale du circulateur de 20 W', () => {
|
|
363
|
+
const conso = consoAuxDistribution(
|
|
364
|
+
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
|
|
365
|
+
{ Janvier: 0 }
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Pcirb = 20 W chaque mois : (5 × 20 + 19 × 20) × (31 + 28) jours = 28320 Wh
|
|
369
|
+
expect(conso).toBeCloseTo(28.32, 9);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('réseau bouclé à faible besoin : puissance du circulateur ramenée au minimum de 20 W', () => {
|
|
373
|
+
const conso = consoAuxDistribution(
|
|
374
|
+
{
|
|
375
|
+
enum_type_installation_id: '2',
|
|
376
|
+
enum_bouclage_reseau_ecs_id: '2',
|
|
377
|
+
surface_habitable: 100,
|
|
378
|
+
nombre_niveau_installation_ecs: 2
|
|
379
|
+
},
|
|
380
|
+
{ Janvier: 1000, Février: 1000 }
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
// Phyd / Effcirb < 20 W => Pcirb = 20 W, identique au cas sans besoin
|
|
384
|
+
expect(conso).toBeCloseTo(28.32, 9);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('réseau bouclé : calcul complet de la consommation du circulateur', () => {
|
|
388
|
+
const conso = consoAuxDistribution(
|
|
389
|
+
{
|
|
390
|
+
enum_type_installation_id: '2',
|
|
391
|
+
enum_bouclage_reseau_ecs_id: '2',
|
|
392
|
+
surface_habitable: 100,
|
|
393
|
+
nombre_niveau_installation_ecs: '2'
|
|
394
|
+
},
|
|
395
|
+
{ Janvier: 10000 }
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Lb = 1,2 × (1,1 × 100 / 2 + 4 × 2) = 75,6 m ; ΔPb = 0,2 × Lb + 10 = 25,12 kPa
|
|
399
|
+
// Janvier : Phyd ≈ 4,645 W => Pcirb ≈ 66,49 W ; Février (besoin absent) : Pcirb = 20 W
|
|
400
|
+
// Référence de régression (calcul des 9 étapes de la méthode) : 35526,3879475995 Wh
|
|
401
|
+
expect(conso).toBeCloseTo(35.526387947599495, 9);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('réseau bouclé sans nombre de niveaux : un seul niveau par défaut', () => {
|
|
405
|
+
const avecUnNiveau = consoAuxDistribution(
|
|
406
|
+
{
|
|
407
|
+
enum_type_installation_id: '2',
|
|
408
|
+
enum_bouclage_reseau_ecs_id: '2',
|
|
409
|
+
surface_habitable: 100,
|
|
410
|
+
nombre_niveau_installation_ecs: 1
|
|
411
|
+
},
|
|
412
|
+
{ Janvier: 10000 }
|
|
413
|
+
);
|
|
414
|
+
const sansNiveau = consoAuxDistribution(
|
|
415
|
+
{ enum_type_installation_id: '2', enum_bouclage_reseau_ecs_id: '2', surface_habitable: 100 },
|
|
416
|
+
{ Janvier: 10000 }
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(sansNiveau).toBe(avecUnNiveau);
|
|
420
|
+
});
|
|
421
|
+
});
|
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_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
|
@@ -23,8 +23,6 @@ Implémentation open source du moteur Open3CL de l'ADEME.
|
|
|
23
23
|
·
|
|
24
24
|
<a href="https://github.com/Open3CL/engine/issues/new?labels=enhancement&template=feature-request---.md">Créer une feature</a>
|
|
25
25
|
·
|
|
26
|
-
<a href="https://open3cl.github.io/engine/build/">Démo en ligne</a>
|
|
27
|
-
·
|
|
28
26
|
<a href="https://open3cl.github.io/engine/reports/corpus">Rapports de corpus</a>
|
|
29
27
|
</p>
|
|
30
28
|
</div>
|
|
@@ -57,7 +55,6 @@ Implémentation open source du moteur Open3CL de l'ADEME.
|
|
|
57
55
|
<li><a href="#api-publique">API publique</a></li>
|
|
58
56
|
<li><a href="#options-de-calcul">Options de calcul</a></li>
|
|
59
57
|
<li><a href="#lire-le-résultat">Lire le résultat</a></li>
|
|
60
|
-
<li><a href="#tester-un-dpe-sans-écrire-de-code">Tester un DPE sans écrire de code</a></li>
|
|
61
58
|
</ul>
|
|
62
59
|
</li>
|
|
63
60
|
<li><a href="#-tests-de-corpus">Tests de corpus</a>
|
|
@@ -142,12 +139,11 @@ flowchart LR
|
|
|
142
139
|
|
|
143
140
|
### L'écosystème Open3CL
|
|
144
141
|
|
|
145
|
-
| Ressource | Description
|
|
146
|
-
| :-------------------------------------------------------------------- |
|
|
147
|
-
| [open3cl.fr](https://open3cl.fr) | Le site du projet
|
|
148
|
-
| [
|
|
149
|
-
| [
|
|
150
|
-
| [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine) | Le paquet npm |
|
|
142
|
+
| Ressource | Description |
|
|
143
|
+
| :-------------------------------------------------------------------- | :-------------------------------------------------------- |
|
|
144
|
+
| [open3cl.fr](https://open3cl.fr) | Le site du projet |
|
|
145
|
+
| [Rapports de corpus](https://open3cl.github.io/engine/reports/corpus) | Le tableau de bord interactif des résultats sur DPE réels |
|
|
146
|
+
| [@open3cl/engine](https://www.npmjs.com/package/@open3cl/engine) | Le paquet npm |
|
|
151
147
|
|
|
152
148
|
<p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
|
|
153
149
|
|
|
@@ -292,17 +288,6 @@ Le DPE renvoyé est l'objet d'entrée, enrichi de `logement.sortie` :
|
|
|
292
288
|
| `sortie.confort_ete` / `qualite_isolation` | Indicateurs de confort d'été et de qualité d'isolation |
|
|
293
289
|
| `sortie.production_electricite` | Production photovoltaïque |
|
|
294
290
|
|
|
295
|
-
### Tester un DPE sans écrire de code
|
|
296
|
-
|
|
297
|
-
Le [démonstrateur en ligne](https://open3cl.github.io/engine/build/) accepte un DPE au format XML, l'envoie au moteur et
|
|
298
|
-
affiche **côte à côte** les valeurs du DPE d'origine, celles calculées par Open3CL, et le différentiel.
|
|
299
|
-
|
|
300
|
-
C'est le moyen le plus rapide de qualifier un écart : problème dans le DPE d'origine, ou bug dans la librairie ?
|
|
301
|
-
|
|
302
|
-
<p align="right">(<a href="#readme-top">Retour sommaire</a>)</p>
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
291
|
## 🧪 Tests de corpus
|
|
307
292
|
|
|
308
293
|
> Un corpus, c'est une liste de numéros de DPE réels. Le moteur les rejoue **tous**, compare ses sorties à celles du
|
package/conso.js
CHANGED
|
@@ -249,6 +249,7 @@ export default function calc_conso(
|
|
|
249
249
|
null,
|
|
250
250
|
prorataECS,
|
|
251
251
|
prorataChauffage,
|
|
252
|
+
ecs,
|
|
252
253
|
conso_aux_distribution_ecs_total
|
|
253
254
|
),
|
|
254
255
|
ep_conso: calc_conso_pond(
|
|
@@ -262,6 +263,7 @@ export default function calc_conso(
|
|
|
262
263
|
coeffEp,
|
|
263
264
|
prorataECS,
|
|
264
265
|
prorataChauffage,
|
|
266
|
+
ecs,
|
|
265
267
|
conso_aux_distribution_ecs_total
|
|
266
268
|
),
|
|
267
269
|
emission_ges: calc_conso_pond(
|
|
@@ -275,6 +277,7 @@ export default function calc_conso(
|
|
|
275
277
|
coef_ges,
|
|
276
278
|
prorataECS,
|
|
277
279
|
prorataChauffage,
|
|
280
|
+
ecs,
|
|
278
281
|
conso_aux_distribution_ecs_total
|
|
279
282
|
),
|
|
280
283
|
cout: calc_conso_pond(
|
|
@@ -288,6 +291,7 @@ export default function calc_conso(
|
|
|
288
291
|
coef_cout,
|
|
289
292
|
prorataECS,
|
|
290
293
|
prorataChauffage,
|
|
294
|
+
ecs,
|
|
291
295
|
conso_aux_distribution_ecs_total
|
|
292
296
|
)
|
|
293
297
|
};
|
|
@@ -343,7 +347,8 @@ export default function calc_conso(
|
|
|
343
347
|
'',
|
|
344
348
|
null,
|
|
345
349
|
prorataECS,
|
|
346
|
-
prorataChauffage
|
|
350
|
+
prorataChauffage,
|
|
351
|
+
type_energie === 'électricité' ? ecs : []
|
|
347
352
|
);
|
|
348
353
|
conso_en = {
|
|
349
354
|
conso_ch: conso_en._ch,
|
|
@@ -474,7 +479,7 @@ function calc_conso_pond(
|
|
|
474
479
|
coef,
|
|
475
480
|
prorataECS,
|
|
476
481
|
prorataChauffage,
|
|
477
|
-
|
|
482
|
+
ecs_installations
|
|
478
483
|
) {
|
|
479
484
|
const ret = {};
|
|
480
485
|
ret.auxiliaire_ventilation = vt_list.reduce((acc, vt) => {
|
|
@@ -538,11 +543,10 @@ function calc_conso_pond(
|
|
|
538
543
|
return acc + getConso(coef, 'électricité auxiliaire', conso);
|
|
539
544
|
}, 0);
|
|
540
545
|
|
|
541
|
-
ret.auxiliaire_distribution_ecs =
|
|
542
|
-
|
|
543
|
-
'électricité auxiliaire',
|
|
544
|
-
|
|
545
|
-
);
|
|
546
|
+
ret.auxiliaire_distribution_ecs = ecs_installations.reduce((acc, inst) => {
|
|
547
|
+
const conso = inst.donnee_intermediaire.conso_auxiliaire_distribution_ecs || 0;
|
|
548
|
+
return acc + getConso(coef, 'électricité auxiliaire', conso);
|
|
549
|
+
}, 0);
|
|
546
550
|
|
|
547
551
|
ret.ecs = getEcsConso(gen_ecs, 'conso_ecs', coef, prorataECS, prefix);
|
|
548
552
|
|
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.
|
|
36
|
+
const LIB_VERSION = '1.8.0';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -437,7 +437,8 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
437
437
|
virtualisationECS,
|
|
438
438
|
dpe.logement.caracteristique_generale.surface_habitable_immeuble,
|
|
439
439
|
dpe.logement.caracteristique_generale.nombre_appartement,
|
|
440
|
-
isImmeubleSystemEcsIndividuels
|
|
440
|
+
isImmeubleSystemEcsIndividuels,
|
|
441
|
+
apport_et_besoin.besoin_ecs_par_mois || {}
|
|
441
442
|
);
|
|
442
443
|
|
|
443
444
|
if (!dpeGenereImmeuble) {
|
|
@@ -129,20 +129,29 @@ export class DeperditionMurService extends DeperditionService {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Pour l’ensemble des parois, la présence d’un doublage apporte une résistance thermique supplémentaire
|
|
132
|
+
* Pour l’ensemble des parois, la présence d’un doublage apporte une résistance thermique supplémentaire.
|
|
133
|
+
* Cependant, lorsqu'une isolation ITE ou ITI est présente, le doublage NE doit PAS être cumulé.
|
|
134
|
+
* Le doublage n'est pris en compte que si aucune isolation ITE/ITI n'est appliquée.
|
|
135
|
+
* Types d'isolation avec ITE ou ITI : 3 (iti), 4 (ite), 6 (iti+ite), 7 (iti+itr), 8 (ite+itr)
|
|
136
|
+
* @see https://github.com/Open3CL/engine/issues/146
|
|
133
137
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
const typeIsolation = parseInt(murDE.enum_type_isolation_id) || 1;
|
|
139
|
+
const hasIteOrIti = [3, 4, 6, 7, 8].includes(typeIsolation);
|
|
140
|
+
|
|
141
|
+
if (!hasIteOrIti) {
|
|
142
|
+
switch (murDE.enum_type_doublage_id) {
|
|
143
|
+
case '3':
|
|
144
|
+
// doublage indéterminé ou lame d'air inf 15 mm
|
|
145
|
+
umur0 = 1 / (1 / umur0 + 0.1);
|
|
146
|
+
break;
|
|
147
|
+
case '4': // doublage indéterminé ou lame d'air sup 15 mm
|
|
148
|
+
case '5': // doublage connu (plâtre brique bois)
|
|
149
|
+
umur0 = 1 / (1 / umur0 + 0.21);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
// absence de doublage ou inconnu
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open3cl/engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Open Source 3CL-DPE engine",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
"prepare": "husky install",
|
|
29
29
|
"assets:sync": "node scripts/synchronize_assets.js",
|
|
30
30
|
"version:update": "node scripts/update_version.js",
|
|
31
|
+
"reports:charts": "node scripts/generate_corpus_history_chart.js",
|
|
31
32
|
"reports:preview": "npm run http-server dist/reports/corpus -o",
|
|
32
|
-
"reports:readme": "node scripts/generate_corpus_readme.js"
|
|
33
|
+
"reports:readme": "node scripts/generate_corpus_readme.js && npm run reports:charts"
|
|
33
34
|
},
|
|
34
35
|
"repository": {
|
|
35
36
|
"type": "git",
|