@open3cl/engine 1.8.7 → 1.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/13.2_generateur_combustion.js +116 -31
- package/13.2_generateur_combustion.spec.js +286 -2
- package/13.2_generateur_combustion_pn.js +248 -0
- package/13.2_generateur_combustion_pn.spec.js +476 -0
- package/6.2_surface_sud_equivalente.js +77 -6
- package/6.2_surface_sud_equivalente.spec.js +138 -2
- package/README.md +22 -22
- package/conso.js +178 -4
- package/conso.spec.js +187 -1
- package/engine.js +7 -4
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.js +136 -70
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +280 -0
- package/features/engine/domain/engine.service.js +4 -1
- package/features/engine/domain/engine.service.spec.js +14 -0
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.js +8 -7
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +230 -0
- package/package.json +1 -1
- package/utils.js +5 -0
- package/utils.spec.js +53 -0
package/engine.js
CHANGED
|
@@ -33,7 +33,7 @@ import getFicheTechnique from './ficheTechnique.js';
|
|
|
33
33
|
import { ProductionENR } from './16.2_production_enr.js';
|
|
34
34
|
import DpeSanitizerService from './dpe-sanitizer.service.js';
|
|
35
35
|
|
|
36
|
-
const LIB_VERSION = '1.8.
|
|
36
|
+
const LIB_VERSION = '1.8.9';
|
|
37
37
|
|
|
38
38
|
const dpeSanitizerService = new DpeSanitizerService();
|
|
39
39
|
|
|
@@ -573,7 +573,8 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
573
573
|
prorataECS,
|
|
574
574
|
prorataChauffage,
|
|
575
575
|
dateDpe,
|
|
576
|
-
coef_ep
|
|
576
|
+
coef_ep,
|
|
577
|
+
th === 'immeuble' ? Nb_lgt : 1
|
|
577
578
|
);
|
|
578
579
|
|
|
579
580
|
const production_electricite = productionENR.calculateEnr(
|
|
@@ -595,7 +596,8 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
595
596
|
prorataECS,
|
|
596
597
|
prorataChauffage,
|
|
597
598
|
dateDpe,
|
|
598
|
-
coef_ep_1_7
|
|
599
|
+
coef_ep_1_7,
|
|
600
|
+
th === 'immeuble' ? Nb_lgt : 1
|
|
599
601
|
);
|
|
600
602
|
|
|
601
603
|
productionENR.calculateEnr(
|
|
@@ -618,7 +620,8 @@ export function calcul_3cl(inputDpe, options) {
|
|
|
618
620
|
prorataECS,
|
|
619
621
|
prorataChauffage,
|
|
620
622
|
dateDpe,
|
|
621
|
-
coef_ep_2_3
|
|
623
|
+
coef_ep_2_3,
|
|
624
|
+
th === 'immeuble' ? Nb_lgt : 1
|
|
622
625
|
);
|
|
623
626
|
|
|
624
627
|
productionENR.calculateEnr(
|
|
@@ -42,88 +42,154 @@ export class SurfaceSudEquivalenteService {
|
|
|
42
42
|
*/
|
|
43
43
|
ssdMois(ctx, enveloppe, mois) {
|
|
44
44
|
const baiesVitrees = enveloppe.baie_vitree_collection?.baie_vitree || [];
|
|
45
|
-
|
|
45
|
+
const zc = ctx.zoneClimatique.id;
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Baies vitrées donnant directement sur l'extérieur
|
|
49
|
+
*/
|
|
50
|
+
const sseBaiesExt = baiesVitrees
|
|
51
|
+
.filter((bv) => parseInt(bv.donnee_entree.enum_type_adjacence_id) === 1)
|
|
52
|
+
.reduce((acc, bv) => acc + this.ssdBaieMois(bv, zc, mois), 0);
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
|
-
* Si une véranda est présente, on calcul l'mpact de l’espace tampon solarisé sur les apports solaires à travers
|
|
51
|
-
* les baies vitrées qui séparent le logement de l'espace tampon
|
|
52
|
-
*
|
|
53
55
|
* 6.3 Traitement des espaces tampons solarisés
|
|
54
56
|
* 10 - 'espace tampon solarisé (véranda,loggia fermée)'
|
|
57
|
+
*
|
|
58
|
+
* Plusieurs espaces tampons solarisés (issue #101) : T, bver, Sst et Ssd sont propres à chaque
|
|
59
|
+
* véranda. Sse = Sse_ext + Σ(v) Sse_veranda_v, chaque baie adjacence 10 n'étant comptée que dans
|
|
60
|
+
* la véranda sur laquelle elle donne (baie.reference_lnc = ets.reference).
|
|
61
|
+
* Sse_ver n'est calculée que pour les baies vitrées qui séparent le logement de l'espace tampon :
|
|
62
|
+
* sans aucune de ces baies, les apports de l'espace tampon n'entrent pas dans le logement.
|
|
55
63
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!Array.isArray(baies)) {
|
|
82
|
-
baies = [baies];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Apports totaux à travers l'espace tampon
|
|
87
|
-
* @type {number}
|
|
88
|
-
*/
|
|
89
|
-
const Sstj = baies.reduce((acc, bv) => {
|
|
90
|
-
return acc + this.ssdBaieMois(bv, ctx.zoneClimatique.id, mois, 0.8 * T + 0.024);
|
|
91
|
-
}, 0);
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Surface sud équivalente représentant l’impact des apports solaires indirects associés au rayonnement
|
|
95
|
-
* solaire entrant dans la partie habitable du logement après de multiples réflexions dans l’espace tampon solarisé
|
|
96
|
-
* @type {number}
|
|
97
|
-
*/
|
|
98
|
-
const Ssindj = Sstj - Ssdj;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Impact de l’espace tampon solarisé sur les apports solaires à travers les baies vitrées qui séparent le logement
|
|
102
|
-
* de l'espace tampon
|
|
103
|
-
* @type {number}
|
|
104
|
-
*/
|
|
105
|
-
SseVerandaj = Ssdj + Ssindj * bver;
|
|
106
|
-
}
|
|
64
|
+
const etsList = this.dedoublonnerEts(this.getEtsList(enveloppe));
|
|
65
|
+
const baiesParEts = this.rattacherBaiesAuxEts(
|
|
66
|
+
this.getBaiesSurEspaceTampon(baiesVitrees),
|
|
67
|
+
etsList
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return etsList.reduce(
|
|
71
|
+
(acc, ets, idx) => acc + this.sseVerandaMois(ets, baiesParEts[idx], zc, mois),
|
|
72
|
+
sseBaiesExt
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Surface sud équivalente apportée par une véranda sur un mois donné (§6.3)
|
|
78
|
+
*
|
|
79
|
+
* @param ets {Ets}
|
|
80
|
+
* @param baiesAdjVeranda {BaieVitree[]} baies vitrées séparant le logement de cette véranda
|
|
81
|
+
* @param zc {string} zone climatique du logement
|
|
82
|
+
* @param mois {string}
|
|
83
|
+
* @returns {number}
|
|
84
|
+
*/
|
|
85
|
+
sseVerandaMois(ets, baiesAdjVeranda, zc, mois) {
|
|
86
|
+
// Véranda sans baie vitrée vers le logement : aucun apport solaire
|
|
87
|
+
if (baiesAdjVeranda.length === 0) {
|
|
88
|
+
return 0;
|
|
107
89
|
}
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
const bver = ets.donnee_intermediaire.bver;
|
|
92
|
+
const T = ets.donnee_intermediaire.coef_transparence_ets;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Surface sud équivalente représentant l’impact des apports solaires associés au rayonnement solaire
|
|
96
|
+
* traversant directement l’espace tampon pour arriver dans la partie habitable du logement
|
|
97
|
+
* Calculés pour les baies vitrées qui séparent le logement de cet espace tampon
|
|
98
|
+
* @type {number}
|
|
99
|
+
*/
|
|
100
|
+
const Ssdj = baiesAdjVeranda.reduce((acc, bv) => acc + T * this.ssdBaieMois(bv, zc, mois), 0);
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (typeAdjacence === 10 && ets) {
|
|
117
|
-
return acc + SseVerandaj;
|
|
118
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
* Baies de l'espace tampon donnant sur l'extérieur (propres à chaque véranda)
|
|
104
|
+
*/
|
|
105
|
+
let baies = ets.baie_ets_collection?.baie_ets || [];
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
107
|
+
if (!Array.isArray(baies)) {
|
|
108
|
+
baies = [baies];
|
|
109
|
+
}
|
|
124
110
|
|
|
125
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Apports totaux à travers l'espace tampon
|
|
113
|
+
* @type {number}
|
|
114
|
+
*/
|
|
115
|
+
const Sstj = baies.reduce((acc, bv) => {
|
|
116
|
+
return acc + this.ssdBaieMois(bv, zc, mois, 0.8 * T + 0.024);
|
|
126
117
|
}, 0);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Surface sud équivalente représentant l’impact des apports solaires indirects associés au rayonnement
|
|
121
|
+
* solaire entrant dans la partie habitable du logement après de multiples réflexions dans l’espace tampon solarisé
|
|
122
|
+
* @type {number}
|
|
123
|
+
*/
|
|
124
|
+
const Ssindj = Sstj - Ssdj;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Impact de l’espace tampon solarisé sur les apports solaires à travers les baies vitrées qui séparent le logement
|
|
128
|
+
* de l'espace tampon
|
|
129
|
+
*/
|
|
130
|
+
return Ssdj + Ssindj * bver;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Liste des espaces tampons solarisés du logement (la collection peut contenir un objet unique,
|
|
135
|
+
* un tableau, ou être absente selon les DPE)
|
|
136
|
+
*
|
|
137
|
+
* @param enveloppe {Enveloppe}
|
|
138
|
+
* @returns {Ets[]}
|
|
139
|
+
*/
|
|
140
|
+
getEtsList(enveloppe) {
|
|
141
|
+
const ets = enveloppe.ets_collection?.ets;
|
|
142
|
+
if (!ets) {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
return Array.isArray(ets) ? ets : [ets];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Certaines vérandas sont dupliquées dans les DPE : une même véranda (même référence, ou à défaut
|
|
150
|
+
* même contenu) ne doit être comptée qu'une seule fois.
|
|
151
|
+
*
|
|
152
|
+
* @param etsList {Ets[]}
|
|
153
|
+
* @returns {Ets[]}
|
|
154
|
+
*/
|
|
155
|
+
dedoublonnerEts(etsList) {
|
|
156
|
+
const vues = new Set();
|
|
157
|
+
return etsList.filter((ets) => {
|
|
158
|
+
if (!ets) return false;
|
|
159
|
+
const reference = ets.donnee_entree?.reference;
|
|
160
|
+
const cle = reference ? `ref:${reference}` : `json:${JSON.stringify(ets)}`;
|
|
161
|
+
if (vues.has(cle)) return false;
|
|
162
|
+
vues.add(cle);
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Rattache chaque baie vitrée adjacence 10 à l'espace tampon sur lequel elle donne
|
|
169
|
+
* (baie.donnee_entree.reference_lnc = ets.donnee_entree.reference).
|
|
170
|
+
*
|
|
171
|
+
* Une baie sans lien exploitable (reference_lnc absente ou ne correspondant à aucune véranda) est
|
|
172
|
+
* rattachée à la première véranda : c'est le comportement historique, et le seul possible quand le
|
|
173
|
+
* DPE ne comporte qu'une véranda.
|
|
174
|
+
*
|
|
175
|
+
* @param baiesAdjVeranda {BaieVitree[]}
|
|
176
|
+
* @param etsList {Ets[]}
|
|
177
|
+
* @returns {BaieVitree[][]} baies par véranda, dans l'ordre de etsList
|
|
178
|
+
*/
|
|
179
|
+
rattacherBaiesAuxEts(baiesAdjVeranda, etsList) {
|
|
180
|
+
const baiesParEts = etsList.map(() => []);
|
|
181
|
+
// Aucune véranda exploitable : les baies adjacence 10 ne peuvent être rattachées à aucun ETS
|
|
182
|
+
if (etsList.length === 0) {
|
|
183
|
+
return baiesParEts;
|
|
184
|
+
}
|
|
185
|
+
baiesAdjVeranda.forEach((bv) => {
|
|
186
|
+
const referenceLnc = bv.donnee_entree.reference_lnc;
|
|
187
|
+
const idx = referenceLnc
|
|
188
|
+
? etsList.findIndex((ets) => ets.donnee_entree?.reference === referenceLnc)
|
|
189
|
+
: -1;
|
|
190
|
+
baiesParEts[idx === -1 ? 0 : idx].push(bv);
|
|
191
|
+
});
|
|
192
|
+
return baiesParEts;
|
|
127
193
|
}
|
|
128
194
|
|
|
129
195
|
/**
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { SurfaceSudEquivalenteService } from './surface-sud-equivalente.service.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Orientations utilisées dans les tests (identifiants arbitraires : le store est un double de test)
|
|
6
|
+
*/
|
|
7
|
+
const SUD = '1';
|
|
8
|
+
const NORD = '2';
|
|
9
|
+
const EST = '3';
|
|
10
|
+
const HORIZONTAL = '5';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Coefficients C1 de janvier (sud = 1, est = 0,40, nord = 0,31, horizontal = 0,3) utilisés pour les
|
|
14
|
+
* cas de test d'Olivier (issue #101). Indexés par orientation.
|
|
15
|
+
*/
|
|
16
|
+
const C1 = { [SUD]: 1, [NORD]: 0.31, [EST]: 0.4, [HORIZONTAL]: 0.3 };
|
|
17
|
+
|
|
18
|
+
/** @type {{ getCoefficientBaieVitree: import('vitest').Mock }} */
|
|
19
|
+
let tvStore;
|
|
20
|
+
|
|
21
|
+
/** @type {SurfaceSudEquivalenteService} */
|
|
22
|
+
let service;
|
|
23
|
+
|
|
24
|
+
/** @type {Contexte} */
|
|
25
|
+
const ctx = { zoneClimatique: { id: '2', value: 'h1b' } };
|
|
26
|
+
|
|
27
|
+
/** Fabrique une baie vitrée avec les données d'entrée/intermédiaires utiles au calcul. */
|
|
28
|
+
function baie({ adjacence = '1', surface, orientationId = SUD, sw = 1, referenceLnc }) {
|
|
29
|
+
const bv = {
|
|
30
|
+
donnee_entree: {
|
|
31
|
+
enum_type_adjacence_id: adjacence,
|
|
32
|
+
enum_orientation_id: orientationId,
|
|
33
|
+
enum_inclinaison_vitrage_id: '3',
|
|
34
|
+
surface_totale_baie: surface
|
|
35
|
+
},
|
|
36
|
+
donnee_intermediaire: { sw }
|
|
37
|
+
};
|
|
38
|
+
if (referenceLnc !== undefined) bv.donnee_entree.reference_lnc = referenceLnc;
|
|
39
|
+
return bv;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Fabrique une baie de l'espace tampon donnant sur l'extérieur (pas de donnée intermédiaire). */
|
|
43
|
+
function baieEts(surface, orientationId) {
|
|
44
|
+
return {
|
|
45
|
+
donnee_entree: {
|
|
46
|
+
enum_orientation_id: orientationId,
|
|
47
|
+
enum_inclinaison_vitrage_id: '3',
|
|
48
|
+
surface_totale_baie: surface
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Fabrique une véranda avec ses propres T, bver et baies extérieures. */
|
|
54
|
+
function veranda(reference, T, bver, baiesExt) {
|
|
55
|
+
return {
|
|
56
|
+
donnee_entree: { reference },
|
|
57
|
+
donnee_intermediaire: { bver, coef_transparence_ets: T },
|
|
58
|
+
baie_ets_collection: {
|
|
59
|
+
baie_ets: baiesExt.map(([surface, orientationId]) => baieEts(surface, orientationId))
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function enveloppe(baiesVitrees, ets) {
|
|
65
|
+
return {
|
|
66
|
+
baie_vitree_collection: { baie_vitree: baiesVitrees },
|
|
67
|
+
ets_collection: ets === undefined ? {} : { ets }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('Calcul de la surface sud équivalente (§6.2 / §6.3)', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
tvStore = {
|
|
74
|
+
getCoefficientBaieVitree: vi.fn((orientation) => C1[orientation])
|
|
75
|
+
};
|
|
76
|
+
service = new SurfaceSudEquivalenteService(tvStore);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('ssdBaieMois', () => {
|
|
80
|
+
test('interroge la table C1 avec orientation, inclinaison, zone climatique et mois', () => {
|
|
81
|
+
const bv = baie({ surface: 10, orientationId: EST, sw: 0.5 });
|
|
82
|
+
// 10 * 0,40 * 0,5
|
|
83
|
+
expect(service.ssdBaieMois(bv, '2', 'Janvier')).toBeCloseTo(2, 9);
|
|
84
|
+
expect(tvStore.getCoefficientBaieVitree).toHaveBeenCalledWith(3, 3, 2, 'Janvier');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('inclinaison absente : vitrage vertical (3) par défaut ; facteurs fe1/fe2 appliqués', () => {
|
|
88
|
+
const bv = baie({ surface: 10, sw: 0.5 });
|
|
89
|
+
delete bv.donnee_entree.enum_inclinaison_vitrage_id;
|
|
90
|
+
bv.donnee_intermediaire.fe1 = 0.8;
|
|
91
|
+
bv.donnee_intermediaire.fe2 = 0.5;
|
|
92
|
+
expect(service.ssdBaieMois(bv, '2', 'Janvier')).toBeCloseTo(10 * 0.5 * 0.8 * 0.5, 9);
|
|
93
|
+
expect(tvStore.getCoefficientBaieVitree).toHaveBeenCalledWith(1, 3, 2, 'Janvier');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('un coefficient explicite remplace le facteur solaire sw de la baie', () => {
|
|
97
|
+
expect(service.ssdBaieMois(baieEts(10, SUD), '2', 'Janvier', 0.3)).toBeCloseTo(3, 9);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('ssdMois - sans espace tampon solarisé', () => {
|
|
102
|
+
test('somme des baies donnant sur l’extérieur ; les autres adjacences sont ignorées', () => {
|
|
103
|
+
const bvList = [
|
|
104
|
+
baie({ surface: 10, sw: 0.5 }),
|
|
105
|
+
baie({ adjacence: '10', surface: 4 }),
|
|
106
|
+
baie({ adjacence: '3', surface: 4 })
|
|
107
|
+
];
|
|
108
|
+
expect(service.ssdMois(ctx, enveloppe(bvList), 'Janvier')).toBeCloseTo(5, 9);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('ets_collection vide (tableau) ou absente : seules les baies extérieures comptent', () => {
|
|
112
|
+
const bvList = [baie({ surface: 10, sw: 0.5 }), baie({ adjacence: '10', surface: 4 })];
|
|
113
|
+
expect(service.ssdMois(ctx, enveloppe(bvList, []), 'Janvier')).toBeCloseTo(5, 9);
|
|
114
|
+
expect(
|
|
115
|
+
service.ssdMois(ctx, { baie_vitree_collection: { baie_vitree: bvList } }, 'Janvier')
|
|
116
|
+
).toBeCloseTo(5, 9);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('aucune baie vitrée : surface sud équivalente nulle', () => {
|
|
120
|
+
expect(service.ssdMois(ctx, {}, 'Janvier')).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('ssdMois - un espace tampon solarisé', () => {
|
|
125
|
+
test('apports directs + indirects : Sse = Sse_ext + Ssd + (Sst - Ssd) × bver', () => {
|
|
126
|
+
// T = 0,5, bver = 0,8, baie extérieure ETS 10 m² sud (unique, non tableau)
|
|
127
|
+
const ets = {
|
|
128
|
+
donnee_entree: {},
|
|
129
|
+
donnee_intermediaire: { bver: 0.8, coef_transparence_ets: 0.5 },
|
|
130
|
+
baie_ets_collection: { baie_ets: baieEts(10, SUD) }
|
|
131
|
+
};
|
|
132
|
+
const bvList = [baie({ surface: 10, sw: 0.5 }), baie({ adjacence: '10', surface: 2 })];
|
|
133
|
+
// Ssd = 0,5 × 2 × 1 × 1 = 1 ; Sst = 10 × (0,8 × 0,5 + 0,024) = 4,24
|
|
134
|
+
// Sse_ver = 1 + (4,24 - 1) × 0,8 = 3,592
|
|
135
|
+
expect(service.ssdMois(ctx, enveloppe(bvList, ets), 'Janvier')).toBeCloseTo(5 + 3.592, 9);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('ETS sans baie adjacence 10 : bver et T sans effet, Sse_ver non ajoutée', () => {
|
|
139
|
+
const ets = veranda('V', 0.5, 0.8, [[10, SUD]]);
|
|
140
|
+
const bvList = [baie({ surface: 10, sw: 0.5 })];
|
|
141
|
+
expect(service.ssdMois(ctx, enveloppe(bvList, ets), 'Janvier')).toBeCloseTo(5, 9);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('ETS sans baie extérieure : Sst = 0', () => {
|
|
145
|
+
const ets = {
|
|
146
|
+
donnee_entree: {},
|
|
147
|
+
donnee_intermediaire: { bver: 0.5, coef_transparence_ets: 0.5 }
|
|
148
|
+
};
|
|
149
|
+
const bvList = [baie({ adjacence: '10', surface: 2 })];
|
|
150
|
+
// Ssd = 1 ; Sse_ver = 1 + (0 - 1) × 0,5 = 0,5
|
|
151
|
+
expect(service.ssdMois(ctx, enveloppe(bvList, ets), 'Janvier')).toBeCloseTo(0.5, 9);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('la Sse_ver n’est comptée qu’une fois quel que soit le nombre de baies adjacence 10', () => {
|
|
155
|
+
const ets = veranda('V', 0.5, 0.8, [[10, SUD]]);
|
|
156
|
+
const uneBaie = [baie({ adjacence: '10', surface: 2 })];
|
|
157
|
+
const deuxBaies = [
|
|
158
|
+
baie({ adjacence: '10', surface: 1 }),
|
|
159
|
+
baie({ adjacence: '10', surface: 1 })
|
|
160
|
+
];
|
|
161
|
+
expect(service.ssdMois(ctx, enveloppe(deuxBaies, ets), 'Janvier')).toBeCloseTo(
|
|
162
|
+
service.ssdMois(ctx, enveloppe(uneBaie, ets), 'Janvier'),
|
|
163
|
+
9
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 6.3 Plusieurs espaces tampons solarisés (issue #101) : T, bver, Sst et Ssd sont propres à chaque
|
|
170
|
+
* véranda ; Sse = Sse_ext + Σ(v) Sse_veranda_v. Une baie adjacence 10 n'entre que dans la véranda
|
|
171
|
+
* sur laquelle elle donne (reference_lnc = ets.reference).
|
|
172
|
+
* Cas de test fournis par Olivier (thermicien) : C1 sud = 1, est = 0,40, nord = 0,31 ; Fe = 1.
|
|
173
|
+
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §6.3
|
|
174
|
+
*/
|
|
175
|
+
describe('ssdMois - plusieurs espaces tampons solarisés (issue #101)', () => {
|
|
176
|
+
// Baie extérieure 10 m² sud, Sw = 0,5 => Sse_ext = 5
|
|
177
|
+
const baieExt = baie({ surface: 10, sw: 0.5 });
|
|
178
|
+
// V1 : T = 0,62, bver = 0,55, baies vers l'extérieur 8 m² sud + 4 m² est
|
|
179
|
+
const V1 = veranda('V1', 0.62, 0.55, [
|
|
180
|
+
[8, SUD],
|
|
181
|
+
[4, EST]
|
|
182
|
+
]);
|
|
183
|
+
// V2 : T = 0,45, bver = 0,85, baie vers l'extérieur 6 m² nord
|
|
184
|
+
const V2 = veranda('V2', 0.45, 0.85, [[6, NORD]]);
|
|
185
|
+
const baiesV1 = [
|
|
186
|
+
baie({ adjacence: '10', surface: 4, sw: 0.47, referenceLnc: 'V1' }),
|
|
187
|
+
baie({ adjacence: '10', surface: 2, sw: 0.47, referenceLnc: 'V1' })
|
|
188
|
+
];
|
|
189
|
+
const baiesV2 = [
|
|
190
|
+
baie({ adjacence: '10', surface: 2, orientationId: NORD, sw: 0.47, referenceLnc: 'V2' }),
|
|
191
|
+
baie({ adjacence: '10', surface: 1, orientationId: NORD, sw: 0.47, referenceLnc: 'V2' })
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
// SseV1 = 1,7484 + (4,992 - 1,7484) × 0,55 = 3,53238
|
|
195
|
+
const SSE_V1 = 3.53238;
|
|
196
|
+
// SseV2 = 0,196695 + (0,71424 - 0,196695) × 0,85 = 0,63660825
|
|
197
|
+
const SSE_V2 = 0.63660825;
|
|
198
|
+
|
|
199
|
+
test('CT1 - cas de l’issue : Sse = Sse_ext + SseV1 + SseV2 avec T et bver propres', () => {
|
|
200
|
+
const env = enveloppe([baieExt, ...baiesV1, ...baiesV2], [V1, V2]);
|
|
201
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V1 + SSE_V2, 9);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('CT2 - seules les baies de V2 : le Sst de V1 n’est pas appliqué', () => {
|
|
205
|
+
const env = enveloppe([baieExt, ...baiesV2], [V1, V2]);
|
|
206
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V2, 9);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('CT3 - seules les baies de V1 : V2 sans baie vers le logement apporte 0', () => {
|
|
210
|
+
const env = enveloppe([baieExt, ...baiesV1], [V1, V2]);
|
|
211
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V1, 9);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('CT4 - véranda dupliquée : pas de double comptage', () => {
|
|
215
|
+
const env = enveloppe([baieExt, ...baiesV1], [V1, structuredClone(V1)]);
|
|
216
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V1, 9);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('baies sans reference_lnc : rattachées à la première véranda (comportement historique)', () => {
|
|
220
|
+
const env = enveloppe(
|
|
221
|
+
[
|
|
222
|
+
baieExt,
|
|
223
|
+
baie({ adjacence: '10', surface: 4, sw: 0.47 }),
|
|
224
|
+
baie({ adjacence: '10', surface: 2, sw: 0.47 })
|
|
225
|
+
],
|
|
226
|
+
[V1, V2]
|
|
227
|
+
);
|
|
228
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V1, 9);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('l’ordre des vérandas dans la collection est sans effet', () => {
|
|
232
|
+
const env = enveloppe([baieExt, ...baiesV1, ...baiesV2], [V2, V1]);
|
|
233
|
+
expect(service.ssdMois(ctx, env, 'Janvier')).toBeCloseTo(5 + SSE_V1 + SSE_V2, 9);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('execute somme la surface sud équivalente de chaque mois', () => {
|
|
237
|
+
const env = enveloppe([baieExt, ...baiesV1, ...baiesV2], [V1, V2]);
|
|
238
|
+
// C1 indépendant du mois dans le double de test : 12 × valeur de janvier
|
|
239
|
+
expect(service.execute(ctx, env)).toBeCloseTo(12 * (5 + SSE_V1 + SSE_V2), 9);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('dedoublonnerEts / rattacherBaiesAuxEts / getEtsList', () => {
|
|
244
|
+
test('getEtsList : objet unique, tableau ou absence', () => {
|
|
245
|
+
const ets = { donnee_entree: {} };
|
|
246
|
+
expect(service.getEtsList({ ets_collection: { ets } })).toEqual([ets]);
|
|
247
|
+
expect(service.getEtsList({ ets_collection: { ets: [ets] } })).toEqual([ets]);
|
|
248
|
+
expect(service.getEtsList({})).toEqual([]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('dédoublonnage par référence, à défaut par contenu identique ; éléments vides ignorés', () => {
|
|
252
|
+
const a = { donnee_entree: { reference: 'A' }, x: 1 };
|
|
253
|
+
const aBis = { donnee_entree: { reference: 'A' }, x: 2 };
|
|
254
|
+
const sansRef = { donnee_intermediaire: { bver: 1 } };
|
|
255
|
+
const autreSansRef = { donnee_intermediaire: { bver: 2 } };
|
|
256
|
+
expect(
|
|
257
|
+
service.dedoublonnerEts([a, aBis, sansRef, structuredClone(sansRef), autreSansRef, null])
|
|
258
|
+
).toEqual([a, sansRef, autreSansRef]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('rattachement par reference_lnc ; référence inconnue ou absente => première véranda', () => {
|
|
262
|
+
const ets = [{ donnee_entree: { reference: 'A' } }, { donnee_entree: { reference: 'B' } }];
|
|
263
|
+
const bA = { donnee_entree: { reference_lnc: 'A' } };
|
|
264
|
+
const bB = { donnee_entree: { reference_lnc: 'B' } };
|
|
265
|
+
const bInconnue = { donnee_entree: { reference_lnc: 'Z' } };
|
|
266
|
+
const bSans = { donnee_entree: {} };
|
|
267
|
+
expect(service.rattacherBaiesAuxEts([bA, bB, bInconnue, bSans], ets)).toEqual([
|
|
268
|
+
[bA, bInconnue, bSans],
|
|
269
|
+
[bB]
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('véranda sans donnee_entree : seules les baies sans lien lui sont rattachées', () => {
|
|
274
|
+
const ets = [{}, { donnee_entree: { reference: 'B' } }];
|
|
275
|
+
const bB = { donnee_entree: { reference_lnc: 'B' } };
|
|
276
|
+
const bSans = { donnee_entree: {} };
|
|
277
|
+
expect(service.rattacherBaiesAuxEts([bB, bSans], ets)).toEqual([[bSans], [bB]]);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -138,7 +138,10 @@ export class EngineService {
|
|
|
138
138
|
delete m.baie_vitree_double_fenetre?.donnee_intermediaire;
|
|
139
139
|
delete m.donnee_intermediaire;
|
|
140
140
|
});
|
|
141
|
-
|
|
141
|
+
// Un DPE peut comporter un ou plusieurs espaces tampons solarisés (objet ou tableau)
|
|
142
|
+
[dpe.logement.enveloppe.ets_collection.ets]
|
|
143
|
+
.flat()
|
|
144
|
+
.forEach((ets) => delete ets?.donnee_intermediaire);
|
|
142
145
|
dpe.logement.enveloppe.plancher_bas_collection.plancher_bas?.map(
|
|
143
146
|
(m) => delete m.donnee_intermediaire
|
|
144
147
|
);
|
|
@@ -227,4 +227,18 @@ describe('EngineService (orchestration de la méthode 3CL)', () => {
|
|
|
227
227
|
// Aucun tableau interne ni production_elec_enr : les `?.` court-circuitent
|
|
228
228
|
expect(() => service.execute(dpeMinimal())).not.toThrow();
|
|
229
229
|
});
|
|
230
|
+
|
|
231
|
+
test('nettoie les données intermédiaires de chaque espace tampon solarisé (issue #101)', () => {
|
|
232
|
+
const dpe = dpeComplet();
|
|
233
|
+
dpe.logement.enveloppe.ets_collection.ets = [
|
|
234
|
+
{ donnee_intermediaire: { bver: 0.55 } },
|
|
235
|
+
{ donnee_intermediaire: { bver: 0.85 } },
|
|
236
|
+
null
|
|
237
|
+
];
|
|
238
|
+
const resultat = service.execute(dpe);
|
|
239
|
+
const [v1, v2, vide] = resultat.logement.enveloppe.ets_collection.ets;
|
|
240
|
+
expect(v1.donnee_intermediaire).toBeUndefined();
|
|
241
|
+
expect(v2.donnee_intermediaire).toBeUndefined();
|
|
242
|
+
expect(vide).toBeNull();
|
|
243
|
+
});
|
|
230
244
|
});
|
|
@@ -258,14 +258,15 @@ export class DeperditionEnveloppeService {
|
|
|
258
258
|
}
|
|
259
259
|
});
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Un DPE peut comporter plusieurs espaces tampons solarisés (issue #101) : bver et le coefficient
|
|
263
|
+
* de transparence sont propres à chacun d'eux.
|
|
264
|
+
*/
|
|
265
|
+
const ets = enveloppe.ets_collection?.ets;
|
|
262
266
|
if (ets) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
ets.donnee_intermediaire = this.#espaceTamponService.execute(ctx, ets);
|
|
267
|
+
(Array.isArray(ets) ? ets : [ets]).forEach((e) => {
|
|
268
|
+
e.donnee_intermediaire = this.#espaceTamponService.execute(ctx, e);
|
|
269
|
+
});
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
enveloppe.pont_thermique_collection.pont_thermique?.forEach((pt) => {
|