@open3cl/engine 1.8.47 → 1.8.49
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 +111 -3
- package/13.2_generateur_combustion.spec.js +208 -1
- package/13.2_generateur_combustion_ch.js +8 -2
- package/13.2_generateur_combustion_ch.spec.js +50 -0
- package/14_generateur_ecs.js +8 -2
- package/14_generateur_ecs.spec.js +33 -0
- package/9_chauffage.js +5 -1
- package/9_chauffage.spec.js +36 -0
- package/README.md +24 -24
- package/engine.js +1 -1
- package/features/engine/domain/ch/generateur-ch.service.js +18 -3
- package/features/engine/domain/ch/generateur-ch.service.spec.js +49 -0
- package/package.json +1 -1
|
@@ -122,6 +122,28 @@ export function caracteristiquesGenerateurCombustion(row, pn, ratio, E, F, type,
|
|
|
122
122
|
return caracteristiques;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Estimation de la puissance nominale (W) d'un générateur à combustion dont la puissance n'est pas connue :
|
|
127
|
+
* Pch = Sh1 / Shtot × 1,2 × GV × (19 − Tbase) / 0,95³
|
|
128
|
+
*
|
|
129
|
+
* Si le générateur n'alimente qu'une partie du logement, la puissance est proratisée à la surface desservie.
|
|
130
|
+
* Le ratio est borné à ]0 ; 1] : une surface absente, nulle ou supérieure à Sh ne modifie pas Pch.
|
|
131
|
+
*
|
|
132
|
+
* Remarque : la méthode ne prévoit cette estimation que pour les générateurs individuels. Pour un générateur
|
|
133
|
+
* collectif sans pn, la même formule (GV du logement, convention pn = Pe) est une extrapolation.
|
|
134
|
+
*
|
|
135
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4 Calcul des puissances nominales (p.90-91)
|
|
136
|
+
*
|
|
137
|
+
* @param GV {number} déperditions enveloppe + renouvellement d'air (W/K)
|
|
138
|
+
* @param tbase {number} température extérieure de base (°C)
|
|
139
|
+
* @param ratioSurface {number} Sh_installation / Sh
|
|
140
|
+
* @returns {number}
|
|
141
|
+
*/
|
|
142
|
+
export function pnEstimee(GV, tbase, ratioSurface = 1) {
|
|
143
|
+
const prorata = ratioSurface > 0 && ratioSurface < 1 ? ratioSurface : 1;
|
|
144
|
+
return (prorata * 1.2 * GV * (19 - tbase)) / 0.95 ** 3;
|
|
145
|
+
}
|
|
146
|
+
|
|
125
147
|
/**
|
|
126
148
|
* Si la méthode de saisie n'est pas "Valeur forfaitaire" mais "caractéristiques saisies"
|
|
127
149
|
* 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 +161,19 @@ export function caracteristiquesGenerateurCombustion(row, pn, ratio, E, F, type,
|
|
|
139
161
|
* @param GV {number} déperdition de l'enveloppe
|
|
140
162
|
* @param tbase {number} température de fonctionnement du générateur
|
|
141
163
|
* @param methodeSaisie {number} méthode de saisie des caractéristiques du générateur
|
|
164
|
+
* @param ratioSurface {number} part de la surface du logement desservie par le générateur
|
|
165
|
+
* (Sh_installation / Sh), utilisée pour proratiser la puissance nominale estimée (§13.2.2.4)
|
|
142
166
|
*/
|
|
143
|
-
export function tv_generateur_combustion(
|
|
167
|
+
export function tv_generateur_combustion(
|
|
168
|
+
dpe,
|
|
169
|
+
di,
|
|
170
|
+
de,
|
|
171
|
+
type,
|
|
172
|
+
GV,
|
|
173
|
+
tbase,
|
|
174
|
+
methodeSaisie,
|
|
175
|
+
ratioSurface = 1
|
|
176
|
+
) {
|
|
144
177
|
const typeGenerateurKey = `enum_type_generateur_${type}_id`;
|
|
145
178
|
let enumTypeGenerateurId = de[typeGenerateurKey];
|
|
146
179
|
let row;
|
|
@@ -207,7 +240,7 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
|
|
|
207
240
|
} else {
|
|
208
241
|
// Calcul de la puissance nominale si non définie
|
|
209
242
|
if (!di.pn) {
|
|
210
|
-
di.pn = (
|
|
243
|
+
di.pn = pnEstimee(GV, tbase, ratioSurface);
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
selectionLigne = (pn, ratio) => {
|
|
@@ -354,14 +387,89 @@ export function updateGenerateurCombustion(dpe, de, type) {
|
|
|
354
387
|
addInfosFromFichesTechniques(dpe, de, type);
|
|
355
388
|
}
|
|
356
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Valeur de la sous-fiche « Présence d'une veilleuse » → 1 (oui), 0 (non) ou undefined (NC, autre).
|
|
392
|
+
* Valeurs observées : « oui », « non », « Non », « NC », « Générateur sans veilleuse ».
|
|
393
|
+
* @param valeur {string|number|undefined}
|
|
394
|
+
* @returns {1 | 0 | undefined}
|
|
395
|
+
*/
|
|
396
|
+
export function valeurVeilleuse(valeur) {
|
|
397
|
+
const v = (valeur ?? '').toString().trim().toLowerCase();
|
|
398
|
+
if (v.startsWith('oui')) return 1;
|
|
399
|
+
if (v.startsWith('non') || v.includes('sans veilleuse') || v.startsWith('absence')) return 0;
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Présence d'une veilleuse sur un générateur à combustion, lue dans la sous-fiche technique
|
|
405
|
+
* « Présence d'une veilleuse » (catégorie 7 pour le chauffage, 8 pour l'ECS) du générateur.
|
|
406
|
+
*
|
|
407
|
+
* Le modèle ADEME n'a pas de donnée d'entrée « veilleuse » : la fiche technique est la seule trace
|
|
408
|
+
* de la saisie. Le moteur de référence (Tribu) prend cette information en entrée (`Is_veilleuse`,
|
|
409
|
+
* DPE.DataClasses/generateur.cs) et annule Pveil en son absence
|
|
410
|
+
* (DPE.Core/Calcul_generateur.cs, `Pveil1 = 0` si `Is_veilleuse != oui`).
|
|
411
|
+
*
|
|
412
|
+
* Une même fiche peut décrire plusieurs générateurs à la suite (ex. LICIEL : deux chaudières dans
|
|
413
|
+
* la même fiche de catégorie 7). getFicheTechnique() renverrait alors la veilleuse du premier
|
|
414
|
+
* générateur pour tous : la sous-fiche retenue est la première « veilleuse » qui suit la sous-fiche
|
|
415
|
+
* dont la valeur contient la description du générateur, avant le générateur suivant. En cas de
|
|
416
|
+
* réponses contradictoires (descriptions identiques) ou d'absence, la présence est non renseignée.
|
|
417
|
+
*
|
|
418
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.4 / §14.1 (« Pveil : si veilleuse »)
|
|
419
|
+
*
|
|
420
|
+
* @param dpe {FullDpe}
|
|
421
|
+
* @param de {Donnee_entree}
|
|
422
|
+
* @param type {'ch' | 'ecs'}
|
|
423
|
+
* @returns {1 | 0 | undefined} 1 si veilleuse, 0 si pas de veilleuse, undefined si non renseigné
|
|
424
|
+
*/
|
|
425
|
+
export function presenceVeilleuse(dpe, de, type) {
|
|
426
|
+
const description = (de.description ?? '').toString().toLowerCase();
|
|
427
|
+
if (!description) return undefined;
|
|
428
|
+
|
|
429
|
+
const categorie = type === 'ecs' ? '8' : '7';
|
|
430
|
+
const liste = (x) => (x === undefined || x === null ? [] : Array.isArray(x) ? x : [x]);
|
|
431
|
+
const valeurs = new Set();
|
|
432
|
+
|
|
433
|
+
for (const fiche of liste(dpe.fiche_technique_collection?.fiche_technique)) {
|
|
434
|
+
if (fiche?.enum_categorie_fiche_technique_id !== categorie) continue;
|
|
435
|
+
const sousFiches = liste(fiche.sous_fiche_technique_collection?.sous_fiche_technique);
|
|
436
|
+
const valeurMinuscule = (i) => (sousFiches[i]?.valeur ?? '').toString().toLowerCase();
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < sousFiches.length; i++) {
|
|
439
|
+
if (!valeurMinuscule(i).includes(description)) continue;
|
|
440
|
+
for (let j = i + 1; j < sousFiches.length; j++) {
|
|
441
|
+
const libelle = (sousFiches[j]?.description ?? '').toString().toLowerCase();
|
|
442
|
+
// début du générateur suivant
|
|
443
|
+
if (
|
|
444
|
+
valeurMinuscule(j).includes(description) ||
|
|
445
|
+
/^type (de )?g[ée]n[ée]rateur/.test(libelle)
|
|
446
|
+
) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
if (libelle.includes('veilleuse')) {
|
|
450
|
+
valeurs.add(valeurVeilleuse(sousFiches[j].valeur));
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return valeurs.size === 1 ? [...valeurs][0] : undefined;
|
|
458
|
+
}
|
|
459
|
+
|
|
357
460
|
/**
|
|
358
461
|
* Récupération d'informations complémentaires issues des fiches techniques pour les générateurs
|
|
359
462
|
*
|
|
360
463
|
* @param dpe {FullDpe}
|
|
361
464
|
* @param de {Donnee_entree}
|
|
362
|
-
* @param type {'ch'|'ecs'}
|
|
465
|
+
* @param type {'ch' | 'ecs'}
|
|
363
466
|
*/
|
|
364
467
|
function addInfosFromFichesTechniques(dpe, de, type) {
|
|
468
|
+
const veilleuse = presenceVeilleuse(dpe, de, type);
|
|
469
|
+
if (veilleuse !== undefined) {
|
|
470
|
+
de.presence_veilleuse = veilleuse;
|
|
471
|
+
}
|
|
472
|
+
|
|
365
473
|
// Récupération de la présence ou non d'un système de ventilation
|
|
366
474
|
// Fiches techniques : catégorie 7 pour le chauffage, catégorie 8 pour l'ECS
|
|
367
475
|
const ficheTechnique = getFicheTechnique(
|
|
@@ -64,9 +64,12 @@ const {
|
|
|
64
64
|
tv_generateur_combustion,
|
|
65
65
|
findGenerateurChMixteJumeau,
|
|
66
66
|
updateGenerateurCombustion,
|
|
67
|
+
presenceVeilleuse,
|
|
68
|
+
valeurVeilleuse,
|
|
67
69
|
caracteristiquesGenerateurCombustion,
|
|
68
70
|
puissanceFormuleQp0,
|
|
69
|
-
PN_MAX_QP0_CHAUDIERE_GAZ
|
|
71
|
+
PN_MAX_QP0_CHAUDIERE_GAZ,
|
|
72
|
+
pnEstimee
|
|
70
73
|
} = await import('./13.2_generateur_combustion.js');
|
|
71
74
|
const { tv, tvColumnLines } = await import('./utils.js');
|
|
72
75
|
const { conventionPuissanceNominale } = await import('./13.2_generateur_combustion_pn.js');
|
|
@@ -125,6 +128,22 @@ describe('tv_generateur_combustion - caractéristiques du générateur', () => {
|
|
|
125
128
|
expect(di.pn).toBe(12345);
|
|
126
129
|
});
|
|
127
130
|
|
|
131
|
+
test('puissance nominale absente, générateur desservant 40 % du logement : Pn proratisée (issue #264)', () => {
|
|
132
|
+
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
133
|
+
const di = {};
|
|
134
|
+
const de = { enum_type_generateur_ch_id: '89', ratio_virtualisation: 1, presence_ventouse: 0 };
|
|
135
|
+
tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1, 0.4);
|
|
136
|
+
expect(di.pn).toBeCloseTo(0.4 * 7837.877241580406, 9);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('puissance nominale renseignée : le prorata de surface ne s’applique pas', () => {
|
|
140
|
+
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
141
|
+
const di = { pn: 12345 };
|
|
142
|
+
const de = { enum_type_generateur_ch_id: '89', ratio_virtualisation: 1, presence_ventouse: 0 };
|
|
143
|
+
tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1, 0.4);
|
|
144
|
+
expect(di.pn).toBe(12345);
|
|
145
|
+
});
|
|
146
|
+
|
|
128
147
|
test('méthode forfaitaire (1) en chauffage : rpn, rpint, qp0 et pveil renseignés', () => {
|
|
129
148
|
tv.mockReturnValue({ ...ROW_DEFAUT });
|
|
130
149
|
const di = { pn: 20000 };
|
|
@@ -805,6 +824,20 @@ describe('tv_generateur_combustion - génération mixte avec plusieurs générat
|
|
|
805
824
|
});
|
|
806
825
|
});
|
|
807
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Construit un DPE avec des fiches techniques (catégorie, liste [description, valeur]).
|
|
829
|
+
*/
|
|
830
|
+
const dpeFiches = (...fiches) => ({
|
|
831
|
+
fiche_technique_collection: {
|
|
832
|
+
fiche_technique: fiches.map(([categorie, sousFiches]) => ({
|
|
833
|
+
enum_categorie_fiche_technique_id: categorie,
|
|
834
|
+
sous_fiche_technique_collection: {
|
|
835
|
+
sous_fiche_technique: sousFiches.map(([description, valeur]) => ({ description, valeur }))
|
|
836
|
+
}
|
|
837
|
+
}))
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
808
841
|
/**
|
|
809
842
|
* 13.2 - Orchestration des substitutions de générateurs et enrichissement fiches techniques.
|
|
810
843
|
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2
|
|
@@ -887,6 +920,28 @@ describe('updateGenerateurCombustion - orchestration des substitutions', () => {
|
|
|
887
920
|
updateGenerateurCombustion({}, deInconnu, 'ch');
|
|
888
921
|
expect(deInconnu.ventilateurCombustion).toBeUndefined();
|
|
889
922
|
});
|
|
923
|
+
|
|
924
|
+
test('présence de veilleuse lue dans la fiche technique : presence_veilleuse renseigné', () => {
|
|
925
|
+
getFicheTechnique.mockReturnValue(null);
|
|
926
|
+
const dpe = dpeFiches([
|
|
927
|
+
'8',
|
|
928
|
+
[
|
|
929
|
+
['Type générateur', 'Chauffe-eau gaz'],
|
|
930
|
+
["Présence d'une veilleuse", 'oui']
|
|
931
|
+
]
|
|
932
|
+
]);
|
|
933
|
+
const de = { description: 'Chauffe-eau gaz' };
|
|
934
|
+
updateGenerateurCombustion(dpe, de, 'ecs');
|
|
935
|
+
expect(de.presence_veilleuse).toBe(1);
|
|
936
|
+
expect(de.presenceVentilateur).toBeUndefined();
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
test('fiche veilleuse absente : presence_veilleuse non renseigné', () => {
|
|
940
|
+
getFicheTechnique.mockReturnValue(null);
|
|
941
|
+
const de = { description: 'gen' };
|
|
942
|
+
updateGenerateurCombustion({}, de, 'ch');
|
|
943
|
+
expect(de).not.toHaveProperty('presence_veilleuse');
|
|
944
|
+
});
|
|
890
945
|
});
|
|
891
946
|
|
|
892
947
|
/**
|
|
@@ -937,6 +992,126 @@ describe('tv_generateur_combustion - coefficients (E, F) selon le ventilateur de
|
|
|
937
992
|
});
|
|
938
993
|
});
|
|
939
994
|
|
|
995
|
+
/**
|
|
996
|
+
* Issue #39 : présence d'une veilleuse lue dans la sous-fiche technique du générateur.
|
|
997
|
+
* Tribu : entrée `Is_veilleuse` (DPE.DataClasses/generateur.cs), Pveil1 = 0 si absente
|
|
998
|
+
* (DPE.Core/Calcul_generateur.cs).
|
|
999
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.4 / §14.1
|
|
1000
|
+
*/
|
|
1001
|
+
describe('presenceVeilleuse - fiche technique « Présence d’une veilleuse »', () => {
|
|
1002
|
+
const ficheGen = (desc, veilleuse) => [
|
|
1003
|
+
['Type générateur', desc],
|
|
1004
|
+
["Année d'installation", '1995'],
|
|
1005
|
+
...(veilleuse === undefined ? [] : [['Présence d’une veilleuse', veilleuse]])
|
|
1006
|
+
];
|
|
1007
|
+
|
|
1008
|
+
test.each([
|
|
1009
|
+
['oui', 1],
|
|
1010
|
+
[' Oui ', 1],
|
|
1011
|
+
['non', 0],
|
|
1012
|
+
['Non', 0],
|
|
1013
|
+
['Générateur sans veilleuse', 0],
|
|
1014
|
+
['Absence de veilleuse', 0],
|
|
1015
|
+
['NC', undefined],
|
|
1016
|
+
['', undefined],
|
|
1017
|
+
[undefined, undefined]
|
|
1018
|
+
])('valeur « %s » -> %s', (valeur, attendu) => {
|
|
1019
|
+
expect(valeurVeilleuse(valeur)).toBe(attendu);
|
|
1020
|
+
const dpe = dpeFiches([
|
|
1021
|
+
'7',
|
|
1022
|
+
[
|
|
1023
|
+
['Type générateur', 'Chaudière'],
|
|
1024
|
+
['Présence d’une veilleuse', valeur]
|
|
1025
|
+
]
|
|
1026
|
+
]);
|
|
1027
|
+
expect(presenceVeilleuse(dpe, { description: 'Chaudière' }, 'ch')).toBe(attendu);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
test('catégorie 7 pour le chauffage, 8 pour l’ECS', () => {
|
|
1031
|
+
const dpe = dpeFiches(
|
|
1032
|
+
['7', ficheGen('Chaudière gaz', 'oui')],
|
|
1033
|
+
['8', ficheGen('Chaudière gaz', 'non')]
|
|
1034
|
+
);
|
|
1035
|
+
expect(presenceVeilleuse(dpe, { description: 'Chaudière gaz' }, 'ch')).toBe(1);
|
|
1036
|
+
expect(presenceVeilleuse(dpe, { description: 'Chaudière gaz' }, 'ecs')).toBe(0);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
test('plusieurs générateurs dans la même fiche : veilleuse du générateur décrit', () => {
|
|
1040
|
+
// cas réel 2551E0134306T (LICIEL) : condensation « non » puis standard « oui »
|
|
1041
|
+
const dpe = dpeFiches([
|
|
1042
|
+
'7',
|
|
1043
|
+
[
|
|
1044
|
+
...ficheGen('Gaz Naturel - Chaudière gaz à condensation', 'non'),
|
|
1045
|
+
...ficheGen('Gaz Naturel - Chaudière gaz standard', 'oui')
|
|
1046
|
+
]
|
|
1047
|
+
]);
|
|
1048
|
+
expect(
|
|
1049
|
+
presenceVeilleuse(dpe, { description: 'Gaz Naturel - Chaudière gaz standard' }, 'ch')
|
|
1050
|
+
).toBe(1);
|
|
1051
|
+
expect(
|
|
1052
|
+
presenceVeilleuse(dpe, { description: 'Gaz Naturel - Chaudière gaz à condensation' }, 'ch')
|
|
1053
|
+
).toBe(0);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test('générateur sans ligne veilleuse : pas de reprise de celle du générateur suivant', () => {
|
|
1057
|
+
const dpe = dpeFiches([
|
|
1058
|
+
'7',
|
|
1059
|
+
[...ficheGen('Poêle bûche'), ...ficheGen('Chaudière standard', 'oui')]
|
|
1060
|
+
]);
|
|
1061
|
+
expect(presenceVeilleuse(dpe, { description: 'Poêle bûche' }, 'ch')).toBeUndefined();
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
test('même description répétée avec des réponses contradictoires : non renseigné', () => {
|
|
1065
|
+
// cas réel 2567E1314804D : deux « Chaudière standard », l'une avec, l'autre sans veilleuse
|
|
1066
|
+
const dpe = dpeFiches([
|
|
1067
|
+
'7',
|
|
1068
|
+
[...ficheGen('Chaudière standard', 'Oui'), ...ficheGen('Chaudière standard', 'Non')]
|
|
1069
|
+
]);
|
|
1070
|
+
expect(presenceVeilleuse(dpe, { description: 'Chaudière standard' }, 'ch')).toBeUndefined();
|
|
1071
|
+
const dpe2 = dpeFiches(
|
|
1072
|
+
['7', ficheGen('Chaudière standard', 'Oui')],
|
|
1073
|
+
['7', ficheGen('Chaudière standard', 'Oui')]
|
|
1074
|
+
);
|
|
1075
|
+
expect(presenceVeilleuse(dpe2, { description: 'Chaudière standard' }, 'ch')).toBe(1);
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test('description absente, fiche absente ou générateur non décrit : non renseigné', () => {
|
|
1079
|
+
const dpe = dpeFiches(['7', ficheGen('Chaudière', 'oui')], ['2', [['x', 'Chaudière']]]);
|
|
1080
|
+
expect(presenceVeilleuse(dpe, {}, 'ch')).toBeUndefined();
|
|
1081
|
+
expect(presenceVeilleuse({}, { description: 'Chaudière' }, 'ch')).toBeUndefined();
|
|
1082
|
+
expect(presenceVeilleuse(dpe, { description: 'Poêle' }, 'ch')).toBeUndefined();
|
|
1083
|
+
const fichesUniques = {
|
|
1084
|
+
fiche_technique_collection: {
|
|
1085
|
+
fiche_technique: {
|
|
1086
|
+
enum_categorie_fiche_technique_id: '7',
|
|
1087
|
+
sous_fiche_technique_collection: {
|
|
1088
|
+
sous_fiche_technique: { description: 'Type', valeur: 'Chaudière' }
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
expect(presenceVeilleuse(fichesUniques, { description: 'Chaudière' }, 'ch')).toBeUndefined();
|
|
1094
|
+
const sansSousFiche = {
|
|
1095
|
+
fiche_technique_collection: {
|
|
1096
|
+
fiche_technique: [null, { enum_categorie_fiche_technique_id: '7' }]
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
expect(presenceVeilleuse(sansSousFiche, { description: 'Chaudière' }, 'ch')).toBeUndefined();
|
|
1100
|
+
const sousFichesVides = dpeFiches([
|
|
1101
|
+
'7',
|
|
1102
|
+
[
|
|
1103
|
+
['Type', 'Chaudière'],
|
|
1104
|
+
[undefined, undefined],
|
|
1105
|
+
[undefined, 'x']
|
|
1106
|
+
]
|
|
1107
|
+
]);
|
|
1108
|
+
sousFichesVides.fiche_technique_collection.fiche_technique[0].sous_fiche_technique_collection.sous_fiche_technique.push(
|
|
1109
|
+
null
|
|
1110
|
+
);
|
|
1111
|
+
expect(presenceVeilleuse(sousFichesVides, { description: 'Chaudière' }, 'ch')).toBeUndefined();
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
|
|
940
1115
|
/**
|
|
941
1116
|
* Issue #124 : générateur collectif virtualisé, puissance saisie collective (Pn) ou individualisée
|
|
942
1117
|
* (Pe = a × Pn) selon les logiciels.
|
|
@@ -1272,3 +1447,35 @@ describe('puissanceFormuleQp0 - plafond de Pn à 400 kW pour les chaudières gaz
|
|
|
1272
1447
|
expect(qp0).toBeCloseTo(127.5468019078, 6);
|
|
1273
1448
|
});
|
|
1274
1449
|
});
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Estimation de la puissance nominale d'un générateur à combustion (pn absente).
|
|
1453
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
|
|
1454
|
+
*/
|
|
1455
|
+
describe('pnEstimee - puissance nominale estimée (issue #264)', () => {
|
|
1456
|
+
// 1,2 × 200 × (19 − (−9)) / 0,95³ -- valeur de référence de régression
|
|
1457
|
+
const PCH_LOGEMENT = 7837.877241580406;
|
|
1458
|
+
|
|
1459
|
+
test('générateur desservant tout le logement : Pch = 1,2 × GV × (19 − Tbase) / 0,95³', () => {
|
|
1460
|
+
expect(pnEstimee(200, -9)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1461
|
+
expect(pnEstimee(200, -9, 1)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
test('générateur desservant une partie du logement : Pch1 = Sh1 / Shtot × Pch', () => {
|
|
1465
|
+
expect(pnEstimee(200, -9, 0.25)).toBeCloseTo(0.25 * PCH_LOGEMENT, 9);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
test('deux générateurs se partageant le logement : Pch1 + Pch2 = Pch', () => {
|
|
1469
|
+
expect(pnEstimee(200, -9, 0.3) + pnEstimee(200, -9, 0.7)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
test.each([
|
|
1473
|
+
['nul', 0],
|
|
1474
|
+
['négatif', -0.5],
|
|
1475
|
+
['supérieur à 1', 1.4],
|
|
1476
|
+
['non numérique', NaN],
|
|
1477
|
+
['absent', undefined]
|
|
1478
|
+
])('ratio %s : pas de prorata (Pch du logement)', (_libelle, ratio) => {
|
|
1479
|
+
expect(pnEstimee(200, -9, ratio)).toBeCloseTo(PCH_LOGEMENT, 9);
|
|
1480
|
+
});
|
|
1481
|
+
});
|
|
@@ -377,8 +377,14 @@ export function calc_generateur_combustion_ch(dpe, di, de, du) {
|
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
/**
|
|
381
|
+
* La puissance de la veilleuse n'est à prendre en compte que si une veilleuse est présente.
|
|
382
|
+
* Présence lue dans la fiche technique du générateur (de.presence_veilleuse, cf.
|
|
383
|
+
* presenceVeilleuse()) ; à défaut, déduite de la valeur intermédiaire pveilleuse du DPE.
|
|
384
|
+
* Tribu : DPE.Core/Calcul_generateur.cs, Pveil1 = 0 si Is_veilleuse != oui.
|
|
385
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.4
|
|
386
|
+
*/
|
|
387
|
+
if (de.presence_veilleuse === 0 || (de.presence_veilleuse !== 1 && !di.pveilleuse)) {
|
|
382
388
|
di.pveil = 0;
|
|
383
389
|
}
|
|
384
390
|
|
|
@@ -857,6 +857,56 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
|
|
|
857
857
|
expect(avecVeilleuse.rg).toBeLessThan((k * pmfou) / (pmcons + 0.15));
|
|
858
858
|
});
|
|
859
859
|
|
|
860
|
+
/**
|
|
861
|
+
* Issue #39 : la présence de veilleuse lue dans la fiche technique (de.presence_veilleuse)
|
|
862
|
+
* prime sur la valeur intermédiaire pveilleuse du DPE. Sans fiche, comportement inchangé.
|
|
863
|
+
* Tribu : Pveil1 = 0 si Is_veilleuse != oui (DPE.Core/Calcul_generateur.cs).
|
|
864
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.4
|
|
865
|
+
*/
|
|
866
|
+
describe('présence de veilleuse (issue #39)', () => {
|
|
867
|
+
const di = (extra) => ({
|
|
868
|
+
pn: 24000,
|
|
869
|
+
rpn: 0.87,
|
|
870
|
+
rpint: 0.84,
|
|
871
|
+
qp0: 0,
|
|
872
|
+
pveil: 150,
|
|
873
|
+
temp_fonc_30: 56,
|
|
874
|
+
temp_fonc_100: 70,
|
|
875
|
+
...extra
|
|
876
|
+
});
|
|
877
|
+
const de = (extra) => ({
|
|
878
|
+
enum_type_generateur_ch_id: '86',
|
|
879
|
+
enum_type_energie_id: '2',
|
|
880
|
+
type_energie: 'gaz naturel',
|
|
881
|
+
presence_regulation_combustion: true,
|
|
882
|
+
description: 't',
|
|
883
|
+
...extra
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test('fiche « oui » sans pveilleuse exportée : Pveil de la table conservé', () => {
|
|
887
|
+
const d = di();
|
|
888
|
+
calc_generateur_combustion_ch({}, d, de({ presence_veilleuse: 1 }), { cdimref: 3 });
|
|
889
|
+
expect(d.pveil).toBe(150);
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test('fiche « non » avec pveilleuse exportée : Pveil annulé', () => {
|
|
893
|
+
const d = di({ pveilleuse: 150 });
|
|
894
|
+
calc_generateur_combustion_ch({}, d, de({ presence_veilleuse: 0 }), { cdimref: 3 });
|
|
895
|
+
expect(d.pveil).toBe(0);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test('pas de fiche : Pveil conservé si pveilleuse exportée, annulé sinon', () => {
|
|
899
|
+
const avec = di({ pveilleuse: 150 });
|
|
900
|
+
calc_generateur_combustion_ch({}, avec, de(), { cdimref: 3 });
|
|
901
|
+
expect(avec.pveil).toBe(150);
|
|
902
|
+
const sans = di();
|
|
903
|
+
calc_generateur_combustion_ch({}, sans, de(), { cdimref: 3 });
|
|
904
|
+
expect(sans.pveil).toBe(0);
|
|
905
|
+
// la veilleuse dégrade le rendement
|
|
906
|
+
expect(avec.rg).toBeLessThan(sans.rg);
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
860
910
|
test('les pertes à charge nulle QP0 réduisent le rendement de génération', () => {
|
|
861
911
|
const di = () => ({
|
|
862
912
|
pn: 23000,
|
package/14_generateur_ecs.js
CHANGED
|
@@ -391,8 +391,14 @@ export default function calc_gen_ecs(dpe, gen_ecs, ecs_di, ecs_de, GV, ca_id, zc
|
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
/**
|
|
395
|
+
* La puissance de la veilleuse n'est à prendre en compte que si une veilleuse est présente.
|
|
396
|
+
* Présence lue dans la fiche technique du générateur (de.presence_veilleuse, cf.
|
|
397
|
+
* presenceVeilleuse()) ; à défaut, déduite de la valeur intermédiaire pveilleuse du DPE.
|
|
398
|
+
* Tribu : DPE.Core/Calcul_generateur.cs, Pveil1 = 0 si Is_veilleuse != oui.
|
|
399
|
+
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §14.1
|
|
400
|
+
*/
|
|
401
|
+
if (de.presence_veilleuse === 0 || (de.presence_veilleuse !== 1 && !di.pveilleuse)) {
|
|
396
402
|
di.pveil = 0;
|
|
397
403
|
}
|
|
398
404
|
|
|
@@ -548,6 +548,39 @@ describe('calc_gen_ecs - consommation par générateur', () => {
|
|
|
548
548
|
expect(g.donnee_intermediaire.conso_ecs).toBeCloseTo(322.3456790123457, 9);
|
|
549
549
|
});
|
|
550
550
|
|
|
551
|
+
/**
|
|
552
|
+
* Issue #39 : présence de veilleuse lue dans la fiche technique (de.presence_veilleuse).
|
|
553
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §14.1
|
|
554
|
+
*/
|
|
555
|
+
test.each([
|
|
556
|
+
[1, undefined, 0.02],
|
|
557
|
+
[0, 1, 0],
|
|
558
|
+
[undefined, 1, 0.02],
|
|
559
|
+
[undefined, undefined, 0]
|
|
560
|
+
])(
|
|
561
|
+
'présence de veilleuse (fiche %s, pveilleuse %s) : pveil = %s',
|
|
562
|
+
(presenceVeilleuse, pveilleuse, attendu) => {
|
|
563
|
+
vi.mocked(tvColumnIDs).mockImplementation((table) =>
|
|
564
|
+
table === 'generateur_combustion' ? ['chaud'] : []
|
|
565
|
+
);
|
|
566
|
+
vi.mocked(tv_generateur_combustion).mockImplementation((dpe, di) => {
|
|
567
|
+
di.rpn = 0.9;
|
|
568
|
+
di.qp0 = 100;
|
|
569
|
+
di.pn = 20000;
|
|
570
|
+
di.pveil = 0.02;
|
|
571
|
+
di.pveilleuse = pveilleuse;
|
|
572
|
+
});
|
|
573
|
+
const g = gen({
|
|
574
|
+
type_energie: 'gaz',
|
|
575
|
+
enum_type_generateur_ecs_id: 'chaud',
|
|
576
|
+
enum_methode_saisie_carac_sys_id: '1',
|
|
577
|
+
presence_veilleuse: presenceVeilleuse
|
|
578
|
+
});
|
|
579
|
+
calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
|
|
580
|
+
expect(g.donnee_intermediaire.pveil).toBe(attendu);
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
|
|
551
584
|
test('chaudière combustion avec stockage (Qgw > 0) : rendement génération + stockage', () => {
|
|
552
585
|
vi.mocked(tvColumnIDs).mockImplementation((table) =>
|
|
553
586
|
table === 'generateur_combustion' ? ['chaud'] : []
|
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,28 +370,28 @@ 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.48`** · branche `main` · généré le 2026-10-12
|
|
374
374
|
> Seuil de tolérance **5%**
|
|
375
375
|
|
|
376
376
|
<table>
|
|
377
377
|
<tr>
|
|
378
378
|
<td align="center"><strong>9</strong><br/><sub>corpus</sub></td>
|
|
379
379
|
<td align="center"><strong>89 984</strong><br/><sub>DPE analysés</sub></td>
|
|
380
|
-
<td align="center"><strong>62
|
|
381
|
-
<td align="center"><strong>69,
|
|
380
|
+
<td align="center"><strong>62 220</strong><br/><sub>DPE conformes</sub></td>
|
|
381
|
+
<td align="center"><strong>69,15 %</strong><br/><sub>réussite globale</sub></td>
|
|
382
382
|
</tr>
|
|
383
383
|
</table>
|
|
384
384
|
|
|
385
385
|
| | Corpus | Réussite | | DPE conformes |
|
|
386
386
|
| :-: | :---------------------------------------------------------------------------------------------------------------------- | ----------: | :--------------------- | -------------: |
|
|
387
|
-
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,
|
|
388
|
-
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,
|
|
389
|
-
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,
|
|
390
|
-
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,
|
|
391
|
-
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,
|
|
392
|
-
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,
|
|
393
|
-
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **66,
|
|
394
|
-
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **54,
|
|
387
|
+
| 🔴 | **Généraliste**<br/><sub>`corpus_dpe.csv`</sub> | **46,45 %** | `█████████░░░░░░░░░░░` | 4 645 / 9 999 |
|
|
388
|
+
| 🟢 | **Appartement · chauffage individuel (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_individuel_2025.csv`</sub> | **92,84 %** | `███████████████████░` | 9 284 / 10 000 |
|
|
389
|
+
| 🟢 | **Logement individuel (2025)**<br/><sub>`dpe_logement_individuel_2025.csv`</sub> | **89,93 %** | `██████████████████░░` | 8 986 / 9 992 |
|
|
390
|
+
| 🟢 | **Maison individuelle (2025)**<br/><sub>`dpe_maison_individuelle_2025.csv`</sub> | **88,83 %** | `██████████████████░░` | 8 882 / 9 999 |
|
|
391
|
+
| 🟡 | **Immeuble · chauffage individuel**<br/><sub>`dpe_immeuble_chauffage_individuel.csv`</sub> | **84,51 %** | `█████████████████░░░` | 8 449 / 9 998 |
|
|
392
|
+
| 🟡 | **Appartement · chauffage collectif (2025)**<br/><sub>`dpe_appartement_individuel_chauffage_collectif_2025.csv`</sub> | **71,71 %** | `██████████████░░░░░░` | 7 171 / 10 000 |
|
|
393
|
+
| 🟡 | **Immeuble · chauffage collectif**<br/><sub>`dpe_immeuble_chauffage_collectif.csv`</sub> | **66,41 %** | `█████████████░░░░░░░` | 6 640 / 9 999 |
|
|
394
|
+
| 🔴 | **Immeuble · chauffage mixte**<br/><sub>`dpe_immeuble_chauffage_mixte.csv`</sub> | **54,15 %** | `███████████░░░░░░░░░` | 5 413 / 9 997 |
|
|
395
395
|
| 🔴 | **Individuel généré depuis l'immeuble (2026)**<br/><sub>`dpe_individuel_a_partir_dpe_immeuble_2026.csv`</sub> | **27,50 %** | `██████░░░░░░░░░░░░░░` | 2 750 / 10 000 |
|
|
396
396
|
|
|
397
397
|
<sub>🟢 ≥ 85 % · 🟡 ≥ 60 % · 🔴 < 60 %</sub>
|
|
@@ -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>5,
|
|
408
|
-
<td align="center"><strong>0,
|
|
409
|
-
<td align="center"><strong>
|
|
406
|
+
<td align="center"><strong>5,92 ms</strong><br/><sub>moyenne</sub></td>
|
|
407
|
+
<td align="center"><strong>5,25 ms</strong><br/><sub>médiane</sub></td>
|
|
408
|
+
<td align="center"><strong>0,11 ms</strong><br/><sub>min</sub></td>
|
|
409
|
+
<td align="center"><strong>590,9 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)** |
|
|
418
|
-
| **Maison individuelle (2025)** | 6,
|
|
419
|
-
| **Immeuble · chauffage individuel** |
|
|
420
|
-
| **Appartement · chauffage collectif (2025)** |
|
|
421
|
-
| **Immeuble · chauffage collectif** | 6,
|
|
422
|
-
| **Immeuble · chauffage mixte** |
|
|
423
|
-
| **Individuel généré depuis l'immeuble (2026)** |
|
|
415
|
+
| **Généraliste** | 4,99 ms | 4,39 ms | 0,21 ms | 95,5 ms | 9,05 ms | 14,6 ms |
|
|
416
|
+
| **Appartement · chauffage individuel (2025)** | 4,42 ms | 4,12 ms | 1,64 ms | 30,2 ms | 7,40 ms | 9,25 ms |
|
|
417
|
+
| **Logement individuel (2025)** | 5,71 ms | 5,18 ms | 1,70 ms | 46,8 ms | 10,4 ms | 14,1 ms |
|
|
418
|
+
| **Maison individuelle (2025)** | 6,69 ms | 6,10 ms | 1,94 ms | 58,0 ms | 11,7 ms | 15,7 ms |
|
|
419
|
+
| **Immeuble · chauffage individuel** | 7,25 ms | 6,20 ms | 0,11 ms | 141,9 ms | 14,6 ms | 23,6 ms |
|
|
420
|
+
| **Appartement · chauffage collectif (2025)** | 4,82 ms | 4,57 ms | 1,68 ms | 37,0 ms | 7,67 ms | 9,89 ms |
|
|
421
|
+
| **Immeuble · chauffage collectif** | 6,49 ms | 5,77 ms | 0,15 ms | 116,1 ms | 11,7 ms | 20,1 ms |
|
|
422
|
+
| **Immeuble · chauffage mixte** | 6,05 ms | 4,97 ms | 0,22 ms | 590,9 ms | 12,7 ms | 20,7 ms |
|
|
423
|
+
| **Individuel généré depuis l'immeuble (2026)** | 6,87 ms | 5,95 ms | 1,71 ms | 72,7 ms | 13,3 ms | 22,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.49';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -73,7 +73,11 @@ export class GenerateurChService {
|
|
|
73
73
|
if (generateurChauffage.donnee_utilisateur.combustion) {
|
|
74
74
|
// Calcul de la puissance nominale si non définie
|
|
75
75
|
if (!generateurChauffageDI.pn) {
|
|
76
|
-
generateurChauffage.donnee_intermediaire.pn = this.pn(
|
|
76
|
+
generateurChauffage.donnee_intermediaire.pn = this.pn(
|
|
77
|
+
ctx,
|
|
78
|
+
logement,
|
|
79
|
+
installationChauffage
|
|
80
|
+
);
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
generateurChauffage.donnee_utilisateur.generateur = this.#chTvStore.getGenerateurCombustion(
|
|
@@ -201,11 +205,22 @@ export class GenerateurChService {
|
|
|
201
205
|
*
|
|
202
206
|
* @param ctx {Contexte}
|
|
203
207
|
* @param logement {Logement}
|
|
208
|
+
* @param installationChauffage {InstallationChauffage}
|
|
204
209
|
* @return {number}
|
|
205
210
|
*/
|
|
206
|
-
pn(ctx, logement) {
|
|
211
|
+
pn(ctx, logement, installationChauffage) {
|
|
207
212
|
const Tbase = this.#tvStore.getTempBase(ctx.altitude.id, ctx.zoneClimatique.id);
|
|
208
|
-
|
|
213
|
+
// §13.2.2.4 : générateur individuel n'alimentant qu'une partie du logement => prorata Sh_installation / Sh.
|
|
214
|
+
// Un générateur collectif dessert tout le bâtiment : pas de prorata.
|
|
215
|
+
const installationDE = installationChauffage?.donnee_entree;
|
|
216
|
+
const ratio =
|
|
217
|
+
String(installationDE?.enum_type_installation_id) === '1'
|
|
218
|
+
? installationDE.surface_chauffee / ctx.surfaceHabitable
|
|
219
|
+
: 1;
|
|
220
|
+
const prorata = ratio > 0 && ratio < 1 ? ratio : 1;
|
|
221
|
+
return (
|
|
222
|
+
(prorata * 1.2 * logement.sortie.deperdition.deperdition_enveloppe * (19 - Tbase)) / 0.95 ** 3
|
|
223
|
+
);
|
|
209
224
|
}
|
|
210
225
|
|
|
211
226
|
/**
|
|
@@ -79,3 +79,52 @@ 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
|
+
});
|