@open3cl/engine 1.8.48 → 1.8.50
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 +132 -2
- package/13.2_generateur_combustion.spec.js +245 -1
- package/13.2_generateur_combustion_pdim.js +212 -0
- package/13.2_generateur_combustion_pdim.spec.js +259 -0
- package/14_generateur_ecs.js +4 -1
- package/9_chauffage.js +5 -1
- package/9_chauffage.spec.js +36 -0
- package/README.md +14 -14
- package/engine.js +1 -1
- package/features/engine/domain/ch/generateur-ch.service.js +74 -3
- package/features/engine/domain/ch/generateur-ch.service.spec.js +150 -0
- package/package.json +1 -1
|
@@ -9,6 +9,11 @@ import { updateGenerateurPacs } from './13.2_generateur_pac.js';
|
|
|
9
9
|
import getFicheTechnique from './ficheTechnique.js';
|
|
10
10
|
import { evaluateFormula as evaluateTvFormula } from './formula.js';
|
|
11
11
|
import { conventionPuissanceNominale } from './13.2_generateur_combustion_pn.js';
|
|
12
|
+
import {
|
|
13
|
+
anneeInstallationGenerateur,
|
|
14
|
+
pnEstimeeDimensionnement,
|
|
15
|
+
valeurChaudiereMurale
|
|
16
|
+
} from './13.2_generateur_combustion_pdim.js';
|
|
12
17
|
|
|
13
18
|
function criterePn(Pn, matcher) {
|
|
14
19
|
let critere_list = tvColumnLines('generateur_combustion', 'critere_pn', matcher);
|
|
@@ -122,6 +127,117 @@ export function caracteristiquesGenerateurCombustion(row, pn, ratio, E, F, type,
|
|
|
122
127
|
return caracteristiques;
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Estimation de la puissance nominale (W) d'un générateur à combustion dont la puissance n'est pas connue :
|
|
132
|
+
* Pch = Sh1 / Shtot × 1,2 × GV × (19 − Tbase) / 0,95³
|
|
133
|
+
*
|
|
134
|
+
* Si le générateur n'alimente qu'une partie du logement, la puissance est proratisée à la surface desservie.
|
|
135
|
+
* Le ratio est borné à ]0 ; 1] : une surface absente, nulle ou supérieure à Sh ne modifie pas Pch.
|
|
136
|
+
*
|
|
137
|
+
* Remarque : la méthode ne prévoit cette estimation que pour les générateurs individuels. Pour un générateur
|
|
138
|
+
* collectif sans pn, la même formule (GV du logement, convention pn = Pe) est une extrapolation.
|
|
139
|
+
*
|
|
140
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4 Calcul des puissances nominales (p.90-91)
|
|
141
|
+
*
|
|
142
|
+
* @param GV {number} déperditions enveloppe + renouvellement d'air (W/K)
|
|
143
|
+
* @param tbase {number} température extérieure de base (°C)
|
|
144
|
+
* @param ratioSurface {number} Sh_installation / Sh
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
export function pnEstimee(GV, tbase, ratioSurface = 1) {
|
|
148
|
+
const prorata = ratioSurface > 0 && ratioSurface < 1 ? ratioSurface : 1;
|
|
149
|
+
return (prorata * 1.2 * GV * (19 - tbase)) / 0.95 ** 3;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Pose murale d'une chaudière, lue dans la sous-fiche technique « Chaudière murale » (catégorie 7
|
|
154
|
+
* pour le chauffage, 8 pour l'ECS) du générateur.
|
|
155
|
+
*
|
|
156
|
+
* @param dpe {FullDpe}
|
|
157
|
+
* @param de {Donnee_entree}
|
|
158
|
+
* @param type {'ch' | 'ecs'}
|
|
159
|
+
* @returns {boolean|undefined} undefined si non renseigné
|
|
160
|
+
*/
|
|
161
|
+
export function chaudiereMurale(dpe, de, type) {
|
|
162
|
+
const fiche = getFicheTechnique(dpe, type === 'ch' ? '7' : '8', 'Chaudière murale', [
|
|
163
|
+
de.description
|
|
164
|
+
]);
|
|
165
|
+
return valeurChaudiereMurale(fiche?.valeur);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Volume de stockage (L) de la production d'ECS assurée par un générateur de chauffage mixte :
|
|
170
|
+
* générateur ECS mixte de même `reference_generateur_mixte`, sinon générateur ECS mixte unique.
|
|
171
|
+
*
|
|
172
|
+
* @param dpe {FullDpe}
|
|
173
|
+
* @param de {Donnee_entree} données du générateur de chauffage
|
|
174
|
+
* @returns {number|undefined}
|
|
175
|
+
*/
|
|
176
|
+
export function volumeStockageEcsMixte(dpe, de) {
|
|
177
|
+
const liste = (x) => (x === undefined || x === null ? [] : Array.isArray(x) ? x : [x]);
|
|
178
|
+
const generateursEcsMixtes = liste(dpe?.logement?.installation_ecs_collection?.installation_ecs)
|
|
179
|
+
.flatMap((installation) => liste(installation.generateur_ecs_collection?.generateur_ecs))
|
|
180
|
+
.filter((generateur) => String(generateur?.donnee_entree?.enum_usage_generateur_id) === '3');
|
|
181
|
+
|
|
182
|
+
const reference = de.reference_generateur_mixte;
|
|
183
|
+
const jumeau =
|
|
184
|
+
(reference !== undefined &&
|
|
185
|
+
reference !== null &&
|
|
186
|
+
generateursEcsMixtes.find(
|
|
187
|
+
(generateur) =>
|
|
188
|
+
String(generateur.donnee_entree.reference_generateur_mixte) === String(reference)
|
|
189
|
+
)) ||
|
|
190
|
+
(generateursEcsMixtes.length === 1 ? generateursEcsMixtes[0] : undefined);
|
|
191
|
+
|
|
192
|
+
return jumeau?.donnee_entree.volume_stockage;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Puissance nominale estimée (W) d'un générateur à combustion dont `pn` est absente.
|
|
197
|
+
*
|
|
198
|
+
* Méthode 3CL-DPE 2021 §13.2.2.4 : Pdim = max(Pch ; Pecs), puis table Pdim → Pn pour les chaudières
|
|
199
|
+
* (cf. 13.2_generateur_combustion_pdim.js). La méthode ne vise que les générateurs individuels :
|
|
200
|
+
* pour une installation collective, Pch est conservée (comportement historique).
|
|
201
|
+
*
|
|
202
|
+
* @param dpe {FullDpe}
|
|
203
|
+
* @param de {Donnee_entree} données du générateur
|
|
204
|
+
* @param type {'ch' | 'ecs'}
|
|
205
|
+
* @param pch {number} Pch (W) proratisée à la surface desservie
|
|
206
|
+
* @param optionsPn {{installationIndividuelle?: boolean, volumeStockage?: number}}
|
|
207
|
+
* @returns {number} Pn (W)
|
|
208
|
+
*/
|
|
209
|
+
export function pnEstimeeGenerateur(dpe, de, type, pch, optionsPn = {}) {
|
|
210
|
+
const installationIndividuelle =
|
|
211
|
+
optionsPn.installationIndividuelle ?? String(de.enum_type_installation_id) === '1';
|
|
212
|
+
if (!installationIndividuelle) {
|
|
213
|
+
return pch;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const usage = String(de.enum_usage_generateur_id);
|
|
217
|
+
const usageEcs = type === 'ecs' || usage === '3';
|
|
218
|
+
const usageChauffage = type === 'ch' || usage === '3';
|
|
219
|
+
const volumeStockage =
|
|
220
|
+
optionsPn.volumeStockage ??
|
|
221
|
+
(type === 'ecs' ? de.volume_stockage : usageEcs ? volumeStockageEcsMixte(dpe, de) : undefined);
|
|
222
|
+
|
|
223
|
+
const libelleTypeGenerateur =
|
|
224
|
+
enums[`type_generateur_${type}`]?.[de[`enum_type_generateur_${type}_id`]];
|
|
225
|
+
|
|
226
|
+
return pnEstimeeDimensionnement({
|
|
227
|
+
pch,
|
|
228
|
+
volumeStockage,
|
|
229
|
+
usageEcs,
|
|
230
|
+
usageChauffage,
|
|
231
|
+
libelleTypeGenerateur,
|
|
232
|
+
murale: chaudiereMurale(dpe, de, type),
|
|
233
|
+
anneeInstallation: anneeInstallationGenerateur(
|
|
234
|
+
de.annee_installation,
|
|
235
|
+
dpe?.logement?.caracteristique_generale?.annee_construction,
|
|
236
|
+
libelleTypeGenerateur
|
|
237
|
+
)
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
125
241
|
/**
|
|
126
242
|
* Si la méthode de saisie n'est pas "Valeur forfaitaire" mais "caractéristiques saisies"
|
|
127
243
|
* Documentation 3CL : "Pour les installations récentes ou recommandées, les caractéristiques réelles des chaudières présentées sur les bases
|
|
@@ -139,8 +255,22 @@ export function caracteristiquesGenerateurCombustion(row, pn, ratio, E, F, type,
|
|
|
139
255
|
* @param GV {number} déperdition de l'enveloppe
|
|
140
256
|
* @param tbase {number} température de fonctionnement du générateur
|
|
141
257
|
* @param methodeSaisie {number} méthode de saisie des caractéristiques du générateur
|
|
258
|
+
* @param ratioSurface {number} part de la surface du logement desservie par le générateur
|
|
259
|
+
* (Sh_installation / Sh), utilisée pour proratiser la puissance nominale estimée (§13.2.2.4)
|
|
260
|
+
* @param optionsPn {{installationIndividuelle?: boolean, volumeStockage?: number}} données de
|
|
261
|
+
* l'installation pour l'estimation de Pn par Pdim (§13.2.2.4, cf. pnEstimeeGenerateur)
|
|
142
262
|
*/
|
|
143
|
-
export function tv_generateur_combustion(
|
|
263
|
+
export function tv_generateur_combustion(
|
|
264
|
+
dpe,
|
|
265
|
+
di,
|
|
266
|
+
de,
|
|
267
|
+
type,
|
|
268
|
+
GV,
|
|
269
|
+
tbase,
|
|
270
|
+
methodeSaisie,
|
|
271
|
+
ratioSurface = 1,
|
|
272
|
+
optionsPn = {}
|
|
273
|
+
) {
|
|
144
274
|
const typeGenerateurKey = `enum_type_generateur_${type}_id`;
|
|
145
275
|
let enumTypeGenerateurId = de[typeGenerateurKey];
|
|
146
276
|
let row;
|
|
@@ -207,7 +337,7 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
|
|
|
207
337
|
} else {
|
|
208
338
|
// Calcul de la puissance nominale si non définie
|
|
209
339
|
if (!di.pn) {
|
|
210
|
-
di.pn = (
|
|
340
|
+
di.pn = pnEstimeeGenerateur(dpe, de, type, pnEstimee(GV, tbase, ratioSurface), optionsPn);
|
|
211
341
|
}
|
|
212
342
|
|
|
213
343
|
selectionLigne = (pn, ratio) => {
|
|
@@ -59,6 +59,14 @@ vi.mock('./ficheTechnique.js', () => ({
|
|
|
59
59
|
vi.mock('./13.2_generateur_combustion_pn.js', () => ({
|
|
60
60
|
conventionPuissanceNominale: vi.fn((pn, ratio) => ({ pn, ratio, convention: 'individualisee' }))
|
|
61
61
|
}));
|
|
62
|
+
/**
|
|
63
|
+
* Estimation Pdim → Pn (issue #271) : testée dans son propre spec. Par défaut, le double renvoie Pch.
|
|
64
|
+
*/
|
|
65
|
+
vi.mock('./13.2_generateur_combustion_pdim.js', () => ({
|
|
66
|
+
pnEstimeeDimensionnement: vi.fn(({ pch }) => pch),
|
|
67
|
+
anneeInstallationGenerateur: vi.fn(() => 2018),
|
|
68
|
+
valeurChaudiereMurale: vi.fn((v) => (v === 'oui' ? true : v === 'non' ? false : undefined))
|
|
69
|
+
}));
|
|
62
70
|
|
|
63
71
|
const {
|
|
64
72
|
tv_generateur_combustion,
|
|
@@ -68,8 +76,14 @@ const {
|
|
|
68
76
|
valeurVeilleuse,
|
|
69
77
|
caracteristiquesGenerateurCombustion,
|
|
70
78
|
puissanceFormuleQp0,
|
|
71
|
-
PN_MAX_QP0_CHAUDIERE_GAZ
|
|
79
|
+
PN_MAX_QP0_CHAUDIERE_GAZ,
|
|
80
|
+
pnEstimee,
|
|
81
|
+
pnEstimeeGenerateur,
|
|
82
|
+
chaudiereMurale,
|
|
83
|
+
volumeStockageEcsMixte
|
|
72
84
|
} = await import('./13.2_generateur_combustion.js');
|
|
85
|
+
const { pnEstimeeDimensionnement, anneeInstallationGenerateur } =
|
|
86
|
+
await import('./13.2_generateur_combustion_pdim.js');
|
|
73
87
|
const { tv, tvColumnLines } = await import('./utils.js');
|
|
74
88
|
const { conventionPuissanceNominale } = await import('./13.2_generateur_combustion_pn.js');
|
|
75
89
|
const { updateGenerateurBouilleurs } = await import('./13.2_generateur_combustion_bouilleur.js');
|
|
@@ -127,6 +141,22 @@ describe('tv_generateur_combustion - caractéristiques du générateur', () => {
|
|
|
127
141
|
expect(di.pn).toBe(12345);
|
|
128
142
|
});
|
|
129
143
|
|
|
144
|
+
test('puissance nominale absente, générateur desservant 40 % du logement : Pn proratisée (issue #264)', () => {
|
|
145
|
+
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
146
|
+
const di = {};
|
|
147
|
+
const de = { enum_type_generateur_ch_id: '89', ratio_virtualisation: 1, presence_ventouse: 0 };
|
|
148
|
+
tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1, 0.4);
|
|
149
|
+
expect(di.pn).toBeCloseTo(0.4 * 7837.877241580406, 9);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('puissance nominale renseignée : le prorata de surface ne s’applique pas', () => {
|
|
153
|
+
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
154
|
+
const di = { pn: 12345 };
|
|
155
|
+
const de = { enum_type_generateur_ch_id: '89', ratio_virtualisation: 1, presence_ventouse: 0 };
|
|
156
|
+
tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1, 0.4);
|
|
157
|
+
expect(di.pn).toBe(12345);
|
|
158
|
+
});
|
|
159
|
+
|
|
130
160
|
test('méthode forfaitaire (1) en chauffage : rpn, rpint, qp0 et pveil renseignés', () => {
|
|
131
161
|
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
132
162
|
const di = { pn: 20000 };
|
|
@@ -1430,3 +1460,217 @@ describe('puissanceFormuleQp0 - plafond de Pn à 400 kW pour les chaudières gaz
|
|
|
1430
1460
|
expect(qp0).toBeCloseTo(127.5468019078, 6);
|
|
1431
1461
|
});
|
|
1432
1462
|
});
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Estimation de la puissance nominale d'un générateur à combustion (pn absente).
|
|
1466
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
|
|
1467
|
+
*/
|
|
1468
|
+
describe('pnEstimee - puissance nominale estimée (issue #264)', () => {
|
|
1469
|
+
// 1,2 × 200 × (19 − (−9)) / 0,95³ -- valeur de référence de régression
|
|
1470
|
+
const PCH_LOGEMENT = 7837.877241580406;
|
|
1471
|
+
|
|
1472
|
+
test('générateur desservant tout le logement : Pch = 1,2 × GV × (19 − Tbase) / 0,95³', () => {
|
|
1473
|
+
expect(pnEstimee(200, -9)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1474
|
+
expect(pnEstimee(200, -9, 1)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
test('générateur desservant une partie du logement : Pch1 = Sh1 / Shtot × Pch', () => {
|
|
1478
|
+
expect(pnEstimee(200, -9, 0.25)).toBeCloseTo(0.25 * PCH_LOGEMENT, 9);
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
test('deux générateurs se partageant le logement : Pch1 + Pch2 = Pch', () => {
|
|
1482
|
+
expect(pnEstimee(200, -9, 0.3) + pnEstimee(200, -9, 0.7)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
test.each([
|
|
1486
|
+
['nul', 0],
|
|
1487
|
+
['négatif', -0.5],
|
|
1488
|
+
['supérieur à 1', 1.4],
|
|
1489
|
+
['non numérique', NaN],
|
|
1490
|
+
['absent', undefined]
|
|
1491
|
+
])('ratio %s : pas de prorata (Pch du logement)', (_libelle, ratio) => {
|
|
1492
|
+
expect(pnEstimee(200, -9, ratio)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Pn estimée par la puissance de dimensionnement (issue #271).
|
|
1498
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
|
|
1499
|
+
*/
|
|
1500
|
+
describe('pnEstimeeGenerateur - Pdim = max(Pch ; Pecs) et table Pdim → Pn (issue #271)', () => {
|
|
1501
|
+
const dpeAvecEcsMixte = (generateursEcs) => ({
|
|
1502
|
+
logement: {
|
|
1503
|
+
caracteristique_generale: { annee_construction: '1975' },
|
|
1504
|
+
installation_ecs_collection: {
|
|
1505
|
+
installation_ecs: [{ generateur_ecs_collection: { generateur_ecs: generateursEcs } }]
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
beforeEach(() => {
|
|
1511
|
+
vi.mocked(pnEstimeeDimensionnement).mockClear();
|
|
1512
|
+
vi.mocked(anneeInstallationGenerateur).mockClear();
|
|
1513
|
+
vi.mocked(getFicheTechnique).mockReset();
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
test('installation collective : Pch conservée, pas de table', () => {
|
|
1517
|
+
const de = { enum_type_installation_id: '2', enum_type_generateur_ch_id: '89' };
|
|
1518
|
+
expect(pnEstimeeGenerateur({}, de, 'ch', 7800)).toBe(7800);
|
|
1519
|
+
expect(pnEstimeeDimensionnement).not.toHaveBeenCalled();
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
test('option installationIndividuelle prioritaire sur la donnée du générateur', () => {
|
|
1523
|
+
const de = { enum_type_installation_id: '1', enum_type_generateur_ecs_id: 'A' };
|
|
1524
|
+
expect(pnEstimeeGenerateur({}, de, 'ecs', 7800, { installationIndividuelle: false })).toBe(
|
|
1525
|
+
7800
|
|
1526
|
+
);
|
|
1527
|
+
expect(pnEstimeeDimensionnement).not.toHaveBeenCalled();
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
test('chaudière de chauffage seul : paramètres transmis (libellé, pose murale, année)', () => {
|
|
1531
|
+
vi.mocked(getFicheTechnique).mockReturnValue({ valeur: 'oui' });
|
|
1532
|
+
const dpe = { logement: { caracteristique_generale: { annee_construction: '1975' } } };
|
|
1533
|
+
const de = {
|
|
1534
|
+
description: 'Chaudière gaz',
|
|
1535
|
+
enum_type_installation_id: '1',
|
|
1536
|
+
enum_usage_generateur_id: '1',
|
|
1537
|
+
enum_type_generateur_ch_id: '89',
|
|
1538
|
+
annee_installation: '2010'
|
|
1539
|
+
};
|
|
1540
|
+
pnEstimeeGenerateur(dpe, de, 'ch', 7800);
|
|
1541
|
+
|
|
1542
|
+
expect(getFicheTechnique).toHaveBeenCalledWith(dpe, '7', 'Chaudière murale', ['Chaudière gaz']);
|
|
1543
|
+
expect(anneeInstallationGenerateur).toHaveBeenCalledWith(
|
|
1544
|
+
'2010',
|
|
1545
|
+
'1975',
|
|
1546
|
+
'chaudière gaz standard 2001-2015'
|
|
1547
|
+
);
|
|
1548
|
+
expect(pnEstimeeDimensionnement).toHaveBeenCalledWith({
|
|
1549
|
+
pch: 7800,
|
|
1550
|
+
volumeStockage: undefined,
|
|
1551
|
+
usageEcs: false,
|
|
1552
|
+
usageChauffage: true,
|
|
1553
|
+
libelleTypeGenerateur: 'chaudière gaz standard 2001-2015',
|
|
1554
|
+
murale: true,
|
|
1555
|
+
anneeInstallation: 2018
|
|
1556
|
+
});
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
test('chaudière mixte : volume de stockage du générateur ECS jumeau (reference_generateur_mixte)', () => {
|
|
1560
|
+
const dpe = dpeAvecEcsMixte([
|
|
1561
|
+
{
|
|
1562
|
+
donnee_entree: {
|
|
1563
|
+
enum_usage_generateur_id: '3',
|
|
1564
|
+
reference_generateur_mixte: 'A',
|
|
1565
|
+
volume_stockage: 0
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
donnee_entree: {
|
|
1570
|
+
enum_usage_generateur_id: '3',
|
|
1571
|
+
reference_generateur_mixte: 'B',
|
|
1572
|
+
volume_stockage: 150
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
]);
|
|
1576
|
+
const de = {
|
|
1577
|
+
enum_type_installation_id: '1',
|
|
1578
|
+
enum_usage_generateur_id: '3',
|
|
1579
|
+
enum_type_generateur_ch_id: '89',
|
|
1580
|
+
reference_generateur_mixte: 'B'
|
|
1581
|
+
};
|
|
1582
|
+
pnEstimeeGenerateur(dpe, de, 'ch', 7800);
|
|
1583
|
+
expect(pnEstimeeDimensionnement).toHaveBeenCalledWith(
|
|
1584
|
+
expect.objectContaining({ volumeStockage: 150, usageEcs: true, usageChauffage: true })
|
|
1585
|
+
);
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
test('générateur ECS : volume de stockage du générateur, usage ECS seul', () => {
|
|
1589
|
+
vi.mocked(getFicheTechnique).mockReturnValue({ valeur: 'non' });
|
|
1590
|
+
const de = {
|
|
1591
|
+
description: 'Chaudière gaz',
|
|
1592
|
+
enum_usage_generateur_id: '2',
|
|
1593
|
+
enum_type_generateur_ecs_id: 'A',
|
|
1594
|
+
volume_stockage: 10
|
|
1595
|
+
};
|
|
1596
|
+
pnEstimeeGenerateur({}, de, 'ecs', 7800, { installationIndividuelle: true });
|
|
1597
|
+
expect(getFicheTechnique).toHaveBeenCalledWith({}, '8', 'Chaudière murale', ['Chaudière gaz']);
|
|
1598
|
+
expect(pnEstimeeDimensionnement).toHaveBeenCalledWith(
|
|
1599
|
+
expect.objectContaining({
|
|
1600
|
+
volumeStockage: 10,
|
|
1601
|
+
usageEcs: true,
|
|
1602
|
+
usageChauffage: false,
|
|
1603
|
+
libelleTypeGenerateur: 'chaudière gaz',
|
|
1604
|
+
murale: false
|
|
1605
|
+
})
|
|
1606
|
+
);
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
test('volume de stockage transmis en option prioritaire', () => {
|
|
1610
|
+
const de = { enum_type_installation_id: '1', enum_usage_generateur_id: '2' };
|
|
1611
|
+
pnEstimeeGenerateur({}, de, 'ecs', 7800, { volumeStockage: 300 });
|
|
1612
|
+
expect(pnEstimeeDimensionnement).toHaveBeenCalledWith(
|
|
1613
|
+
expect.objectContaining({ volumeStockage: 300 })
|
|
1614
|
+
);
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
test('tv_generateur_combustion : pn absente estimée via pnEstimeeGenerateur', () => {
|
|
1618
|
+
vi.mocked(pnEstimeeDimensionnement).mockReturnValueOnce(24000);
|
|
1619
|
+
vi.mocked(tv).mockReturnValue({ tv_generateur_combustion_id: '1', rpn: '90', rpint: '90' });
|
|
1620
|
+
const di = {};
|
|
1621
|
+
const de = {
|
|
1622
|
+
enum_type_generateur_ch_id: '89',
|
|
1623
|
+
enum_type_installation_id: '1',
|
|
1624
|
+
enum_usage_generateur_id: '1'
|
|
1625
|
+
};
|
|
1626
|
+
tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
|
|
1627
|
+
expect(di.pn).toBe(24000);
|
|
1628
|
+
});
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
describe('volumeStockageEcsMixte - volume de stockage de l’ECS d’un générateur mixte', () => {
|
|
1632
|
+
const ecs = (usage, reference, volume) => ({
|
|
1633
|
+
donnee_entree: {
|
|
1634
|
+
enum_usage_generateur_id: usage,
|
|
1635
|
+
reference_generateur_mixte: reference,
|
|
1636
|
+
volume_stockage: volume
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
const dpe = (generateurs) => ({
|
|
1640
|
+
logement: {
|
|
1641
|
+
installation_ecs_collection: {
|
|
1642
|
+
installation_ecs: { generateur_ecs_collection: { generateur_ecs: generateurs } }
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
test('jumeau par reference_generateur_mixte', () => {
|
|
1648
|
+
const d = dpe([ecs('3', 'R1', 80), ecs('3', 'R2', 200)]);
|
|
1649
|
+
expect(volumeStockageEcsMixte(d, { reference_generateur_mixte: 'R2' })).toBe(200);
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
test('sans référence : générateur ECS mixte unique', () => {
|
|
1653
|
+
const d = dpe([ecs('2', undefined, 300), ecs('3', undefined, 0)]);
|
|
1654
|
+
expect(volumeStockageEcsMixte(d, {})).toBe(0);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
test('plusieurs générateurs mixtes sans référence commune : volume inconnu', () => {
|
|
1658
|
+
const d = dpe([ecs('3', 'R1', 80), ecs('3', 'R2', 200)]);
|
|
1659
|
+
expect(volumeStockageEcsMixte(d, { reference_generateur_mixte: 'R3' })).toBeUndefined();
|
|
1660
|
+
expect(volumeStockageEcsMixte(d, {})).toBeUndefined();
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
test('aucune installation ECS', () => {
|
|
1664
|
+
expect(volumeStockageEcsMixte({}, {})).toBeUndefined();
|
|
1665
|
+
expect(
|
|
1666
|
+
volumeStockageEcsMixte(dpe(ecs('3', 'R', 50)), { reference_generateur_mixte: 'R' })
|
|
1667
|
+
).toBe(50);
|
|
1668
|
+
});
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
describe('chaudiereMurale - sous-fiche « Chaudière murale »', () => {
|
|
1672
|
+
test('fiche absente : pose inconnue', () => {
|
|
1673
|
+
vi.mocked(getFicheTechnique).mockReturnValue(null);
|
|
1674
|
+
expect(chaudiereMurale({}, { description: 'x' }, 'ch')).toBeUndefined();
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estimation de la puissance nominale d'un générateur à combustion individuel lorsque `pn` n'est
|
|
3
|
+
* pas connue (repli du moteur quand `donnee_intermediaire.pn` est absente).
|
|
4
|
+
*
|
|
5
|
+
* Méthode 3CL-DPE 2021, §13.2.2.4 « Calcul des puissances nominales » (p.90-91) :
|
|
6
|
+
* 1. Pch = Sh1/Shtot × 1,2 × GV × (19 − Tbase) / (1000 × 0,95³) (cf. `pnEstimee`)
|
|
7
|
+
* 2. Pecs selon le type de production d'ECS (volume de stockage Vs) ;
|
|
8
|
+
* 3. Pdim = max(Pch ; Pecs) ;
|
|
9
|
+
* 4. Pn des chaudières lue dans la table Pdim → Pn (chaudières murales installées avant 2006 ou
|
|
10
|
+
* chaudières au sol / chaudières murales installées à partir de 2006).
|
|
11
|
+
*
|
|
12
|
+
* Les autres générateurs à combustion (radiateurs gaz, générateurs d'air chaud, poêles,
|
|
13
|
+
* accumulateurs, chauffe-eau…) conservent Pn = Pdim.
|
|
14
|
+
*
|
|
15
|
+
* Le moteur de référence (Tribu) ne fait pas cette estimation (Pn est une donnée d'entrée
|
|
16
|
+
* obligatoire) : elle relève du logiciel de saisie et sert surtout aux saisies incomplètes et aux
|
|
17
|
+
* recommandations.
|
|
18
|
+
*
|
|
19
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4 Calcul des puissances nominales (p.90-91)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Année à partir de laquelle les chaudières murales relèvent de la seconde colonne de la table
|
|
24
|
+
* Pdim → Pn.
|
|
25
|
+
*/
|
|
26
|
+
export const ANNEE_CHAUDIERE_MURALE_RECENTE = 2006;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Table Pdim → Pn (kW) des chaudières : [Pdim max (kW), Pn murale < 2006 ou sol, Pn murale ≥ 2006].
|
|
30
|
+
* Au-delà de 40 kW : Pn = (Partie entière(Pdim / 5) + 1) × 5 pour les deux colonnes.
|
|
31
|
+
*/
|
|
32
|
+
const TABLE_PDIM_PN = [
|
|
33
|
+
[5, 18, 5],
|
|
34
|
+
[10, 18, 10],
|
|
35
|
+
[13, 18, 13],
|
|
36
|
+
[18, 18, 18],
|
|
37
|
+
[24, 24, 24],
|
|
38
|
+
[28, 28, 28],
|
|
39
|
+
[32, 32, 32],
|
|
40
|
+
[40, 40, 40]
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Puissance de dimensionnement nécessaire pour l'ECS (kW) selon le volume de stockage (L).
|
|
45
|
+
*
|
|
46
|
+
* | Type de production | Volume de stockage | Pecs (kW) |
|
|
47
|
+
* |--------------------|--------------------|----------------------------|
|
|
48
|
+
* | Instantanée | Vs = 0 | 21 |
|
|
49
|
+
* | Semi-instantanée | 0 < Vs ≤ 20 | 21 − 0,8 × Vs |
|
|
50
|
+
* | Semi-accumulation | 20 < Vs ≤ 150 | 5 − 1,751 × (Vs − 20) / 65 |
|
|
51
|
+
* | Accumulation | 150 < Vs | (7,14 × Vs + 428) / 1000 |
|
|
52
|
+
*
|
|
53
|
+
* Formules reprises telles que publiées (p.91).
|
|
54
|
+
*
|
|
55
|
+
* @param volumeStockage {number|string|undefined} Vs (L)
|
|
56
|
+
* @returns {number|undefined} Pecs (kW), undefined si le volume n'est pas renseigné
|
|
57
|
+
*/
|
|
58
|
+
export function puissanceEcs(volumeStockage) {
|
|
59
|
+
if (volumeStockage === undefined || volumeStockage === null || volumeStockage === '') {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const vs = Number(volumeStockage);
|
|
63
|
+
if (!Number.isFinite(vs) || vs < 0) return undefined;
|
|
64
|
+
|
|
65
|
+
if (vs === 0) return 21;
|
|
66
|
+
if (vs <= 20) return 21 - 0.8 * vs;
|
|
67
|
+
if (vs <= 150) return 5 - (1.751 * (vs - 20)) / 65;
|
|
68
|
+
return (7.14 * vs + 428) / 1000;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pn (kW) d'une chaudière à partir de sa puissance de dimensionnement (table §13.2.2.4).
|
|
73
|
+
*
|
|
74
|
+
* @param pdim {number} Pdim (kW)
|
|
75
|
+
* @param muraleRecente {boolean} true pour une chaudière murale installée à partir de 2006,
|
|
76
|
+
* false pour une chaudière murale installée avant 2006 ou une chaudière au sol
|
|
77
|
+
* @returns {number} Pn (kW)
|
|
78
|
+
*/
|
|
79
|
+
export function pnChaudiere(pdim, muraleRecente) {
|
|
80
|
+
for (const [pdimMax, pnAncienneOuSol, pnMuraleRecente] of TABLE_PDIM_PN) {
|
|
81
|
+
if (pdim <= pdimMax) {
|
|
82
|
+
return muraleRecente ? pnMuraleRecente : pnAncienneOuSol;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return (Math.floor(pdim / 5) + 1) * 5;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Le générateur est-il une chaudière (table Pdim → Pn) ?
|
|
90
|
+
* Les « chaudière(s) … multi bâtiment modélisée comme un réseau de chaleur » ne sont pas
|
|
91
|
+
* concernées (générateurs collectifs).
|
|
92
|
+
*
|
|
93
|
+
* @param libelleTypeGenerateur {string|undefined} libellé enum_type_generateur_ch / _ecs
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
export function isChaudiere(libelleTypeGenerateur) {
|
|
97
|
+
const libelle = (libelleTypeGenerateur ?? '').toString().toLowerCase();
|
|
98
|
+
return libelle.includes('chaudière') && !libelle.includes('réseau de chaleur');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Intervalle [min ; max] des années d'installation possibles d'après le libellé du type de
|
|
103
|
+
* générateur (« avant 1981 », « 2001-2015 », « après 2015 », « à partir de 2012 »…).
|
|
104
|
+
*
|
|
105
|
+
* @param libelleTypeGenerateur {string|undefined}
|
|
106
|
+
* @returns {{min: number, max: number}}
|
|
107
|
+
*/
|
|
108
|
+
export function periodeInstallationLibelle(libelleTypeGenerateur) {
|
|
109
|
+
const libelle = (libelleTypeGenerateur ?? '').toString().toLowerCase();
|
|
110
|
+
let match = libelle.match(/(\d{4})\s*-\s*(\d{4})/);
|
|
111
|
+
if (match) return { min: Number(match[1]), max: Number(match[2]) };
|
|
112
|
+
|
|
113
|
+
match = libelle.match(/avant (\d{4})/);
|
|
114
|
+
if (match) return { min: -Infinity, max: Number(match[1]) - 1 };
|
|
115
|
+
|
|
116
|
+
match = libelle.match(/(?:après|à partir de) (\d{4})/);
|
|
117
|
+
if (match) {
|
|
118
|
+
// « après 2015 » succède à « 2001-2015 » : 2015 appartient déjà à la période précédente
|
|
119
|
+
return { min: Number(match[1]) + (libelle.includes('après') ? 1 : 0), max: Infinity };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { min: -Infinity, max: Infinity };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Année d'installation du générateur : année saisie, sinon année de construction ramenée dans la
|
|
127
|
+
* période indiquée par le type de générateur (une « chaudière gaz à condensation après 2015 » d'un
|
|
128
|
+
* bâtiment de 1970 a été installée après 2015).
|
|
129
|
+
*
|
|
130
|
+
* @param anneeInstallation {number|string|undefined} année d'installation saisie
|
|
131
|
+
* @param anneeConstruction {number|string|undefined} année de construction du bâtiment
|
|
132
|
+
* @param libelleTypeGenerateur {string|undefined}
|
|
133
|
+
* @returns {number|undefined}
|
|
134
|
+
*/
|
|
135
|
+
export function anneeInstallationGenerateur(
|
|
136
|
+
anneeInstallation,
|
|
137
|
+
anneeConstruction,
|
|
138
|
+
libelleTypeGenerateur
|
|
139
|
+
) {
|
|
140
|
+
const saisie = parseInt(anneeInstallation, 10);
|
|
141
|
+
if (Number.isFinite(saisie)) return saisie;
|
|
142
|
+
|
|
143
|
+
const { min, max } = periodeInstallationLibelle(libelleTypeGenerateur);
|
|
144
|
+
const construction = parseInt(anneeConstruction, 10);
|
|
145
|
+
if (Number.isFinite(construction)) return Math.min(Math.max(construction, min), max);
|
|
146
|
+
|
|
147
|
+
// Période entièrement connue d'un seul côté de 2006 : la borne suffit
|
|
148
|
+
if (Number.isFinite(min) && min >= ANNEE_CHAUDIERE_MURALE_RECENTE) return min;
|
|
149
|
+
if (Number.isFinite(max) && max < ANNEE_CHAUDIERE_MURALE_RECENTE) return max;
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Valeur de la sous-fiche technique « Chaudière murale » → true (oui), false (non), undefined.
|
|
155
|
+
* @param valeur {string|undefined}
|
|
156
|
+
* @returns {boolean|undefined}
|
|
157
|
+
*/
|
|
158
|
+
export function valeurChaudiereMurale(valeur) {
|
|
159
|
+
const v = (valeur ?? '').toString().trim().toLowerCase();
|
|
160
|
+
if (v.startsWith('oui')) return true;
|
|
161
|
+
if (v.startsWith('non')) return false;
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Puissance nominale estimée (W) d'un générateur à combustion sans `pn` (§13.2.2.4).
|
|
167
|
+
*
|
|
168
|
+
* Choix de colonne de la table Pdim → Pn :
|
|
169
|
+
* - chaudière murale (fiche technique « Chaudière murale : oui ») installée à partir de 2006 →
|
|
170
|
+
* seconde colonne ;
|
|
171
|
+
* - chaudière au sol (« Chaudière murale : non ») ou installée avant 2006 → première colonne ;
|
|
172
|
+
* - type de pose inconnu → seconde colonne si installée à partir de 2006 (chaudières récentes
|
|
173
|
+
* majoritairement murales), première sinon.
|
|
174
|
+
*
|
|
175
|
+
* @param params {{
|
|
176
|
+
* pch: number, Pch (W), déjà proratisée à la surface desservie
|
|
177
|
+
* volumeStockage?: number|string, Vs (L) de la production d'ECS assurée par le générateur
|
|
178
|
+
* usageEcs?: boolean, le générateur produit de l'ECS (usage ECS ou mixte)
|
|
179
|
+
* usageChauffage?: boolean, le générateur assure le chauffage (défaut true)
|
|
180
|
+
* libelleTypeGenerateur?: string,
|
|
181
|
+
* murale?: boolean, pose murale (undefined si inconnue)
|
|
182
|
+
* anneeInstallation?: number année d'installation (undefined si inconnue)
|
|
183
|
+
* }}
|
|
184
|
+
* @returns {number} Pn (W)
|
|
185
|
+
*/
|
|
186
|
+
export function pnEstimeeDimensionnement({
|
|
187
|
+
pch,
|
|
188
|
+
volumeStockage,
|
|
189
|
+
usageEcs = false,
|
|
190
|
+
usageChauffage = true,
|
|
191
|
+
libelleTypeGenerateur,
|
|
192
|
+
murale,
|
|
193
|
+
anneeInstallation
|
|
194
|
+
}) {
|
|
195
|
+
const pchKw = usageChauffage ? pch / 1000 : 0;
|
|
196
|
+
const pecsKw = usageEcs ? puissanceEcs(volumeStockage) : undefined;
|
|
197
|
+
const pdim = Math.max(pchKw, pecsKw ?? 0);
|
|
198
|
+
|
|
199
|
+
// Aucune donnée exploitable pour l'ECS d'un générateur ECS seul : comportement historique (Pch)
|
|
200
|
+
if (!usageChauffage && pecsKw === undefined) {
|
|
201
|
+
return pch;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!isChaudiere(libelleTypeGenerateur)) {
|
|
205
|
+
return pdim * 1000;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const recente =
|
|
209
|
+
anneeInstallation !== undefined && anneeInstallation >= ANNEE_CHAUDIERE_MURALE_RECENTE;
|
|
210
|
+
const muraleRecente = recente && murale !== false;
|
|
211
|
+
return pnChaudiere(pdim, muraleRecente) * 1000;
|
|
212
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
ANNEE_CHAUDIERE_MURALE_RECENTE,
|
|
4
|
+
anneeInstallationGenerateur,
|
|
5
|
+
isChaudiere,
|
|
6
|
+
periodeInstallationLibelle,
|
|
7
|
+
pnChaudiere,
|
|
8
|
+
pnEstimeeDimensionnement,
|
|
9
|
+
puissanceEcs,
|
|
10
|
+
valeurChaudiereMurale
|
|
11
|
+
} from './13.2_generateur_combustion_pdim.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Module de calcul pur (aucune dépendance) : pas de mock nécessaire.
|
|
15
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4 Calcul des puissances nominales (p.90-91)
|
|
16
|
+
*/
|
|
17
|
+
describe('puissanceEcs - puissance de dimensionnement ECS selon le volume de stockage', () => {
|
|
18
|
+
test('production instantanée (Vs = 0) : Pecs = 21 kW', () => {
|
|
19
|
+
expect(puissanceEcs(0)).toBe(21);
|
|
20
|
+
expect(puissanceEcs('0.000')).toBe(21);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('semi-instantanée (0 < Vs ≤ 20) : Pecs = 21 − 0,8 × Vs', () => {
|
|
24
|
+
expect(puissanceEcs(10)).toBeCloseTo(13, 9);
|
|
25
|
+
expect(puissanceEcs(20)).toBeCloseTo(5, 9);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('semi-accumulation (20 < Vs ≤ 150) : Pecs = 5 − 1,751 × (Vs − 20) / 65', () => {
|
|
29
|
+
expect(puissanceEcs(85)).toBeCloseTo(5 - 1.751, 9);
|
|
30
|
+
expect(puissanceEcs(150)).toBeCloseTo(5 - (1.751 * 130) / 65, 9);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('accumulation (Vs > 150) : Pecs = (7,14 × Vs + 428) / 1000', () => {
|
|
34
|
+
expect(puissanceEcs(200)).toBeCloseTo((7.14 * 200 + 428) / 1000, 9);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test.each([undefined, null, '', 'abc', -5])('volume %s non exploitable : undefined', (vs) => {
|
|
38
|
+
expect(puissanceEcs(vs)).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('pnChaudiere - table Pdim → Pn des chaudières', () => {
|
|
43
|
+
test.each([
|
|
44
|
+
[3, 18, 5],
|
|
45
|
+
[5, 18, 5],
|
|
46
|
+
[7, 18, 10],
|
|
47
|
+
[12, 18, 13],
|
|
48
|
+
[15, 18, 18],
|
|
49
|
+
[18, 18, 18],
|
|
50
|
+
[20, 24, 24],
|
|
51
|
+
[26, 28, 28],
|
|
52
|
+
[30, 32, 32],
|
|
53
|
+
[40, 40, 40]
|
|
54
|
+
])('Pdim = %s kW : Pn = %s kW (murale < 2006 ou sol), %s kW (murale ≥ 2006)', (pdim, a, b) => {
|
|
55
|
+
expect(pnChaudiere(pdim, false)).toBe(a);
|
|
56
|
+
expect(pnChaudiere(pdim, true)).toBe(b);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('Pdim > 40 kW : Pn = (Partie entière(Pdim / 5) + 1) × 5, quelle que soit la colonne', () => {
|
|
60
|
+
expect(pnChaudiere(41, false)).toBe(45);
|
|
61
|
+
expect(pnChaudiere(45, true)).toBe(50);
|
|
62
|
+
expect(pnChaudiere(57.3, true)).toBe(60);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('Pn ≥ Pdim sur toute la table', () => {
|
|
66
|
+
for (let pdim = 0.5; pdim < 80; pdim += 0.5) {
|
|
67
|
+
expect(pnChaudiere(pdim, false)).toBeGreaterThanOrEqual(pdim);
|
|
68
|
+
expect(pnChaudiere(pdim, true)).toBeGreaterThanOrEqual(pdim);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('isChaudiere - générateurs concernés par la table Pdim → Pn', () => {
|
|
74
|
+
test.each([
|
|
75
|
+
['chaudière gaz à condensation après 2015', true],
|
|
76
|
+
['pompe à chaleur hybride : partie chaudière chaudière gaz à condensation après 2015', true],
|
|
77
|
+
['chaudière(s) gaz multi bâtiment modélisée comme un réseau de chaleur', false],
|
|
78
|
+
['radiateur à gaz indépendant ou autonome après 2006', false],
|
|
79
|
+
['générateur à air chaud à combustion avant 2006', false],
|
|
80
|
+
['accumulateur gaz classique après 2000', false],
|
|
81
|
+
[undefined, false]
|
|
82
|
+
])('%s → %s', (libelle, attendu) => {
|
|
83
|
+
expect(isChaudiere(libelle)).toBe(attendu);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('periodeInstallationLibelle - période d’installation déduite du type de générateur', () => {
|
|
88
|
+
test.each([
|
|
89
|
+
['chaudière gaz standard 2001-2015', 2001, 2015],
|
|
90
|
+
['chaudière gaz classique avant 1981', -Infinity, 1980],
|
|
91
|
+
['chaudière gaz à condensation après 2015', 2016, Infinity],
|
|
92
|
+
['poêle à bois bouilleur bûche installé à partir de 2012', 2012, Infinity],
|
|
93
|
+
['chaudière électrique', -Infinity, Infinity],
|
|
94
|
+
[undefined, -Infinity, Infinity]
|
|
95
|
+
])('%s → [%s ; %s]', (libelle, min, max) => {
|
|
96
|
+
expect(periodeInstallationLibelle(libelle)).toStrictEqual({ min, max });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('anneeInstallationGenerateur - année d’installation retenue', () => {
|
|
101
|
+
test('année d’installation saisie prioritaire', () => {
|
|
102
|
+
expect(anneeInstallationGenerateur('2003', 1970, 'chaudière gaz après 2015')).toBe(2003);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('repli : année de construction si elle est compatible avec le type de générateur', () => {
|
|
106
|
+
expect(anneeInstallationGenerateur(undefined, '2010', 'chaudière gaz standard 2001-2015')).toBe(
|
|
107
|
+
2010
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('repli : année de construction ramenée dans la période du type de générateur', () => {
|
|
112
|
+
expect(
|
|
113
|
+
anneeInstallationGenerateur(undefined, 1970, 'chaudière gaz à condensation après 2015')
|
|
114
|
+
).toBe(2016);
|
|
115
|
+
expect(anneeInstallationGenerateur(undefined, 2020, 'chaudière gaz classique avant 1981')).toBe(
|
|
116
|
+
1980
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('sans année de construction : borne de la période si elle tranche par rapport à 2006', () => {
|
|
121
|
+
expect(anneeInstallationGenerateur(undefined, undefined, 'chaudière gaz après 2015')).toBe(
|
|
122
|
+
2016
|
|
123
|
+
);
|
|
124
|
+
expect(anneeInstallationGenerateur(undefined, undefined, 'chaudière gaz avant 1981')).toBe(
|
|
125
|
+
1980
|
|
126
|
+
);
|
|
127
|
+
expect(
|
|
128
|
+
anneeInstallationGenerateur(undefined, undefined, 'chaudière gaz standard 2001-2015')
|
|
129
|
+
).toBeUndefined();
|
|
130
|
+
expect(
|
|
131
|
+
anneeInstallationGenerateur(undefined, undefined, 'chaudière électrique')
|
|
132
|
+
).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('valeurChaudiereMurale - sous-fiche « Chaudière murale »', () => {
|
|
137
|
+
test.each([
|
|
138
|
+
['oui', true],
|
|
139
|
+
[' Oui ', true],
|
|
140
|
+
['non', false],
|
|
141
|
+
['NC', undefined],
|
|
142
|
+
[undefined, undefined]
|
|
143
|
+
])('%s → %s', (valeur, attendu) => {
|
|
144
|
+
expect(valeurChaudiereMurale(valeur)).toBe(attendu);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('pnEstimeeDimensionnement - Pn estimée (W) d’un générateur sans pn', () => {
|
|
149
|
+
const CHAUDIERE = 'chaudière gaz à condensation après 2015';
|
|
150
|
+
|
|
151
|
+
test('chaudière de chauffage seul, murale récente : Pn = table(Pch) seconde colonne', () => {
|
|
152
|
+
expect(
|
|
153
|
+
pnEstimeeDimensionnement({
|
|
154
|
+
pch: 7800,
|
|
155
|
+
libelleTypeGenerateur: CHAUDIERE,
|
|
156
|
+
murale: true,
|
|
157
|
+
anneeInstallation: 2018
|
|
158
|
+
})
|
|
159
|
+
).toBe(10000);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('chaudière au sol : première colonne (plancher 18 kW) même installée après 2006', () => {
|
|
163
|
+
expect(
|
|
164
|
+
pnEstimeeDimensionnement({
|
|
165
|
+
pch: 7800,
|
|
166
|
+
libelleTypeGenerateur: CHAUDIERE,
|
|
167
|
+
murale: false,
|
|
168
|
+
anneeInstallation: 2018
|
|
169
|
+
})
|
|
170
|
+
).toBe(18000);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('chaudière murale installée avant 2006 : première colonne', () => {
|
|
174
|
+
expect(
|
|
175
|
+
pnEstimeeDimensionnement({
|
|
176
|
+
pch: 2300,
|
|
177
|
+
libelleTypeGenerateur: 'chaudière gaz standard 1991-2000',
|
|
178
|
+
murale: true,
|
|
179
|
+
anneeInstallation: ANNEE_CHAUDIERE_MURALE_RECENTE - 1
|
|
180
|
+
})
|
|
181
|
+
).toBe(18000);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('pose inconnue : seconde colonne si installée à partir de 2006, première sinon', () => {
|
|
185
|
+
const base = { pch: 2300, libelleTypeGenerateur: CHAUDIERE };
|
|
186
|
+
expect(pnEstimeeDimensionnement({ ...base, anneeInstallation: 2006 })).toBe(5000);
|
|
187
|
+
expect(pnEstimeeDimensionnement({ ...base, anneeInstallation: 2005 })).toBe(18000);
|
|
188
|
+
expect(pnEstimeeDimensionnement({ ...base, anneeInstallation: undefined })).toBe(18000);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('chaudière mixte à production instantanée : Pdim = max(Pch ; 21 kW)', () => {
|
|
192
|
+
expect(
|
|
193
|
+
pnEstimeeDimensionnement({
|
|
194
|
+
pch: 7800,
|
|
195
|
+
volumeStockage: 0,
|
|
196
|
+
usageEcs: true,
|
|
197
|
+
libelleTypeGenerateur: CHAUDIERE,
|
|
198
|
+
murale: true,
|
|
199
|
+
anneeInstallation: 2018
|
|
200
|
+
})
|
|
201
|
+
).toBe(24000);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('chaudière mixte à accumulation : Pecs faible, Pdim = Pch', () => {
|
|
205
|
+
expect(
|
|
206
|
+
pnEstimeeDimensionnement({
|
|
207
|
+
pch: 12500,
|
|
208
|
+
volumeStockage: 200,
|
|
209
|
+
usageEcs: true,
|
|
210
|
+
libelleTypeGenerateur: CHAUDIERE,
|
|
211
|
+
murale: true,
|
|
212
|
+
anneeInstallation: 2018
|
|
213
|
+
})
|
|
214
|
+
).toBe(13000);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('générateur ECS seul : Pdim = Pecs (Pch ignorée)', () => {
|
|
218
|
+
expect(
|
|
219
|
+
pnEstimeeDimensionnement({
|
|
220
|
+
pch: 30000,
|
|
221
|
+
volumeStockage: 10,
|
|
222
|
+
usageEcs: true,
|
|
223
|
+
usageChauffage: false,
|
|
224
|
+
libelleTypeGenerateur: CHAUDIERE,
|
|
225
|
+
murale: true,
|
|
226
|
+
anneeInstallation: 2018
|
|
227
|
+
})
|
|
228
|
+
).toBe(13000);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('générateur ECS seul sans volume de stockage : Pch conservée (comportement historique)', () => {
|
|
232
|
+
expect(
|
|
233
|
+
pnEstimeeDimensionnement({
|
|
234
|
+
pch: 7800,
|
|
235
|
+
usageEcs: true,
|
|
236
|
+
usageChauffage: false,
|
|
237
|
+
libelleTypeGenerateur: CHAUDIERE
|
|
238
|
+
})
|
|
239
|
+
).toBe(7800);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('générateur non-chaudière (radiateur gaz, air chaud…) : Pn = Pdim', () => {
|
|
243
|
+
expect(
|
|
244
|
+
pnEstimeeDimensionnement({
|
|
245
|
+
pch: 7800,
|
|
246
|
+
libelleTypeGenerateur: 'radiateur à gaz indépendant ou autonome après 2006'
|
|
247
|
+
})
|
|
248
|
+
).toBeCloseTo(7800, 9);
|
|
249
|
+
expect(
|
|
250
|
+
pnEstimeeDimensionnement({
|
|
251
|
+
pch: 3000,
|
|
252
|
+
volumeStockage: 0,
|
|
253
|
+
usageEcs: true,
|
|
254
|
+
usageChauffage: false,
|
|
255
|
+
libelleTypeGenerateur: 'chauffe-eau gaz à production instantanée après 2015'
|
|
256
|
+
})
|
|
257
|
+
).toBeCloseTo(21000, 9);
|
|
258
|
+
});
|
|
259
|
+
});
|
package/14_generateur_ecs.js
CHANGED
|
@@ -380,7 +380,10 @@ export default function calc_gen_ecs(dpe, gen_ecs, ecs_di, ecs_de, GV, ca_id, zc
|
|
|
380
380
|
const tbase = Tbase[ca][zc.slice(0, 2)];
|
|
381
381
|
|
|
382
382
|
const methodeSaisie = parseInt(de.enum_methode_saisie_carac_sys_id);
|
|
383
|
-
|
|
383
|
+
// §13.2.2.4 : Pn estimée (si absente) à partir de Pecs (volume de stockage) pour les installations individuelles
|
|
384
|
+
tv_generateur_combustion(dpe, di, de, 'ecs', GV, tbase, methodeSaisie, 1, {
|
|
385
|
+
installationIndividuelle: String(ecs_de.enum_type_installation_id) === '1'
|
|
386
|
+
});
|
|
384
387
|
|
|
385
388
|
if (bug_for_bug_compat) {
|
|
386
389
|
if (di.qp0 < 1) {
|
package/9_chauffage.js
CHANGED
|
@@ -80,7 +80,11 @@ export default function calc_chauffage(
|
|
|
80
80
|
|
|
81
81
|
if (genChDu.isCombustionGenerator) {
|
|
82
82
|
const methodeSaisie = parseInt(genChDe.enum_methode_saisie_carac_sys_id);
|
|
83
|
-
|
|
83
|
+
// §13.2.2.4 : Pn estimée proratisée à la surface chauffée par l'installation (individuelle uniquement :
|
|
84
|
+
// un générateur collectif dessert tout le bâtiment, la surface chauffée du logement n'est pas la sienne)
|
|
85
|
+
const ratioSurface =
|
|
86
|
+
String(genChDe.enum_type_installation_id) === '1' ? genChDe.surface_chauffee / Sh : 1;
|
|
87
|
+
tv_generateur_combustion(dpe, genChDi, genChDe, 'ch', GV, tbase, methodeSaisie, ratioSurface);
|
|
84
88
|
|
|
85
89
|
const type_gen_ch_list = tvColumnIDs('temp_fonc_30', 'type_generateur_ch');
|
|
86
90
|
if (type_gen_ch_list.includes(genChDe.enum_type_generateur_ch_id)) {
|
package/9_chauffage.spec.js
CHANGED
|
@@ -336,6 +336,42 @@ describe('calc_chauffage - générateurs à combustion', () => {
|
|
|
336
336
|
expect(tv_temp_fonc_30_100).toHaveBeenCalled();
|
|
337
337
|
});
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4 (prorata de Pch à la surface desservie)
|
|
341
|
+
*/
|
|
342
|
+
test('ratio surface chauffée de l’installation / Sh transmis pour la Pn estimée (issue #264)', () => {
|
|
343
|
+
const ch = installation(
|
|
344
|
+
[generateur({ enum_type_generateur_ch_id: '5', enum_methode_saisie_carac_sys_id: '1' })],
|
|
345
|
+
[emetteur('1')],
|
|
346
|
+
{ surface_chauffee: 40, enum_type_installation_id: '1' }
|
|
347
|
+
);
|
|
348
|
+
appel(ch, { Sh: 100, GV: 150 });
|
|
349
|
+
const args = vi.mocked(tv_generateur_combustion).mock.calls[0];
|
|
350
|
+
expect(args[3]).toBe('ch');
|
|
351
|
+
expect(args[4]).toBe(150);
|
|
352
|
+
expect(args[7]).toBeCloseTo(0.4, 9);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('surface chauffée absente : repli sur Sh, ratio de 1', () => {
|
|
356
|
+
const ch = installation(
|
|
357
|
+
[generateur({ enum_type_generateur_ch_id: '5', enum_methode_saisie_carac_sys_id: '1' })],
|
|
358
|
+
[emetteur('1')],
|
|
359
|
+
{ surface_chauffee: undefined, enum_type_installation_id: '1' }
|
|
360
|
+
);
|
|
361
|
+
appel(ch, { Sh: 100 });
|
|
362
|
+
expect(vi.mocked(tv_generateur_combustion).mock.calls[0][7]).toBe(1);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('installation collective : pas de prorata, le générateur dessert tout le bâtiment', () => {
|
|
366
|
+
const ch = installation(
|
|
367
|
+
[generateur({ enum_type_generateur_ch_id: '5', enum_methode_saisie_carac_sys_id: '1' })],
|
|
368
|
+
[emetteur('1')],
|
|
369
|
+
{ surface_chauffee: 40, enum_type_installation_id: '2' }
|
|
370
|
+
);
|
|
371
|
+
appel(ch, { Sh: 100 });
|
|
372
|
+
expect(vi.mocked(tv_generateur_combustion).mock.calls[0][7]).toBe(1);
|
|
373
|
+
});
|
|
374
|
+
|
|
339
375
|
test('méthode 5 avec températures déjà connues : pas de recalcul', () => {
|
|
340
376
|
const ch = installation(
|
|
341
377
|
[
|
package/README.md
CHANGED
|
@@ -370,7 +370,7 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
370
370
|
|
|
371
371
|
<!-- CORPUS:START -->
|
|
372
372
|
|
|
373
|
-
> **Version `1.8.
|
|
373
|
+
> **Version `1.8.49`** · branche `main` · généré le 2026-10-12
|
|
374
374
|
> Seuil de tolérance **5%**
|
|
375
375
|
|
|
376
376
|
<table>
|
|
@@ -403,24 +403,24 @@ Ces résultats sont **générés automatiquement** à la fin de `npm run test:co
|
|
|
403
403
|
|
|
404
404
|
<table>
|
|
405
405
|
<tr>
|
|
406
|
-
<td align="center"><strong>5,
|
|
407
|
-
<td align="center"><strong>4,
|
|
408
|
-
<td align="center"><strong>0,
|
|
409
|
-
<td align="center"><strong>
|
|
406
|
+
<td align="center"><strong>5,56 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>4,89 ms</strong><br/><sub>médiane</sub></td>
|
|
408
|
+
<td align="center"><strong>0,13 ms</strong><br/><sub>min</sub></td>
|
|
409
|
+
<td align="center"><strong>581,4 ms</strong><br/><sub>max</sub></td>
|
|
410
410
|
</tr>
|
|
411
411
|
</table>
|
|
412
412
|
|
|
413
413
|
| Corpus | Moyenne | Médiane | Min | Max | p95 | p99 |
|
|
414
414
|
| :--------------------------------------------- | ------: | ------: | ------: | -------: | ------: | ------: |
|
|
415
|
-
| **Généraliste** |
|
|
416
|
-
| **Appartement · chauffage individuel (2025)** | 4,
|
|
417
|
-
| **Logement individuel (2025)** | 5,
|
|
418
|
-
| **Maison individuelle (2025)** |
|
|
419
|
-
| **Immeuble · chauffage individuel** | 6,
|
|
420
|
-
| **Appartement · chauffage collectif (2025)** | 4,
|
|
421
|
-
| **Immeuble · chauffage collectif** | 5,
|
|
422
|
-
| **Immeuble · chauffage mixte** | 5,
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** |
|
|
415
|
+
| **Généraliste** | 4,50 ms | 4,07 ms | 0,21 ms | 36,7 ms | 8,24 ms | 11,2 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 4,06 ms | 3,74 ms | 1,54 ms | 29,8 ms | 6,88 ms | 8,57 ms |
|
|
417
|
+
| **Logement individuel (2025)** | 5,52 ms | 4,95 ms | 1,71 ms | 34,2 ms | 10,1 ms | 13,7 ms |
|
|
418
|
+
| **Maison individuelle (2025)** | 6,52 ms | 5,95 ms | 2,14 ms | 49,4 ms | 11,6 ms | 14,7 ms |
|
|
419
|
+
| **Immeuble · chauffage individuel** | 6,74 ms | 5,70 ms | 0,13 ms | 159,0 ms | 13,9 ms | 21,9 ms |
|
|
420
|
+
| **Appartement · chauffage collectif (2025)** | 4,52 ms | 4,20 ms | 1,58 ms | 39,5 ms | 7,25 ms | 9,05 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 5,77 ms | 4,91 ms | 0,13 ms | 131,8 ms | 10,7 ms | 18,8 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 5,79 ms | 4,72 ms | 0,21 ms | 581,4 ms | 12,2 ms | 19,9 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,62 ms | 5,72 ms | 1,79 ms | 58,3 ms | 12,5 ms | 20,3 ms |
|
|
424
424
|
|
|
425
425
|
<sub>La moyenne est tirée vers le haut par les DPE collectifs, dont le coût atteint plusieurs
|
|
426
426
|
dizaines de fois la médiane : c’est la médiane qui décrit le cas courant, et p95/p99 le cas
|
package/engine.js
CHANGED
|
@@ -33,7 +33,7 @@ import getFicheTechnique from './ficheTechnique.js';
|
|
|
33
33
|
import { ProductionENR, ratioProrataPvLogement } from './16.2_production_enr.js';
|
|
34
34
|
import DpeSanitizerService from './dpe-sanitizer.service.js';
|
|
35
35
|
|
|
36
|
-
const LIB_VERSION = '1.8.
|
|
36
|
+
const LIB_VERSION = '1.8.50';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -4,6 +4,11 @@ import { excel_to_js_exec } from '../../../../utils.js';
|
|
|
4
4
|
import { TvStore } from '../../../dpe/infrastructure/tv.store.js';
|
|
5
5
|
import { EmetteurChService } from './emetteur-ch.service.js';
|
|
6
6
|
import { TypeGenerateur } from '../../../dpe/domain/models/type-generateur.model.js';
|
|
7
|
+
import enums from '../../../../enums.js';
|
|
8
|
+
import {
|
|
9
|
+
anneeInstallationGenerateur,
|
|
10
|
+
pnEstimeeDimensionnement
|
|
11
|
+
} from '../../../../13.2_generateur_combustion_pdim.js';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Calcul des données de calcul pour chacun des générateurs
|
|
@@ -73,7 +78,12 @@ export class GenerateurChService {
|
|
|
73
78
|
if (generateurChauffage.donnee_utilisateur.combustion) {
|
|
74
79
|
// Calcul de la puissance nominale si non définie
|
|
75
80
|
if (!generateurChauffageDI.pn) {
|
|
76
|
-
generateurChauffage.donnee_intermediaire.pn = this.pn(
|
|
81
|
+
generateurChauffage.donnee_intermediaire.pn = this.pn(
|
|
82
|
+
ctx,
|
|
83
|
+
logement,
|
|
84
|
+
installationChauffage,
|
|
85
|
+
generateurChauffage
|
|
86
|
+
);
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
generateurChauffage.donnee_utilisateur.generateur = this.#chTvStore.getGenerateurCombustion(
|
|
@@ -201,11 +211,72 @@ export class GenerateurChService {
|
|
|
201
211
|
*
|
|
202
212
|
* @param ctx {Contexte}
|
|
203
213
|
* @param logement {Logement}
|
|
214
|
+
* @param installationChauffage {InstallationChauffage}
|
|
215
|
+
* @param generateurChauffage {GenerateurChauffage}
|
|
204
216
|
* @return {number}
|
|
205
217
|
*/
|
|
206
|
-
pn(ctx, logement) {
|
|
218
|
+
pn(ctx, logement, installationChauffage, generateurChauffage) {
|
|
207
219
|
const Tbase = this.#tvStore.getTempBase(ctx.altitude.id, ctx.zoneClimatique.id);
|
|
208
|
-
|
|
220
|
+
// §13.2.2.4 : générateur individuel n'alimentant qu'une partie du logement => prorata Sh_installation / Sh.
|
|
221
|
+
// Un générateur collectif dessert tout le bâtiment : pas de prorata.
|
|
222
|
+
const installationDE = installationChauffage?.donnee_entree;
|
|
223
|
+
const ratio =
|
|
224
|
+
String(installationDE?.enum_type_installation_id) === '1'
|
|
225
|
+
? installationDE.surface_chauffee / ctx.surfaceHabitable
|
|
226
|
+
: 1;
|
|
227
|
+
const prorata = ratio > 0 && ratio < 1 ? ratio : 1;
|
|
228
|
+
const pch =
|
|
229
|
+
(prorata * 1.2 * logement.sortie.deperdition.deperdition_enveloppe * (19 - Tbase)) /
|
|
230
|
+
0.95 ** 3;
|
|
231
|
+
|
|
232
|
+
// §13.2.2.4 : Pdim = max(Pch ; Pecs) puis table Pdim → Pn des chaudières (générateurs individuels)
|
|
233
|
+
if (String(installationDE?.enum_type_installation_id) !== '1' || !generateurChauffage) {
|
|
234
|
+
return pch;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const generateurDE = generateurChauffage.donnee_entree;
|
|
238
|
+
const usageMixte = String(generateurDE.enum_usage_generateur_id) === '3';
|
|
239
|
+
const libelleTypeGenerateur = enums.type_generateur_ch[generateurDE.enum_type_generateur_ch_id];
|
|
240
|
+
|
|
241
|
+
return pnEstimeeDimensionnement({
|
|
242
|
+
pch,
|
|
243
|
+
volumeStockage: usageMixte ? this.volumeStockageEcsMixte(logement, generateurDE) : undefined,
|
|
244
|
+
usageEcs: usageMixte,
|
|
245
|
+
libelleTypeGenerateur,
|
|
246
|
+
// Fiches techniques non disponibles dans la nouvelle architecture : pose murale inconnue
|
|
247
|
+
murale: undefined,
|
|
248
|
+
anneeInstallation: anneeInstallationGenerateur(
|
|
249
|
+
generateurDE.annee_installation,
|
|
250
|
+
ctx.anneeConstruction,
|
|
251
|
+
libelleTypeGenerateur
|
|
252
|
+
)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Volume de stockage (L) de la production d'ECS assurée par un générateur de chauffage mixte :
|
|
258
|
+
* générateur ECS mixte de même `reference_generateur_mixte`, sinon générateur ECS mixte unique.
|
|
259
|
+
*
|
|
260
|
+
* @param logement {Logement}
|
|
261
|
+
* @param generateurChauffageDE {GenerateurChauffageDE}
|
|
262
|
+
* @return {number|undefined}
|
|
263
|
+
*/
|
|
264
|
+
volumeStockageEcsMixte(logement, generateurChauffageDE) {
|
|
265
|
+
const generateursEcsMixtes = (logement.installation_ecs_collection?.installation_ecs || [])
|
|
266
|
+
.flatMap((installation) => installation.generateur_ecs_collection?.generateur_ecs || [])
|
|
267
|
+
.filter((generateur) => String(generateur.donnee_entree?.enum_usage_generateur_id) === '3');
|
|
268
|
+
|
|
269
|
+
const reference = generateurChauffageDE.reference_generateur_mixte;
|
|
270
|
+
const jumeau =
|
|
271
|
+
(reference !== undefined &&
|
|
272
|
+
reference !== null &&
|
|
273
|
+
generateursEcsMixtes.find(
|
|
274
|
+
(generateur) =>
|
|
275
|
+
String(generateur.donnee_entree.reference_generateur_mixte) === String(reference)
|
|
276
|
+
)) ||
|
|
277
|
+
(generateursEcsMixtes.length === 1 ? generateursEcsMixtes[0] : undefined);
|
|
278
|
+
|
|
279
|
+
return jumeau?.donnee_entree.volume_stockage;
|
|
209
280
|
}
|
|
210
281
|
|
|
211
282
|
/**
|
|
@@ -79,3 +79,153 @@ describe('Températures de fonctionnement des générateurs de chauffage', () =>
|
|
|
79
79
|
);
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Puissance nominale estimée d'un générateur à combustion sans pn.
|
|
85
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
|
|
86
|
+
*/
|
|
87
|
+
describe('Puissance nominale estimée des générateurs de chauffage (issue #264)', () => {
|
|
88
|
+
/** @type {TvStore} */
|
|
89
|
+
let tvStore;
|
|
90
|
+
const ctx = { altitude: { id: 1 }, zoneClimatique: { id: 1 }, surfaceHabitable: 100 };
|
|
91
|
+
const logement = { sortie: { deperdition: { deperdition_enveloppe: 200 } } };
|
|
92
|
+
// 1,2 × 200 × (19 − (−9)) / 0,95³ -- valeur de référence de régression
|
|
93
|
+
const PCH_LOGEMENT = 7837.877241580406;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
tvStore = new TvStore();
|
|
97
|
+
service = new GenerateurChService(new ChTvStore(), tvStore, new EmetteurChService());
|
|
98
|
+
vi.spyOn(tvStore, 'getTempBase').mockReturnValue(-9);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('installation desservant tout le logement : Pch sans prorata', () => {
|
|
102
|
+
const installation = { donnee_entree: { surface_chauffee: 100, enum_type_installation_id: 1 } };
|
|
103
|
+
expect(service.pn(ctx, logement, installation)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
104
|
+
expect(tvStore.getTempBase).toHaveBeenCalledWith(1, 1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('installation desservant 40 % du logement : Pch proratisée à la surface chauffée', () => {
|
|
108
|
+
const installation = { donnee_entree: { surface_chauffee: 40, enum_type_installation_id: 1 } };
|
|
109
|
+
expect(service.pn(ctx, logement, installation)).toBeCloseTo(0.4 * PCH_LOGEMENT, 9);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test.each([
|
|
113
|
+
['surface chauffée absente', { donnee_entree: { enum_type_installation_id: 1 } }],
|
|
114
|
+
[
|
|
115
|
+
'installation collective',
|
|
116
|
+
{ donnee_entree: { surface_chauffee: 40, enum_type_installation_id: 2 } }
|
|
117
|
+
],
|
|
118
|
+
['installation absente', undefined],
|
|
119
|
+
[
|
|
120
|
+
'surface chauffée supérieure à Sh',
|
|
121
|
+
{ donnee_entree: { surface_chauffee: 130, enum_type_installation_id: 1 } }
|
|
122
|
+
],
|
|
123
|
+
[
|
|
124
|
+
'surface chauffée nulle',
|
|
125
|
+
{ donnee_entree: { surface_chauffee: 0, enum_type_installation_id: 1 } }
|
|
126
|
+
]
|
|
127
|
+
])('%s : pas de prorata', (_libelle, installation) => {
|
|
128
|
+
expect(service.pn(ctx, logement, installation)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Puissance nominale estimée par la puissance de dimensionnement : Pdim = max(Pch ; Pecs) puis table
|
|
134
|
+
* Pdim → Pn des chaudières (le calcul de la table est testé dans 13.2_generateur_combustion_pdim.spec.js).
|
|
135
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
|
|
136
|
+
*/
|
|
137
|
+
describe('Puissance nominale estimée : Pdim et table Pdim → Pn (issue #271)', () => {
|
|
138
|
+
/** @type {TvStore} */
|
|
139
|
+
let tvStore;
|
|
140
|
+
const ctx = {
|
|
141
|
+
altitude: { id: 1 },
|
|
142
|
+
zoneClimatique: { id: 1 },
|
|
143
|
+
surfaceHabitable: 100,
|
|
144
|
+
anneeConstruction: 1975
|
|
145
|
+
};
|
|
146
|
+
const installation = { donnee_entree: { surface_chauffee: 100, enum_type_installation_id: 1 } };
|
|
147
|
+
const logementAvecEcs = (generateursEcs) => ({
|
|
148
|
+
sortie: { deperdition: { deperdition_enveloppe: 200 } },
|
|
149
|
+
installation_ecs_collection: {
|
|
150
|
+
installation_ecs: [{ generateur_ecs_collection: { generateur_ecs: generateursEcs } }]
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const generateur = (de) => ({ donnee_entree: de });
|
|
154
|
+
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
tvStore = new TvStore();
|
|
157
|
+
service = new GenerateurChService(new ChTvStore(), tvStore, new EmetteurChService());
|
|
158
|
+
vi.spyOn(tvStore, 'getTempBase').mockReturnValue(-9);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('chaudière de chauffage seul installée après 2015 : Pch = 7,84 kW → Pn = 10 kW', () => {
|
|
162
|
+
const logement = logementAvecEcs([]);
|
|
163
|
+
const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 1 });
|
|
164
|
+
expect(service.pn(ctx, logement, installation, gen)).toBe(10000);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('chaudière 1991-2000 (pose inconnue, avant 2006) : plancher 18 kW', () => {
|
|
168
|
+
const logement = logementAvecEcs([]);
|
|
169
|
+
const gen = generateur({ enum_type_generateur_ch_id: '88', enum_usage_generateur_id: 1 });
|
|
170
|
+
expect(service.pn(ctx, logement, installation, gen)).toBe(18000);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('chaudière mixte instantanée : Pdim = max(Pch ; 21 kW) → Pn = 24 kW', () => {
|
|
174
|
+
const logement = logementAvecEcs([
|
|
175
|
+
{ donnee_entree: { enum_usage_generateur_id: 2, volume_stockage: 300 } },
|
|
176
|
+
{ donnee_entree: { enum_usage_generateur_id: 3, volume_stockage: 0 } }
|
|
177
|
+
]);
|
|
178
|
+
const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 3 });
|
|
179
|
+
expect(service.pn(ctx, logement, installation, gen)).toBe(24000);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('radiateur gaz : Pn = Pdim (pas de table)', () => {
|
|
183
|
+
const logement = logementAvecEcs([]);
|
|
184
|
+
const gen = generateur({ enum_type_generateur_ch_id: '54', enum_usage_generateur_id: 1 });
|
|
185
|
+
expect(service.pn(ctx, logement, installation, gen)).toBeCloseTo(7837.877241580406, 9);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('installation collective : Pch conservée', () => {
|
|
189
|
+
const logement = logementAvecEcs([]);
|
|
190
|
+
const collective = { donnee_entree: { enum_type_installation_id: 2 } };
|
|
191
|
+
const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 1 });
|
|
192
|
+
expect(service.pn(ctx, logement, collective, gen)).toBeCloseTo(7837.877241580406, 9);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('volume de stockage du générateur ECS mixte jumeau (reference_generateur_mixte)', () => {
|
|
196
|
+
const ecs = (reference, volume) => ({
|
|
197
|
+
donnee_entree: {
|
|
198
|
+
enum_usage_generateur_id: '3',
|
|
199
|
+
reference_generateur_mixte: reference,
|
|
200
|
+
volume_stockage: volume
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
const logement = logementAvecEcs([ecs('A', 0), ecs('B', 200)]);
|
|
204
|
+
expect(service.volumeStockageEcsMixte(logement, { reference_generateur_mixte: 'B' })).toBe(200);
|
|
205
|
+
expect(service.volumeStockageEcsMixte(logement, {})).toBeUndefined();
|
|
206
|
+
expect(service.volumeStockageEcsMixte({}, {})).toBeUndefined();
|
|
207
|
+
expect(
|
|
208
|
+
service.volumeStockageEcsMixte(
|
|
209
|
+
{ installation_ecs_collection: { installation_ecs: [{}] } },
|
|
210
|
+
{ reference_generateur_mixte: 'B' }
|
|
211
|
+
)
|
|
212
|
+
).toBeUndefined();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('execute : le générateur est transmis à l’estimation de pn', () => {
|
|
216
|
+
vi.spyOn(service, 'pn').mockReturnValue(24000);
|
|
217
|
+
vi.spyOn(service, 'caracteristiques').mockReturnValue({});
|
|
218
|
+
const gen = {
|
|
219
|
+
donnee_entree: { enum_type_generateur_ch_id: '97', enum_lien_generateur_emetteur_id: 1 },
|
|
220
|
+
donnee_intermediaire: {}
|
|
221
|
+
};
|
|
222
|
+
const inst = {
|
|
223
|
+
donnee_entree: { enum_type_installation_id: 1 },
|
|
224
|
+
generateur_chauffage_collection: { generateur_chauffage: [gen] }
|
|
225
|
+
};
|
|
226
|
+
const logement = logementAvecEcs([]);
|
|
227
|
+
service.execute(ctx, logement, inst);
|
|
228
|
+
expect(service.pn).toHaveBeenCalledWith(ctx, logement, inst, gen);
|
|
229
|
+
expect(gen.donnee_intermediaire.pn).toBe(24000);
|
|
230
|
+
});
|
|
231
|
+
});
|