@open3cl/engine 1.6.1 → 1.6.2
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/10_besoin_fr.spec.js +122 -0
- package/10_clim.spec.js +116 -0
- package/11_besoin_ecs.spec.js +69 -0
- package/11_ecs.spec.js +282 -0
- package/11_nadeq.spec.js +37 -0
- package/12.4_pac.spec.js +131 -0
- package/13.2_generateur_combustion.js +2 -0
- package/13.2_generateur_combustion.spec.js +537 -0
- package/13.2_generateur_combustion_bouilleur.js +4 -1
- package/13.2_generateur_combustion_bouilleur.spec.js +74 -0
- package/13.2_generateur_combustion_ch.spec.js +323 -0
- package/13.2_generateur_combustion_chaudiere.spec.js +85 -0
- package/13.2_generateur_pac.spec.js +64 -0
- package/13_rendement_distribution_ecs.spec.js +38 -0
- package/14_generateur_ecs.js +4 -4
- package/14_generateur_ecs.spec.js +745 -0
- package/15_conso_aux.spec.js +225 -0
- package/16.2_production_enr.spec.js +188 -0
- package/16_conso_eclairage.spec.js +62 -0
- package/2021_04_13_confort_ete.spec.js +159 -0
- package/2021_04_13_qualite_isolation.js +1 -1
- package/2021_04_13_qualite_isolation.spec.js +286 -0
- package/3.1_b.js +1 -1
- package/3.1_b.spec.js +367 -0
- package/3.2.1_mur.spec.js +812 -39
- package/3.2.2_plancher_bas.spec.js +643 -78
- package/3.2.3_plancher_haut.spec.js +452 -0
- package/3.3.1.4_porte.spec.js +114 -0
- package/3.3_baie_vitree.spec.js +449 -295
- package/3.4_pont_thermique.spec.js +890 -0
- package/3_deperdition.spec.js +397 -0
- package/4_ventilation.js +1 -1
- package/4_ventilation.spec.js +406 -0
- package/5_conso_ventilation.spec.js +186 -0
- package/6.2_surface_sud_equivalente.spec.js +138 -0
- package/8_intermittence.spec.js +33 -0
- package/9_besoin_ch.js +2 -2
- package/9_besoin_ch.spec.js +381 -0
- package/9_chauffage.js +4 -0
- package/9_chauffage.spec.js +478 -92
- package/9_conso_ch.spec.js +502 -248
- package/9_emetteur_ch.spec.js +339 -0
- package/9_generateur_ch.spec.js +467 -81
- package/apport_et_besoin.spec.js +126 -0
- package/conso.js +1 -1
- package/conso.spec.js +471 -82
- package/core/assets/domain/add-additionnal-ue-values-tables.spec.js +53 -0
- package/core/assets/domain/synchronize-solicitations-tables.spec.js +25 -0
- package/core/assets/domain/synchronize-valeur-tables.spec.js +48 -0
- package/core/conf/infrastructure/application.config.spec.js +35 -0
- package/core/file/infrastructure/adapter/csv-parser.store.spec.js +31 -13
- package/core/file/infrastructure/adapter/file.store.spec.js +148 -12
- package/core/tv/infrastructure/assets/additional-ue-values.spec.js +36 -0
- package/core/util/infrastructure/object-util.spec.js +20 -0
- package/core/util/logger/log-service.js +16 -7
- package/core/util/logger/log-service.spec.js +101 -0
- package/dpe-sanitizer.service.spec.js +138 -0
- package/engine.js +4 -1
- package/engine.spec.js +854 -0
- package/enums.spec.js +46 -0
- package/features/dpe/domain/models/type-generateur.model.js +1 -2
- package/features/dpe/domain/models/type-generateur.model.spec.js +25 -0
- package/features/dpe/domain/models/type-habitation.model.spec.js +41 -0
- package/features/dpe/domain/models/type-stockage.model.spec.js +18 -0
- package/features/dpe/domain/models/type-ventilation.model.spec.js +19 -0
- package/features/dpe/infrastructure/ch/chTv.store.spec.js +4 -4
- package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.js +14 -2
- package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.spec.js +16 -0
- package/features/dpe/infrastructure/tv.store.js +28 -9
- package/features/dpe/infrastructure/tv.store.spec.js +44 -0
- package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +2 -1
- package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.spec.js +18 -0
- package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +33 -1
- package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +21 -1
- package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +41 -1
- package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +44 -1
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +2 -1
- package/features/engine/domain/ch/generateur-ch.service.js +2 -0
- package/features/engine/domain/ch/generateur-ch.service.spec.js +117 -2
- package/features/engine/domain/ch/installation-ch.service.spec.js +14 -0
- package/features/engine/domain/conso/froid/conso-froid.service.spec.js +82 -1
- package/features/engine/domain/constants.spec.js +14 -0
- package/features/engine/domain/contexte.builder.spec.js +21 -0
- package/features/engine/domain/ecs/generateur-ecs.service.spec.js +11 -0
- package/features/engine/domain/ecs/installation-ecs.service.spec.js +55 -2
- package/features/engine/domain/engine.service.spec.js +227 -4
- package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +124 -1
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +270 -2
- package/features/engine/domain/enveloppe/deperdition.service.spec.js +141 -0
- package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +2 -1
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +117 -1
- package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +134 -1
- package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +126 -1
- package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +18 -1
- package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +2 -1
- package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +67 -1
- package/features/normalizer/domain/dpe-normalizer.service.js +1 -1
- package/features/normalizer/domain/dpe-normalizer.service.spec.js +53 -2
- package/ficheTechnique.spec.js +26 -0
- package/index.spec.js +54 -0
- package/package.json +5 -2
- package/preprocessor/index.js +20 -0
- package/preprocessor/index.spec.js +15 -0
- package/tv.spec.js +38 -0
- package/utils.js +43 -1
- package/utils.spec.js +518 -2
- package/output.js +0 -1
package/conso.js
CHANGED
|
@@ -98,7 +98,7 @@ const coef_cout = {
|
|
|
98
98
|
* @param destination {'ch' | 'ecs' | 'éclairage'}
|
|
99
99
|
* @return {string} la clef à utiliser pour récupérer le coefficient depuis la table `coef_ges`
|
|
100
100
|
*/
|
|
101
|
-
function getCoefKey(type_energie, destination) {
|
|
101
|
+
export function getCoefKey(type_energie, destination) {
|
|
102
102
|
if (type_energie === 'électricité') {
|
|
103
103
|
if (!['ch', 'ecs', 'éclairage'].includes(destination)) {
|
|
104
104
|
console.error(`Type d'électricité inconnu: ${destination}`);
|
package/conso.spec.js
CHANGED
|
@@ -1,91 +1,480 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dépendances mockées pour isoler `conso.js` :
|
|
5
|
+
* - `enums` : libellés de type d'énergie / classe d'altitude / zone climatique ;
|
|
6
|
+
* - `16_conso_eclairage` : consommation d'éclairage forcée à 1 kWh/m² (=> conso = Sh) ;
|
|
7
|
+
* - `tvs` : seuils de classe DPE / GES contrôlés ;
|
|
8
|
+
* - `tv` (utils) : lecture des réseaux de chaleur, retour maîtrisé.
|
|
9
|
+
* Les tables internes du module (coef_ges, coef_cout, coef_ep...) ne sont pas mockées :
|
|
10
|
+
* elles font partie du code testé.
|
|
11
|
+
*/
|
|
12
|
+
vi.mock('./enums.js', () => ({
|
|
13
|
+
default: {
|
|
14
|
+
type_energie: {
|
|
15
|
+
1: 'électricité',
|
|
16
|
+
2: 'gaz naturel',
|
|
17
|
+
3: 'réseau de chauffage urbain',
|
|
18
|
+
4: 'inconnu'
|
|
19
|
+
},
|
|
20
|
+
classe_altitude: { 1: 'inférieur à 400m', 2: 'supérieur à 800m' },
|
|
21
|
+
zone_climatique: { 1: 'h1a', 2: 'h1b' }
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('./16_conso_eclairage.js', () => ({ default: vi.fn(() => 1) }));
|
|
26
|
+
|
|
27
|
+
vi.mock('./tv.js', () => ({
|
|
28
|
+
default: {
|
|
29
|
+
dpe_class_limit: {
|
|
30
|
+
'inférieur à 400m': { 100: { A: 10, B: 20, C: 30, D: 40, E: 300, F: 400 } },
|
|
31
|
+
'supérieur à 800m': {}
|
|
32
|
+
},
|
|
33
|
+
ges_class_limit: {
|
|
34
|
+
'inférieur à 400m': { 100: { A: 1, B: 2, C: 3, D: 4, E: 60, F: 90 } },
|
|
35
|
+
'supérieur à 800m': {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock('./utils.js', () => ({
|
|
41
|
+
tv: vi.fn((table, matcher) => {
|
|
42
|
+
// Réseau de chaleur "R1" connu, "R2" inconnu
|
|
43
|
+
if (matcher?.identifiant_reseau === 'R1') return { contenu_co2_acv: 0.15 };
|
|
44
|
+
return null;
|
|
45
|
+
})
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
default: calc_conso,
|
|
50
|
+
classe_bilan_dpe,
|
|
51
|
+
classe_emission_ges,
|
|
52
|
+
getCoefKey,
|
|
53
|
+
coef_ep
|
|
54
|
+
} = await import('./conso.js');
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Classe énergétique (bilan DPE)
|
|
58
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf
|
|
59
|
+
*/
|
|
60
|
+
describe('classe_bilan_dpe - seuils de classe énergétique', () => {
|
|
61
|
+
test('consommation nulle (null) : aucune classe', () => {
|
|
62
|
+
expect(classe_bilan_dpe(null, 1, 1, 100)).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ca_id=1 => seuils personnalisés {A:10,B:20,C:30,D:40,E:300,F:400}
|
|
66
|
+
test.each([
|
|
67
|
+
[5, 'A'],
|
|
68
|
+
[15, 'B'],
|
|
69
|
+
[25, 'C'],
|
|
70
|
+
[35, 'D'],
|
|
71
|
+
[250, 'E'],
|
|
72
|
+
[350, 'F'],
|
|
73
|
+
[450, 'G']
|
|
74
|
+
])('seuils personnalisés : conso %s => classe %s', (conso, classe) => {
|
|
75
|
+
expect(classe_bilan_dpe(conso, 1, 1, 100)).toBe(classe);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ca_id=2 => seuils absents => valeurs par défaut
|
|
79
|
+
test('valeurs par défaut A-D quand la table ne fournit pas de seuil', () => {
|
|
80
|
+
expect(classe_bilan_dpe(50, 1, 2, 100)).toBe('A'); // < 70
|
|
81
|
+
expect(classe_bilan_dpe(100, 1, 2, 100)).toBe('B'); // < 110
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('zone climatique standard : seuils E/F par défaut 330/420', () => {
|
|
85
|
+
// zc_id=1 (h1a) non concernée par le cas montagne => 330/420
|
|
86
|
+
expect(classe_bilan_dpe(300, 1, 2, 100)).toBe('E');
|
|
87
|
+
expect(classe_bilan_dpe(400, 1, 2, 100)).toBe('F');
|
|
88
|
+
expect(classe_bilan_dpe(500, 1, 2, 100)).toBe('G');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('zone montagne (h1b, > 800m) : seuils E/F relevés à 390/500', () => {
|
|
92
|
+
// zc_id=2 (h1b) + ca 'supérieur à 800m' => 390/500
|
|
93
|
+
expect(classe_bilan_dpe(350, 2, 2, 100)).toBe('E');
|
|
94
|
+
expect(classe_bilan_dpe(450, 2, 2, 100)).toBe('F');
|
|
95
|
+
expect(classe_bilan_dpe(600, 2, 2, 100)).toBe('G');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Classe climat (émissions GES)
|
|
101
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf
|
|
102
|
+
*/
|
|
103
|
+
describe('classe_emission_ges - seuils de classe climat', () => {
|
|
104
|
+
test('émission nulle (null) : aucune classe', () => {
|
|
105
|
+
expect(classe_emission_ges(null, 1, 1, 100)).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test.each([
|
|
109
|
+
[0.5, 'A'],
|
|
110
|
+
[1.5, 'B'],
|
|
111
|
+
[2.5, 'C'],
|
|
112
|
+
[3.5, 'D'],
|
|
113
|
+
[55, 'E'],
|
|
114
|
+
[70, 'F'],
|
|
115
|
+
[95, 'G']
|
|
116
|
+
])('seuils personnalisés : émission %s => classe %s', (emission, classe) => {
|
|
117
|
+
expect(classe_emission_ges(emission, 1, 1, 100)).toBe(classe);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('valeurs par défaut A-D quand la table ne fournit pas de seuil', () => {
|
|
121
|
+
expect(classe_emission_ges(3, 1, 2, 100)).toBe('A'); // < 6
|
|
122
|
+
expect(classe_emission_ges(10, 1, 2, 100)).toBe('B'); // < 11
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('zone climatique standard : seuils E/F par défaut 70/100', () => {
|
|
126
|
+
expect(classe_emission_ges(60, 1, 2, 100)).toBe('E');
|
|
127
|
+
expect(classe_emission_ges(80, 1, 2, 100)).toBe('F');
|
|
128
|
+
expect(classe_emission_ges(120, 1, 2, 100)).toBe('G');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('zone montagne (h1b, > 800m) : seuils E/F relevés à 80/110', () => {
|
|
132
|
+
expect(classe_emission_ges(70, 2, 2, 100)).toBe('E');
|
|
133
|
+
expect(classe_emission_ges(90, 2, 2, 100)).toBe('F');
|
|
134
|
+
expect(classe_emission_ges(120, 2, 2, 100)).toBe('G');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Construction de la clef de la table `coef_ges` pour l'électricité (dépend de l'usage).
|
|
140
|
+
*/
|
|
141
|
+
describe('getCoefKey - clef du coefficient GES', () => {
|
|
142
|
+
test('énergie non électrique : le libellé est renvoyé tel quel', () => {
|
|
143
|
+
expect(getCoefKey('gaz naturel', 'ch')).toBe('gaz naturel');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("électricité : suffixe d'usage ajouté", () => {
|
|
147
|
+
expect(getCoefKey('électricité', 'ch')).toBe('électricité ch');
|
|
148
|
+
expect(getCoefKey('électricité', 'ecs')).toBe('électricité ecs');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('électricité avec usage inconnu : erreur signalée mais clef construite', () => {
|
|
152
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
153
|
+
expect(getCoefKey('électricité', 'xxx')).toBe('électricité xxx');
|
|
154
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('xxx'));
|
|
155
|
+
errorSpy.mockRestore();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
/** Fabrique un générateur de chauffage. */
|
|
160
|
+
function genCh(energieId, di, deExtra = {}) {
|
|
161
|
+
return {
|
|
162
|
+
donnee_entree: { enum_type_energie_id: energieId, ...deExtra },
|
|
163
|
+
donnee_intermediaire: di
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Fabrique une installation de chauffage. */
|
|
168
|
+
function installCh(deExtra, gens) {
|
|
169
|
+
return {
|
|
170
|
+
donnee_entree: {
|
|
171
|
+
enum_type_installation_id: '1',
|
|
172
|
+
enum_methode_calcul_conso_id: '1',
|
|
173
|
+
...deExtra
|
|
174
|
+
},
|
|
175
|
+
generateur_chauffage_collection: { generateur_chauffage: gens }
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Fabrique un générateur ECS. */
|
|
180
|
+
function genEcs(energieId, di, deExtra = {}) {
|
|
181
|
+
return {
|
|
182
|
+
donnee_entree: { enum_type_energie_id: energieId, ...deExtra },
|
|
183
|
+
donnee_intermediaire: di
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Fabrique une installation ECS. */
|
|
188
|
+
function installEcs(deExtra, gens) {
|
|
189
|
+
return {
|
|
190
|
+
donnee_entree: { ...deExtra },
|
|
191
|
+
generateur_ecs_collection: { generateur_ecs: gens }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const DATE_DPE = '2024-01-01';
|
|
196
|
+
|
|
197
|
+
describe('calc_conso - agrégation des consommations', () => {
|
|
198
|
+
let errorSpy;
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
afterEach(() => {
|
|
205
|
+
errorSpy.mockRestore();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('chauffage électrique individuel : EF, EP (×1,9), GES et coût déterministes', () => {
|
|
10
209
|
const ch = [
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
210
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
211
|
+
genCh('1', { conso_ch: 1000, conso_ch_depensier: 1200 })
|
|
212
|
+
])
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
216
|
+
|
|
217
|
+
// EF : passe-plat (coefficient nul)
|
|
218
|
+
expect(res.ef_conso.conso_ch).toBe(1000);
|
|
219
|
+
// EP : coefficient électricité 1,9
|
|
220
|
+
expect(res.ep_conso.ep_conso_ch).toBeCloseTo(1900, 9);
|
|
221
|
+
// GES : coef_ges['électricité ch'] = 0,079
|
|
222
|
+
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(79, 9);
|
|
223
|
+
// Coût : cout_electricite(1000) = 149 + 0,14066 * 1000
|
|
224
|
+
expect(res.cout.cout_ch).toBeCloseTo(149 + 0.14066 * 1000, 9);
|
|
225
|
+
// Classes calculées et injectées
|
|
226
|
+
expect(res.ep_conso.classe_bilan_dpe).toBeDefined();
|
|
227
|
+
expect(res.emission_ges.classe_emission_ges).toBeDefined();
|
|
228
|
+
// Éclairage = calc_conso_eclairage(zc) * Sh = 1 * 100 (passe-plat EF)
|
|
229
|
+
expect(res.ef_conso.conso_eclairage).toBe(100);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('installation collective (méthode 2) : clé de répartition appliquée à conso et distribution', () => {
|
|
233
|
+
const ch = [
|
|
234
|
+
installCh(
|
|
235
|
+
{
|
|
236
|
+
cle_repartition_ch: 2,
|
|
237
|
+
enum_type_installation_id: '2',
|
|
238
|
+
enum_methode_calcul_conso_id: '2'
|
|
22
239
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
]
|
|
240
|
+
[
|
|
241
|
+
genCh('1', {
|
|
242
|
+
conso_ch: 1000,
|
|
243
|
+
conso_ch_depensier: 1200,
|
|
244
|
+
conso_auxiliaire_distribution_ch: 100
|
|
245
|
+
})
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
251
|
+
|
|
252
|
+
// conso_ch = 1000 * clé (2)
|
|
253
|
+
expect(res.ef_conso.conso_ch).toBe(2000);
|
|
254
|
+
// distribution = 100 * clé (2) (méthode 2 + clé présente)
|
|
255
|
+
expect(res.ef_conso.conso_auxiliaire_distribution_ch).toBe(200);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('installation avec clé absente (non collective) : répartition = prorata chauffage', () => {
|
|
259
|
+
const ch = [
|
|
260
|
+
installCh(
|
|
261
|
+
{
|
|
262
|
+
cle_repartition_ch: 0,
|
|
263
|
+
enum_type_installation_id: '1',
|
|
264
|
+
enum_methode_calcul_conso_id: '3'
|
|
51
265
|
},
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
266
|
+
[
|
|
267
|
+
genCh('1', {
|
|
268
|
+
conso_ch: 1000,
|
|
269
|
+
conso_ch_depensier: 1200,
|
|
270
|
+
conso_auxiliaire_distribution_ch: 50
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
277
|
+
|
|
278
|
+
// clé 0 => prorataChauffage (1) ; distribution non multipliée (méthode 3)
|
|
279
|
+
expect(res.ef_conso.conso_ch).toBe(1000);
|
|
280
|
+
expect(res.ef_conso.conso_auxiliaire_distribution_ch).toBe(50);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('clé de répartition nulle (prorata 0) : consommation de chauffage annulée', () => {
|
|
284
|
+
// cle===1 => value.cle = prorataChauffage(0) ; type collectif => répartition = 0
|
|
285
|
+
const ch = [
|
|
286
|
+
installCh({ cle_repartition_ch: 1, enum_type_installation_id: '2' }, [
|
|
287
|
+
genCh('1', { conso_ch: 1000, conso_ch_depensier: 1200 })
|
|
288
|
+
])
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 0, DATE_DPE, coef_ep);
|
|
292
|
+
expect(res.ef_conso.conso_ch).toBe(0);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('réseau de chaleur connu : le coefficient GES provient de la table réseau', () => {
|
|
296
|
+
const ch = [
|
|
297
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
298
|
+
genCh(
|
|
299
|
+
'3',
|
|
300
|
+
{ conso_ch: 1000, conso_ch_depensier: 1200 },
|
|
301
|
+
{ identifiant_reseau_chaleur: 'R1', date_arrete_reseau_chaleur: '2030-01-01' }
|
|
302
|
+
)
|
|
303
|
+
])
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
307
|
+
// contenu_co2_acv = 0,15 (tv mocké) => émission = 1000 * 0,15
|
|
308
|
+
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(150, 9);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('réseau de chaleur inconnu (identifiant sans ligne) : erreur signalée', () => {
|
|
312
|
+
const ch = [
|
|
313
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
314
|
+
genCh(
|
|
315
|
+
'3',
|
|
316
|
+
{ conso_ch: 1000, conso_ch_depensier: 1200 },
|
|
317
|
+
{ identifiant_reseau_chaleur: 'R2' }
|
|
318
|
+
)
|
|
319
|
+
])
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
323
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('R2'));
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('réseau de chaleur sans identifiant : coefficient GES forfaitaire 0,385', () => {
|
|
327
|
+
const ch = [
|
|
328
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
329
|
+
genCh('3', { conso_ch: 1000, conso_ch_depensier: 1200 })
|
|
330
|
+
])
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
334
|
+
// 1000 * 0,385
|
|
335
|
+
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(385, 9);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("date de DPE invalide (levée d'exception) : année de repli 2022", () => {
|
|
339
|
+
// date_arrete_reseau_chaleur = Symbol => new Date(...) lève => catch => 2022
|
|
340
|
+
const ch = [
|
|
341
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
342
|
+
genCh(
|
|
343
|
+
'1',
|
|
344
|
+
{ conso_ch: 1000, conso_ch_depensier: 1200 },
|
|
345
|
+
{ date_arrete_reseau_chaleur: Symbol('invalide') }
|
|
346
|
+
)
|
|
347
|
+
])
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
// Ne doit pas lever
|
|
351
|
+
expect(() => calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep)).not.toThrow();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('énergie inconnue : coefficient GES par défaut de 1', () => {
|
|
355
|
+
const ch = [
|
|
356
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
357
|
+
genCh('4', { conso_ch: 1000, conso_ch_depensier: 1200 })
|
|
358
|
+
])
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
362
|
+
// coef_ges['inconnu'] absent => getGesCoeffForGenerateur renvoie 1 => émission = 1000
|
|
363
|
+
expect(res.emission_ges.emission_ges_ch).toBeCloseTo(1000, 9);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('coût du gaz naturel : trois paliers tarifaires couverts', () => {
|
|
367
|
+
// Trois générateurs gaz avec des consommations dans chaque palier tarifaire
|
|
368
|
+
const ch = [
|
|
369
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
370
|
+
genCh('2', { conso_ch: 3000, conso_ch_depensier: 3000 }), // < 5009
|
|
371
|
+
genCh('2', { conso_ch: 30000, conso_ch_depensier: 30000 }), // < 50055
|
|
372
|
+
genCh('2', { conso_ch: 60000, conso_ch_depensier: 60000 }) // else
|
|
373
|
+
])
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
377
|
+
// Somme des trois paliers : 0,11121*3000 + (230+0,06533*30000) + (415+0,06164*60000)
|
|
378
|
+
const attendu = 0.11121 * 3000 + (230 + 0.06533 * 30000) + (415 + 0.06164 * 60000);
|
|
379
|
+
expect(res.cout.cout_ch).toBeCloseTo(attendu, 6);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("coût de l'électricité : cinq paliers tarifaires couverts", () => {
|
|
383
|
+
const ch = [
|
|
384
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
385
|
+
genCh('1', { conso_ch: 500, conso_ch_depensier: 500 }), // < 1000
|
|
386
|
+
genCh('1', { conso_ch: 1500, conso_ch_depensier: 1500 }), // < 2500
|
|
387
|
+
genCh('1', { conso_ch: 3000, conso_ch_depensier: 3000 }), // < 5000
|
|
388
|
+
genCh('1', { conso_ch: 10000, conso_ch_depensier: 10000 }), // < 15000
|
|
389
|
+
genCh('1', { conso_ch: 20000, conso_ch_depensier: 20000 }) // else
|
|
390
|
+
])
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
394
|
+
const attendu =
|
|
395
|
+
0.29007 * 500 +
|
|
396
|
+
(149 + 0.14066 * 1500) +
|
|
397
|
+
(122 + 0.15176 * 3000) +
|
|
398
|
+
(94 + 0.15735 * 10000) +
|
|
399
|
+
(56 + 0.15989 * 20000);
|
|
400
|
+
expect(res.cout.cout_ch).toBeCloseTo(attendu, 6);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('ECS collective (prorataECS=1) : clé de répartition = clé × rdim', () => {
|
|
404
|
+
const ecs = [
|
|
405
|
+
installEcs({ cle_repartition_ecs: 2, rdim: 3 }, [
|
|
406
|
+
genEcs('1', { conso_ecs: 100, conso_ecs_depensier: 120 })
|
|
407
|
+
])
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
const res = calc_conso(100, 1, 1, [], [], ecs, [], 1, 1, DATE_DPE, coef_ep);
|
|
411
|
+
// clé = 2 * 3 = 6 => conso_ecs = 100 * 6
|
|
412
|
+
expect(res.ef_conso.conso_ecs).toBe(600);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('ECS avec prorataECS différent de 1 : clé issue du prorata', () => {
|
|
416
|
+
// Quand prorataECS !== 1, la boucle ECS ne renseigne pas donnee_utilisateur :
|
|
417
|
+
// on la fournit en amont (coefficient GES déjà connu) pour refléter les données réelles.
|
|
418
|
+
const gen = genEcs('1', { conso_ecs: 100, conso_ecs_depensier: 120 });
|
|
419
|
+
gen.donnee_utilisateur = { coeffEmissionGes: 0.065 };
|
|
420
|
+
const ecs = [installEcs({}, [gen])];
|
|
421
|
+
|
|
422
|
+
const res = calc_conso(100, 1, 1, [], [], ecs, [], 0.5, 1, DATE_DPE, coef_ep);
|
|
423
|
+
// clé absente => prorataECS (0,5) => conso_ecs = 100 * 0,5
|
|
424
|
+
expect(res.ef_conso.conso_ecs).toBe(50);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test('ECS sans clé ni rdim, énergie non renseignée : valeurs de repli (1 et électricité)', () => {
|
|
428
|
+
// cle_repartition_ecs et rdim absents => 1 ; enum_type_energie_id absent => électricité (id 1)
|
|
429
|
+
const ecs = [installEcs({}, [genEcs(undefined, { conso_ecs: 100, conso_ecs_depensier: 120 })])];
|
|
430
|
+
|
|
431
|
+
const res = calc_conso(100, 1, 1, [], [], ecs, [], 1, 1, DATE_DPE, coef_ep);
|
|
432
|
+
// clé = 1 * 1 => conso_ecs = 100 ; EP électricité => 100 * 1,9
|
|
433
|
+
expect(res.ef_conso.conso_ecs).toBe(100);
|
|
434
|
+
expect(res.ep_conso.ep_conso_ecs).toBeCloseTo(190, 9);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('ventilation et froid : auxiliaires et froid agrégés par énergie', () => {
|
|
438
|
+
const vt = [
|
|
439
|
+
{
|
|
440
|
+
donnee_entree: { cle_repartition_ventilation: 2 },
|
|
441
|
+
donnee_intermediaire: { conso_auxiliaire_ventilation: 20 }
|
|
442
|
+
},
|
|
443
|
+
// conso absente => 0 ; pas de clé de répartition
|
|
444
|
+
{ donnee_entree: {}, donnee_intermediaire: {} }
|
|
445
|
+
];
|
|
446
|
+
const fr = [
|
|
447
|
+
{
|
|
448
|
+
donnee_entree: { enum_type_energie_id: '1' },
|
|
449
|
+
donnee_intermediaire: { conso_fr: 200, conso_fr_depensier: 250 }
|
|
81
450
|
}
|
|
82
451
|
];
|
|
83
|
-
const ecs = [];
|
|
84
|
-
const fr = [];
|
|
85
452
|
|
|
86
|
-
const
|
|
453
|
+
const res = calc_conso(100, 1, 1, vt, [], [], fr, 1, 1, DATE_DPE, coef_ep);
|
|
454
|
+
|
|
455
|
+
// ventilation : 20 * 2 (clé) + 0 = 40
|
|
456
|
+
expect(res.ef_conso.conso_auxiliaire_ventilation).toBe(40);
|
|
457
|
+
expect(res.ef_conso.conso_fr).toBe(200);
|
|
458
|
+
// sortie par énergie pour l'électricité
|
|
459
|
+
const elec = res.sortie_par_energie_collection.sortie_par_energie.find(
|
|
460
|
+
(e) => e.enum_type_energie_id === '1'
|
|
461
|
+
);
|
|
462
|
+
expect(elec.conso_ch).toBeDefined();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test('sortie par énergie : électricité ajoutée même en son absence', () => {
|
|
466
|
+
// Seul le gaz est présent => l'électricité (id 1) est tout de même ajoutée
|
|
467
|
+
const ch = [
|
|
468
|
+
installCh({ cle_repartition_ch: 1 }, [
|
|
469
|
+
genCh('2', { conso_ch: 1000, conso_ch_depensier: 1200 })
|
|
470
|
+
])
|
|
471
|
+
];
|
|
87
472
|
|
|
88
|
-
|
|
89
|
-
|
|
473
|
+
const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
|
|
474
|
+
const ids = res.sortie_par_energie_collection.sortie_par_energie.map(
|
|
475
|
+
(e) => e.enum_type_energie_id
|
|
476
|
+
);
|
|
477
|
+
expect(ids).toContain('2');
|
|
478
|
+
expect(ids).toContain('1');
|
|
90
479
|
});
|
|
91
480
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { AddAdditionnalUeValuesTables } from './add-additionnal-ue-values-tables.js';
|
|
3
|
+
import { UPB_ADDITIONAL_VALUES } from '../../tv/infrastructure/assets/additional-ue-values.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `execute` n'utilise ni le fileStore ni l'appConfig : on les passe en doubles
|
|
7
|
+
* vides pour isoler la logique d'aplatissement des valeurs Ue additionnelles
|
|
8
|
+
* dans la table `ue`.
|
|
9
|
+
*/
|
|
10
|
+
describe('AddAdditionnalUeValuesTables', () => {
|
|
11
|
+
const buildService = () => new AddAdditionnalUeValuesTables({}, {});
|
|
12
|
+
|
|
13
|
+
test('ajoute toutes les valeurs additionnelles dans la table ue', () => {
|
|
14
|
+
const nbAttendu = UPB_ADDITIONAL_VALUES.reduce((acc, entree) => acc + entree.values.length, 0);
|
|
15
|
+
const tableValues = { ue: [] };
|
|
16
|
+
|
|
17
|
+
const resultat = buildService().execute(tableValues);
|
|
18
|
+
|
|
19
|
+
expect(resultat).toBe(tableValues);
|
|
20
|
+
expect(tableValues.ue).toHaveLength(nbAttendu);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('préserve les valeurs déjà présentes dans la table ue', () => {
|
|
24
|
+
const ligneExistante = { existant: true };
|
|
25
|
+
const tableValues = { ue: [ligneExistante] };
|
|
26
|
+
const nbAjoutees = UPB_ADDITIONAL_VALUES.reduce((acc, entree) => acc + entree.values.length, 0);
|
|
27
|
+
|
|
28
|
+
buildService().execute(tableValues);
|
|
29
|
+
|
|
30
|
+
expect(tableValues.ue[0]).toBe(ligneExistante);
|
|
31
|
+
expect(tableValues.ue).toHaveLength(nbAjoutees + 1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('chaque ligne ajoutée fusionne les métadonnées d’adjacence et les valeurs', () => {
|
|
35
|
+
const tableValues = { ue: [] };
|
|
36
|
+
|
|
37
|
+
buildService().execute(tableValues);
|
|
38
|
+
|
|
39
|
+
const premiereEntree = UPB_ADDITIONAL_VALUES[0];
|
|
40
|
+
const premiereValeur = premiereEntree.values[0];
|
|
41
|
+
|
|
42
|
+
expect(tableValues.ue[0]).toEqual({
|
|
43
|
+
type_adjacence_plancher: premiereEntree.type_adjacence_plancher,
|
|
44
|
+
enum_type_adjacence_id: premiereEntree.enum_type_adjacence_id,
|
|
45
|
+
type_adjacence: premiereEntree.type_adjacence,
|
|
46
|
+
enum_periode_construction_id: premiereEntree.enum_periode_construction_id,
|
|
47
|
+
periode_construction: premiereEntree.periode_construction,
|
|
48
|
+
'2s_p': premiereValeur['2s_p'],
|
|
49
|
+
upb: premiereValeur.upb,
|
|
50
|
+
ue: premiereValeur.ue
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -44,4 +44,29 @@ describe('SynchronizeSolicitationsTables unit tests', () => {
|
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test("regroupe sans clé quand la ligne n'a ni ilpa ni classe_altitude", () => {
|
|
49
|
+
const fileStore = new FileStore();
|
|
50
|
+
const appConfig = new ApplicationConfig();
|
|
51
|
+
const synchronizeSolicitationsTables = new SynchronizeSolicitationsTables(fileStore, appConfig);
|
|
52
|
+
|
|
53
|
+
// Feuille ne comportant que `mois` et une valeur : `groupKey` doit rester
|
|
54
|
+
// vide (branche `: ''`) et le chemin construit est `feuille.mois.clef`.
|
|
55
|
+
vi.spyOn(fileStore, 'readLocalOdsFileAndConvertToJson').mockResolvedValue({
|
|
56
|
+
text_ext: [{ mois: 'Janvier', h1a: '12.5' }]
|
|
57
|
+
});
|
|
58
|
+
vi.spyOn(ApplicationConfig.prototype, 'solicitationsExtFilePath', 'get').mockReturnValue(
|
|
59
|
+
'src/file.ods'
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return synchronizeSolicitationsTables.execute().then((output) => {
|
|
63
|
+
expect(output).toEqual({
|
|
64
|
+
text_ext: {
|
|
65
|
+
Janvier: {
|
|
66
|
+
h1a: 12.5
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
47
72
|
});
|