@open3cl/engine 1.8.7 → 1.8.9
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 +116 -31
- package/13.2_generateur_combustion.spec.js +286 -2
- package/13.2_generateur_combustion_pn.js +248 -0
- package/13.2_generateur_combustion_pn.spec.js +476 -0
- package/6.2_surface_sud_equivalente.js +77 -6
- package/6.2_surface_sud_equivalente.spec.js +138 -2
- package/README.md +22 -22
- package/conso.js +178 -4
- package/conso.spec.js +187 -1
- package/engine.js +7 -4
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.js +136 -70
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +280 -0
- package/features/engine/domain/engine.service.js +4 -1
- package/features/engine/domain/engine.service.spec.js +14 -0
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.js +8 -7
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +230 -0
- package/package.json +1 -1
- package/utils.js +5 -0
- package/utils.spec.js +53 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
conventionPuissanceNominale,
|
|
4
|
+
decalageDecimal,
|
|
5
|
+
inverserRendement,
|
|
6
|
+
nombreDecimales,
|
|
7
|
+
puissanceDepuisRpn,
|
|
8
|
+
puissanceRecalculeeDepuisRpn,
|
|
9
|
+
qp0Coherent,
|
|
10
|
+
valeurCoherente
|
|
11
|
+
} from './13.2_generateur_combustion_pn.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Module pur (aucune dépendance importée) : les caractéristiques du générateur sont fournies par
|
|
15
|
+
* une fonction injectée, remplacée ici par un double de test déterministe.
|
|
16
|
+
*
|
|
17
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2 et §17.2.1 (virtualisation)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Double de test des caractéristiques d'une chaudière : rpn = (84 + 2 log10(Pn collectif kW)) / 100
|
|
22
|
+
* et qp0 = 1 % de Pn collectif, ramené au logement (× ratio).
|
|
23
|
+
*/
|
|
24
|
+
const caracteristiquesChaudiere = vi.fn((pn, ratio) => {
|
|
25
|
+
const pnCollectifKw = pn / ratio / 1000;
|
|
26
|
+
return {
|
|
27
|
+
rpn: (84 + 2 * Math.log10(pnCollectifKw)) / 100,
|
|
28
|
+
qp0: 0.01 * pnCollectifKw * 1000 * ratio
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('nombreDecimales', () => {
|
|
33
|
+
test.each([
|
|
34
|
+
[12, 0],
|
|
35
|
+
[0.87, 2],
|
|
36
|
+
[0.868943, 6],
|
|
37
|
+
[1e-7, 7],
|
|
38
|
+
[1.5e-7, 8],
|
|
39
|
+
[1.5e3, 0]
|
|
40
|
+
])('%s possède %s décimale(s)', (valeur, attendu) => {
|
|
41
|
+
expect(nombreDecimales(valeur)).toBe(attendu);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('valeurCoherente', () => {
|
|
46
|
+
test('valeur arrondie à 2 décimales : tolérance d’une demi-unité (0,005)', () => {
|
|
47
|
+
expect(valeurCoherente(0.87, 0.8749)).toBe(true);
|
|
48
|
+
expect(valeurCoherente(0.87, 0.8651)).toBe(true);
|
|
49
|
+
expect(valeurCoherente(0.87, 0.876)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('valeur arrondie à 1 décimale : la tolérance reste bornée à 0,005', () => {
|
|
53
|
+
expect(valeurCoherente(0.9, 0.904)).toBe(true);
|
|
54
|
+
expect(valeurCoherente(0.9, 0.908)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('valeur non arrondie : tolérance relative de 0,1 % (troncature de la puissance)', () => {
|
|
58
|
+
expect(valeurCoherente(0.868943160626844, 0.8696)).toBe(true);
|
|
59
|
+
expect(valeurCoherente(0.868943160626844, 0.8705)).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('grande valeur entière : tolérance relative de 0,1 %', () => {
|
|
63
|
+
expect(valeurCoherente(2400, 2402)).toBe(true);
|
|
64
|
+
expect(valeurCoherente(2400, 2403)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('qp0Coherent', () => {
|
|
69
|
+
test('qp0 stocké en W', () => {
|
|
70
|
+
expect(qp0Coherent(240, 240)).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('qp0 stocké en kW (certains logiciels)', () => {
|
|
74
|
+
expect(qp0Coherent(0.24, 240)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('qp0 incohérent dans les deux unités', () => {
|
|
78
|
+
expect(qp0Coherent(0.5, 240)).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('conventionPuissanceNominale - cas sans détection', () => {
|
|
83
|
+
test.each([
|
|
84
|
+
['installation individuelle (ratio = 1)', 23000, 1, { rpn: 0.87 }],
|
|
85
|
+
['ratio absent', 23000, undefined, { rpn: 0.87 }],
|
|
86
|
+
['ratio nul', 23000, 0, { rpn: 0.87 }],
|
|
87
|
+
['puissance absente', 0, 0.1, { rpn: 0.87 }],
|
|
88
|
+
['rpn du DPE absent', 23000, 0.1, {}],
|
|
89
|
+
['rpn du DPE non numérique', 23000, 0.1, { rpn: '0.87' }],
|
|
90
|
+
['valeurs du DPE absentes', 23000, 0.1, undefined]
|
|
91
|
+
])('%s : convention individualisée conservée', (_, pn, ratio, valeursDpe) => {
|
|
92
|
+
const caracteristiques = vi.fn();
|
|
93
|
+
expect(conventionPuissanceNominale(pn, ratio, valeursDpe, caracteristiques, true)).toEqual({
|
|
94
|
+
pn,
|
|
95
|
+
ratio,
|
|
96
|
+
convention: 'individualisee'
|
|
97
|
+
});
|
|
98
|
+
expect(caracteristiques).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('conventionPuissanceNominale - détection de la convention du logiciel', () => {
|
|
103
|
+
const ratio = 0.1;
|
|
104
|
+
const pn = 23000;
|
|
105
|
+
|
|
106
|
+
test('rpn cohérent avec pn / ratio : pn est bien la puissance virtualisée (convention 3CL)', () => {
|
|
107
|
+
// rpn sur Pn collectif = 230 kW
|
|
108
|
+
const rpn = caracteristiquesChaudiere(pn, ratio).rpn;
|
|
109
|
+
expect(
|
|
110
|
+
conventionPuissanceNominale(pn, ratio, { rpn }, caracteristiquesChaudiere, false)
|
|
111
|
+
).toEqual({ pn, ratio, convention: 'individualisee' });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('rpn non recalculé (caractéristique saisie) : convention individualisée conservée', () => {
|
|
115
|
+
const caracteristiques = vi.fn(() => ({ qp0: 100 }));
|
|
116
|
+
expect(conventionPuissanceNominale(pn, ratio, { rpn: 0.87 }, caracteristiques, false)).toEqual({
|
|
117
|
+
pn,
|
|
118
|
+
ratio,
|
|
119
|
+
convention: 'individualisee'
|
|
120
|
+
});
|
|
121
|
+
expect(caracteristiques).toHaveBeenCalledTimes(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('rpn incohérent avec les deux hypothèses : convention individualisée conservée', () => {
|
|
125
|
+
expect(
|
|
126
|
+
conventionPuissanceNominale(pn, ratio, { rpn: 0.5 }, caracteristiquesChaudiere, true)
|
|
127
|
+
).toEqual({ pn, ratio, convention: 'individualisee' });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('rpn et qp0 calculés par le logiciel sur pn collectif, qp0 virtualisé : pn ramené à Pe = pn × ratio', () => {
|
|
131
|
+
// rpn sur 23 kW, qp0 = 1 % × 23 kW × 0,1 = 23 W
|
|
132
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn, qp0: 23 };
|
|
133
|
+
const resultat = conventionPuissanceNominale(
|
|
134
|
+
pn,
|
|
135
|
+
ratio,
|
|
136
|
+
valeursDpe,
|
|
137
|
+
caracteristiquesChaudiere,
|
|
138
|
+
true
|
|
139
|
+
);
|
|
140
|
+
expect(resultat.convention).toBe('collective_virtualisee');
|
|
141
|
+
expect(resultat.pn).toBeCloseTo(2300, 9);
|
|
142
|
+
expect(resultat.ratio).toBe(ratio);
|
|
143
|
+
// Hypothèse virtualisée évaluée sur Pe = pn × ratio
|
|
144
|
+
expect(caracteristiquesChaudiere).toHaveBeenCalledWith(pn * ratio, ratio);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('qp0 du DPE absent : rpn suffit à établir que pn est la puissance collective', () => {
|
|
148
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn };
|
|
149
|
+
expect(
|
|
150
|
+
conventionPuissanceNominale(pn, ratio, valeursDpe, caracteristiquesChaudiere, true)
|
|
151
|
+
).toMatchObject({ convention: 'collective_virtualisee', ratio });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('qp0 non recalculé (méthode de saisie 4/5) : rpn suffit', () => {
|
|
155
|
+
const rpnCollectif = caracteristiquesChaudiere(pn, 1).rpn;
|
|
156
|
+
const caracteristiques = (p, r) => ({ rpn: caracteristiquesChaudiere(p, r).rpn });
|
|
157
|
+
expect(
|
|
158
|
+
conventionPuissanceNominale(
|
|
159
|
+
pn,
|
|
160
|
+
ratio,
|
|
161
|
+
{ rpn: rpnCollectif, qp0: 999 },
|
|
162
|
+
caracteristiques,
|
|
163
|
+
true
|
|
164
|
+
)
|
|
165
|
+
).toMatchObject({ convention: 'collective_virtualisee' });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('logiciel n’ayant pas virtualisé qp0 (qp0 en kW sur pn) + bug_for_bug_compat : calcul non virtualisé reproduit', () => {
|
|
169
|
+
// qp0 = 1 % × 23 kW = 0,23 kW, non multiplié par le ratio
|
|
170
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn, qp0: 0.23 };
|
|
171
|
+
expect(
|
|
172
|
+
conventionPuissanceNominale(pn, ratio, valeursDpe, caracteristiquesChaudiere, true)
|
|
173
|
+
).toEqual({ pn, ratio: 1, convention: 'collective_non_virtualisee' });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('logiciel n’ayant pas virtualisé qp0 sans bug_for_bug_compat : application de la méthode (pn × ratio)', () => {
|
|
177
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn, qp0: 230 };
|
|
178
|
+
const resultat = conventionPuissanceNominale(
|
|
179
|
+
pn,
|
|
180
|
+
ratio,
|
|
181
|
+
valeursDpe,
|
|
182
|
+
caracteristiquesChaudiere,
|
|
183
|
+
false
|
|
184
|
+
);
|
|
185
|
+
expect(resultat.convention).toBe('collective_virtualisee');
|
|
186
|
+
expect(resultat.pn).toBeCloseTo(2300, 9);
|
|
187
|
+
expect(resultat.ratio).toBe(ratio);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('qp0 incohérent avec les deux hypothèses : convention individualisée conservée', () => {
|
|
191
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn, qp0: 5000 };
|
|
192
|
+
expect(
|
|
193
|
+
conventionPuissanceNominale(pn, ratio, valeursDpe, caracteristiquesChaudiere, true)
|
|
194
|
+
).toEqual({ pn, ratio, convention: 'individualisee' });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('qp0 non recalculable dans l’hypothèse non virtualisée : convention individualisée conservée', () => {
|
|
198
|
+
const caracteristiques = (p, r) =>
|
|
199
|
+
r === 1 ? { rpn: 0.8 } : { ...caracteristiquesChaudiere(p, r) };
|
|
200
|
+
const valeursDpe = { rpn: caracteristiquesChaudiere(pn, 1).rpn, qp0: 5000 };
|
|
201
|
+
expect(conventionPuissanceNominale(pn, ratio, valeursDpe, caracteristiques, true)).toEqual({
|
|
202
|
+
pn,
|
|
203
|
+
ratio,
|
|
204
|
+
convention: 'individualisee'
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('cas réel DPEWin 2415E2886950G (rpn = 0,87 ; qp0 = 0,276 kW) avec une table réaliste', () => {
|
|
209
|
+
// Chaudière gaz standard : rpn = 84 + 2 log(Pn), qp0 = 1,2 % de Pn
|
|
210
|
+
const caracteristiques = (p, r) => ({
|
|
211
|
+
rpn: (84 + 2 * Math.log10(p / r / 1000)) / 100,
|
|
212
|
+
qp0: 0.012 * p
|
|
213
|
+
});
|
|
214
|
+
const resultat = conventionPuissanceNominale(
|
|
215
|
+
23000,
|
|
216
|
+
0.1150571,
|
|
217
|
+
{ rpn: 0.87, qp0: 0.276 },
|
|
218
|
+
caracteristiques,
|
|
219
|
+
true
|
|
220
|
+
);
|
|
221
|
+
expect(resultat).toEqual({ pn: 23000, ratio: 1, convention: 'collective_non_virtualisee' });
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 4e hypothèse : pn recalculé à partir du rpn stocké dans le DPE (issue #124, DPE 2592E2935275X).
|
|
227
|
+
* Double de test d'une chaudière gaz à condensation 2001-2015 : rpn = 91 + log10(Pn),
|
|
228
|
+
* rpint = 97 + log10(Pn) (Pn collectif en kW), qp0 = 1 % de la puissance saisie.
|
|
229
|
+
*/
|
|
230
|
+
const condensation = {
|
|
231
|
+
rpn: (pnCollectif) => (91 + Math.log10(pnCollectif / 1000)) / 100,
|
|
232
|
+
caracteristiques: (pn, ratio) => ({
|
|
233
|
+
rpn: (91 + Math.log10(pn / ratio / 1000)) / 100,
|
|
234
|
+
rpint: (97 + Math.log10(pn / ratio / 1000)) / 100,
|
|
235
|
+
qp0: 0.01 * pn
|
|
236
|
+
})
|
|
237
|
+
};
|
|
238
|
+
const formuleRpnCondensation = () => condensation.rpn;
|
|
239
|
+
|
|
240
|
+
describe('inverserRendement', () => {
|
|
241
|
+
test('formule « A + B × log10(Pn) » : Pn retrouvé (W)', () => {
|
|
242
|
+
// rpn = 91 + log10(696) % -> Pn = 696 kW
|
|
243
|
+
expect(inverserRendement((91 + Math.log10(696)) / 100, condensation.rpn)).toBeCloseTo(
|
|
244
|
+
696000,
|
|
245
|
+
6
|
|
246
|
+
);
|
|
247
|
+
const formule = (p) => (84 + 2 * Math.log10(p / 1000)) / 100;
|
|
248
|
+
expect(inverserRendement(formule(23000), formule)).toBeCloseTo(23000, 6);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('rendement indépendant de Pn (B = 0) : non inversible', () => {
|
|
252
|
+
expect(inverserRendement(0.9, () => 0.9)).toBeUndefined();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('formule non affine en log10(Pn) : non inversible', () => {
|
|
256
|
+
expect(inverserRendement(0.9, (p) => (80 + Math.log10(p / 1000) ** 2) / 100)).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('formule non évaluable : non inversible', () => {
|
|
260
|
+
expect(inverserRendement(0.9, () => NaN)).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('puissanceDepuisRpn', () => {
|
|
265
|
+
/**
|
|
266
|
+
* Table à deux lignes selon le critère de puissance : Pn ≤ 70 kW -> 84 + 2 log10(Pn),
|
|
267
|
+
* Pn > 70 kW -> 91 + log10(Pn).
|
|
268
|
+
*/
|
|
269
|
+
const petite = (p) => (84 + 2 * Math.log10(p / 1000)) / 100;
|
|
270
|
+
const formuleSelonPn = (pnCollectif) => (pnCollectif <= 70000 ? petite : condensation.rpn);
|
|
271
|
+
|
|
272
|
+
test('ligne de table stable : Pn obtenu en une inversion', () => {
|
|
273
|
+
const formuleRpn = vi.fn(formuleSelonPn);
|
|
274
|
+
expect(puissanceDepuisRpn(500000, condensation.rpn(696000), formuleRpn)).toBeCloseTo(696000, 6);
|
|
275
|
+
expect(formuleRpn).toHaveBeenCalledTimes(2);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('première ligne inadaptée : ligne re-sélectionnée sur le Pn obtenu', () => {
|
|
279
|
+
// Départ sur la ligne « Pn ≤ 70 kW », Pn réel 696 kW
|
|
280
|
+
const formuleRpn = vi.fn(formuleSelonPn);
|
|
281
|
+
expect(puissanceDepuisRpn(24000, condensation.rpn(696000), formuleRpn)).toBeCloseTo(696000, 6);
|
|
282
|
+
expect(formuleRpn).toHaveBeenCalledTimes(4);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('aucune ligne de table : abandon', () => {
|
|
286
|
+
expect(puissanceDepuisRpn(24000, 0.93, () => undefined)).toBeUndefined();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('ligne sans formule rpn après re-sélection : abandon après 3 itérations', () => {
|
|
290
|
+
const formuleRpn = vi.fn((p) => (p === 24000 ? condensation.rpn : undefined));
|
|
291
|
+
expect(puissanceDepuisRpn(24000, 0.93, formuleRpn)).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('rpn indépendant de Pn : abandon', () => {
|
|
295
|
+
expect(puissanceDepuisRpn(24000, 0.9, () => () => 0.9)).toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('lignes incompatibles (Pn hors de la plage de chaque ligne) : abandon', () => {
|
|
299
|
+
// 0,93 -> 100 kW sur la ligne « Pn ≤ 70 kW » et 14 kW sur la ligne « Pn > 70 kW »
|
|
300
|
+
const grande = (p) => (70 + 20 * Math.log10(p / 1000)) / 100;
|
|
301
|
+
const formuleRpn = (p) => (p <= 70000 ? condensation.rpn : grande);
|
|
302
|
+
expect(puissanceDepuisRpn(24000, 0.93, formuleRpn)).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('decalageDecimal', () => {
|
|
307
|
+
test('puissance saisie décalée d’une puissance de dix (69600 W pour 696 kW)', () => {
|
|
308
|
+
expect(decalageDecimal(69600, 696000)).toBe(true);
|
|
309
|
+
expect(decalageDecimal(696, 696000)).toBe(true);
|
|
310
|
+
expect(decalageDecimal(2018.32, 201.832)).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('puissance identique ou sans rapport : pas de décalage', () => {
|
|
314
|
+
expect(decalageDecimal(696000, 696000)).toBe(false);
|
|
315
|
+
expect(decalageDecimal(23000, 696000)).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('puissanceRecalculeeDepuisRpn', () => {
|
|
320
|
+
// Valeurs du DPE 2592E2935275X (WinDpe) : pn = 69600 W, Pn réel = 696 kW
|
|
321
|
+
const ratio = 0.00289987937273824;
|
|
322
|
+
const rpn = 0.938426100909721;
|
|
323
|
+
const rpint = 0.998426100909721;
|
|
324
|
+
|
|
325
|
+
test('rpint du DPE cohérent avec le Pn retrouvé : Pe = a × Pn retenu', () => {
|
|
326
|
+
const resultat = puissanceRecalculeeDepuisRpn(
|
|
327
|
+
69600,
|
|
328
|
+
ratio,
|
|
329
|
+
{ rpn, rpint, qp0: 2.01832 },
|
|
330
|
+
condensation.caracteristiques,
|
|
331
|
+
formuleRpnCondensation
|
|
332
|
+
);
|
|
333
|
+
expect(resultat.convention).toBe('puissance_depuis_rpn');
|
|
334
|
+
expect(resultat.ratio).toBe(ratio);
|
|
335
|
+
/**
|
|
336
|
+
* Référence de régression : le logiciel a calculé rpn sur Pn = Pe / a avec Pe = 2018,32 W
|
|
337
|
+
* (arrondi au centième), soit Pn = 696,0014 kW ; on retrouve exactement Pe = 2018,32 W.
|
|
338
|
+
*/
|
|
339
|
+
expect(resultat.pnCollectif).toBeCloseTo(2018.32 / ratio, 3);
|
|
340
|
+
expect(resultat.pn).toBeCloseTo(2018.32, 6);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('rpint du DPE incohérent avec le Pn retrouvé : abandon', () => {
|
|
344
|
+
expect(
|
|
345
|
+
puissanceRecalculeeDepuisRpn(
|
|
346
|
+
69600,
|
|
347
|
+
ratio,
|
|
348
|
+
{ rpn, rpint: 0.95, qp0: 20.1832 },
|
|
349
|
+
condensation.caracteristiques,
|
|
350
|
+
formuleRpnCondensation
|
|
351
|
+
)
|
|
352
|
+
).toBeUndefined();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('sans rpint : qp0 virtualisé du DPE (W) confirme le Pn retrouvé', () => {
|
|
356
|
+
expect(
|
|
357
|
+
puissanceRecalculeeDepuisRpn(
|
|
358
|
+
12345,
|
|
359
|
+
ratio,
|
|
360
|
+
{ rpn, qp0: 20.1832 },
|
|
361
|
+
condensation.caracteristiques,
|
|
362
|
+
formuleRpnCondensation
|
|
363
|
+
)
|
|
364
|
+
).toMatchObject({ convention: 'puissance_depuis_rpn' });
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('sans rpint : qp0 non virtualisé du DPE (kW) confirme le Pn retrouvé', () => {
|
|
368
|
+
// qp0 = 1 % × 696 kW = 6,96 kW
|
|
369
|
+
expect(
|
|
370
|
+
puissanceRecalculeeDepuisRpn(
|
|
371
|
+
12345,
|
|
372
|
+
ratio,
|
|
373
|
+
{ rpn, qp0: 6.96 },
|
|
374
|
+
condensation.caracteristiques,
|
|
375
|
+
formuleRpnCondensation
|
|
376
|
+
)
|
|
377
|
+
).toMatchObject({ convention: 'puissance_depuis_rpn' });
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('sans rpint ni qp0 cohérent : pn saisi décalé d’une puissance de dix de Pn', () => {
|
|
381
|
+
expect(
|
|
382
|
+
puissanceRecalculeeDepuisRpn(
|
|
383
|
+
69600,
|
|
384
|
+
ratio,
|
|
385
|
+
{ rpn, qp0: 2.01832 },
|
|
386
|
+
condensation.caracteristiques,
|
|
387
|
+
formuleRpnCondensation
|
|
388
|
+
)
|
|
389
|
+
).toMatchObject({ convention: 'puissance_depuis_rpn' });
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('sans rpint ni qp0 : pn saisi décalé d’une puissance de dix de Pe', () => {
|
|
393
|
+
expect(
|
|
394
|
+
puissanceRecalculeeDepuisRpn(
|
|
395
|
+
201.832,
|
|
396
|
+
ratio,
|
|
397
|
+
{ rpn },
|
|
398
|
+
condensation.caracteristiques,
|
|
399
|
+
formuleRpnCondensation
|
|
400
|
+
)
|
|
401
|
+
).toMatchObject({ convention: 'puissance_depuis_rpn' });
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('aucune confirmation (rpint et qp0 non recalculés, pn sans rapport) : abandon', () => {
|
|
405
|
+
const caracteristiques = (pn, r) => ({ rpn: condensation.caracteristiques(pn, r).rpn });
|
|
406
|
+
expect(
|
|
407
|
+
puissanceRecalculeeDepuisRpn(
|
|
408
|
+
12345,
|
|
409
|
+
ratio,
|
|
410
|
+
{ rpn, rpint, qp0: 2.01832 },
|
|
411
|
+
caracteristiques,
|
|
412
|
+
formuleRpnCondensation
|
|
413
|
+
)
|
|
414
|
+
).toBeUndefined();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('rpn non inversible : abandon sans calcul des caractéristiques', () => {
|
|
418
|
+
const caracteristiques = vi.fn();
|
|
419
|
+
expect(
|
|
420
|
+
puissanceRecalculeeDepuisRpn(69600, ratio, { rpn }, caracteristiques, () => () => 0.9)
|
|
421
|
+
).toBeUndefined();
|
|
422
|
+
expect(caracteristiques).not.toHaveBeenCalled();
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('conventionPuissanceNominale - 4e hypothèse (pn recalculé depuis rpn)', () => {
|
|
427
|
+
const ratio = 0.00289987937273824;
|
|
428
|
+
const valeursDpe = { rpn: 0.938426100909721, rpint: 0.998426100909721, qp0: 2.01832 };
|
|
429
|
+
|
|
430
|
+
test('cas réel WinDpe 2592E2935275X : pn ni Pe ni Pn -> Pe = a × Pn(rpn) = 2018,32 W', () => {
|
|
431
|
+
const resultat = conventionPuissanceNominale(
|
|
432
|
+
69600,
|
|
433
|
+
ratio,
|
|
434
|
+
valeursDpe,
|
|
435
|
+
condensation.caracteristiques,
|
|
436
|
+
true,
|
|
437
|
+
formuleRpnCondensation
|
|
438
|
+
);
|
|
439
|
+
expect(resultat).toMatchObject({ convention: 'puissance_depuis_rpn', ratio });
|
|
440
|
+
expect(resultat.pn).toBeCloseTo(2018.32, 6);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('formule rpn non fournie : convention individualisée conservée', () => {
|
|
444
|
+
expect(
|
|
445
|
+
conventionPuissanceNominale(69600, ratio, valeursDpe, condensation.caracteristiques, true)
|
|
446
|
+
).toEqual({ pn: 69600, ratio, convention: 'individualisee' });
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('confirmation impossible : convention individualisée conservée', () => {
|
|
450
|
+
expect(
|
|
451
|
+
conventionPuissanceNominale(
|
|
452
|
+
69600,
|
|
453
|
+
ratio,
|
|
454
|
+
{ ...valeursDpe, rpint: 0.95 },
|
|
455
|
+
condensation.caracteristiques,
|
|
456
|
+
true,
|
|
457
|
+
formuleRpnCondensation
|
|
458
|
+
)
|
|
459
|
+
).toEqual({ pn: 69600, ratio, convention: 'individualisee' });
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('pn cohérent avec Pe : la 4e hypothèse n’est pas évaluée', () => {
|
|
463
|
+
const formuleRpn = vi.fn(formuleRpnCondensation);
|
|
464
|
+
expect(
|
|
465
|
+
conventionPuissanceNominale(
|
|
466
|
+
2018.32,
|
|
467
|
+
ratio,
|
|
468
|
+
valeursDpe,
|
|
469
|
+
condensation.caracteristiques,
|
|
470
|
+
true,
|
|
471
|
+
formuleRpn
|
|
472
|
+
)
|
|
473
|
+
).toMatchObject({ convention: 'individualisee' });
|
|
474
|
+
expect(formuleRpn).not.toHaveBeenCalled();
|
|
475
|
+
});
|
|
476
|
+
});
|
|
@@ -14,11 +14,6 @@ export function calc_sse_j(bv_list, ets, ca, zc, mois) {
|
|
|
14
14
|
return sseBaiesExt;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Certaines vérandas sont dupliqués dans les DPE.
|
|
18
|
-
if (Array.isArray(ets)) {
|
|
19
|
-
ets = ets[0];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
/**
|
|
23
18
|
* 6.3 Traitement des espaces tampons solarisés
|
|
24
19
|
* Sse_ver n'est calculée que pour les baies vitrées qui séparent le logement de l'espace tampon
|
|
@@ -28,6 +23,82 @@ export function calc_sse_j(bv_list, ets, ca, zc, mois) {
|
|
|
28
23
|
return sseBaiesExt;
|
|
29
24
|
}
|
|
30
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Plusieurs espaces tampons solarisés (issue #101) : T, bver, Sst et Ssd sont propres à chaque
|
|
28
|
+
* véranda. Sse = Sse_ext + Σ(v) Sse_veranda_v, chaque baie adjacence 10 n'étant comptée que dans
|
|
29
|
+
* la véranda sur laquelle elle donne (baie.reference_lnc = ets.reference).
|
|
30
|
+
*/
|
|
31
|
+
const etsList = dedoublonnerEts(Array.isArray(ets) ? ets : [ets]);
|
|
32
|
+
const baiesParEts = rattacherBaiesAuxEts(baiesAdjVeranda, etsList);
|
|
33
|
+
|
|
34
|
+
return etsList.reduce(
|
|
35
|
+
(acc, etsItem, idx) => acc + calc_sse_veranda_j(etsItem, baiesParEts[idx], zc, mois),
|
|
36
|
+
sseBaiesExt
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Certaines vérandas sont dupliquées dans les DPE : une même véranda (même référence, ou à défaut
|
|
42
|
+
* même contenu) ne doit être comptée qu'une seule fois.
|
|
43
|
+
*
|
|
44
|
+
* @param etsList {EtsItem[]}
|
|
45
|
+
* @returns {EtsItem[]}
|
|
46
|
+
*/
|
|
47
|
+
export function dedoublonnerEts(etsList) {
|
|
48
|
+
const vues = new Set();
|
|
49
|
+
return etsList.filter((etsItem) => {
|
|
50
|
+
if (!etsItem) return false;
|
|
51
|
+
const reference = etsItem.donnee_entree?.reference;
|
|
52
|
+
const cle = reference ? `ref:${reference}` : `json:${JSON.stringify(etsItem)}`;
|
|
53
|
+
if (vues.has(cle)) return false;
|
|
54
|
+
vues.add(cle);
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Rattache chaque baie vitrée adjacence 10 à l'espace tampon sur lequel elle donne
|
|
61
|
+
* (baie.donnee_entree.reference_lnc = ets.donnee_entree.reference).
|
|
62
|
+
*
|
|
63
|
+
* Une baie sans lien exploitable (reference_lnc absente ou ne correspondant à aucune véranda) est
|
|
64
|
+
* rattachée à la première véranda : c'est le comportement historique, et le seul possible quand le
|
|
65
|
+
* DPE ne comporte qu'une véranda. La méthode ne permet pas de faire mieux pour une donnée manquante.
|
|
66
|
+
*
|
|
67
|
+
* @param baiesAdjVeranda {BaieVitreeItem[]}
|
|
68
|
+
* @param etsList {EtsItem[]}
|
|
69
|
+
* @returns {BaieVitreeItem[][]} baies par véranda, dans l'ordre de etsList
|
|
70
|
+
*/
|
|
71
|
+
export function rattacherBaiesAuxEts(baiesAdjVeranda, etsList) {
|
|
72
|
+
const baiesParEts = etsList.map(() => []);
|
|
73
|
+
// Aucune véranda exploitable : les baies adjacence 10 ne peuvent être rattachées à aucun ETS
|
|
74
|
+
if (etsList.length === 0) {
|
|
75
|
+
return baiesParEts;
|
|
76
|
+
}
|
|
77
|
+
baiesAdjVeranda.forEach((bv) => {
|
|
78
|
+
const referenceLnc = bv.donnee_entree.reference_lnc;
|
|
79
|
+
const idx = referenceLnc
|
|
80
|
+
? etsList.findIndex((etsItem) => etsItem.donnee_entree?.reference === referenceLnc)
|
|
81
|
+
: -1;
|
|
82
|
+
baiesParEts[idx === -1 ? 0 : idx].push(bv);
|
|
83
|
+
});
|
|
84
|
+
return baiesParEts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Surface sud équivalente apportée par une véranda sur le mois j (§6.3).
|
|
89
|
+
*
|
|
90
|
+
* @param ets {EtsItem}
|
|
91
|
+
* @param baiesAdjVeranda {BaieVitreeItem[]} baies séparant le logement de cette véranda
|
|
92
|
+
* @param zc {string}
|
|
93
|
+
* @param mois {string}
|
|
94
|
+
* @returns {number}
|
|
95
|
+
*/
|
|
96
|
+
function calc_sse_veranda_j(ets, baiesAdjVeranda, zc, mois) {
|
|
97
|
+
// Véranda sans baie vitrée vers le logement : aucun apport solaire
|
|
98
|
+
if (baiesAdjVeranda.length === 0) {
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
31
102
|
const bver = ets.donnee_intermediaire.bver;
|
|
32
103
|
const T = ets.donnee_intermediaire.coef_transparence_ets;
|
|
33
104
|
|
|
@@ -80,7 +151,7 @@ export function calc_sse_j(bv_list, ets, ca, zc, mois) {
|
|
|
80
151
|
*/
|
|
81
152
|
const SseVerandaj = Ssdj + ssIndj * bver;
|
|
82
153
|
|
|
83
|
-
return
|
|
154
|
+
return SseVerandaj;
|
|
84
155
|
}
|
|
85
156
|
|
|
86
157
|
/**
|