@open3cl/engine 1.0.9 → 1.0.11
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/features/dpe/domain/models/type-habitation.model.js +8 -0
- package/features/dpe/infrastructure/ecs/ecsTv.store.js +22 -0
- package/features/dpe/infrastructure/ecs/ecsTv.store.spec.js +33 -0
- package/features/dpe/infrastructure/{baieVitreeTv.store.js → enveloppe/baieVitreeTv.store.js} +74 -4
- package/features/dpe/infrastructure/{baieVitreeTv.store.spec.js → enveloppe/baieVitreeTv.store.spec.js} +106 -0
- package/features/dpe/infrastructure/{pontThermiqueTv.store.js → enveloppe/pontThermiqueTv.store.js} +3 -3
- package/features/dpe/infrastructure/froid/frTv.store.js +36 -0
- package/features/dpe/infrastructure/froid/frTv.store.spec.js +52 -0
- package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.js +74 -0
- package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +85 -0
- package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.js +134 -0
- package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.spec.js +167 -0
- package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.js +58 -0
- package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +67 -0
- package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.js +131 -0
- package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +229 -0
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.js +168 -0
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +230 -0
- package/features/engine/domain/contexte.builder.js +103 -64
- package/features/engine/domain/contexte.builder.spec.js +99 -64
- package/features/engine/domain/engine.service.js +16 -2
- package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.js +2 -2
- package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +1 -1
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.js +19 -0
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +3 -3
- package/features/engine/domain/enveloppe/deperdition.service.js +2 -3
- package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.js +44 -0
- package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +81 -0
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.js +3 -3
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +30 -10
- package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.js +2 -2
- package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +18 -6
- package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.js +2 -2
- package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +21 -7
- package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.js +1 -1
- package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +1 -1
- package/features/engine/domain/enveloppe/porte/deperdition-porte.service.js +1 -1
- package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +2 -2
- package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js +1 -1
- package/features/engine/domain/logement/nadeq.service.js +63 -0
- package/features/engine/domain/logement/nadeq.service.spec.js +61 -0
- package/features/engine/domain/models/contexte.model.ts +19 -5
- package/features/engine/domain/models/deperdition.model.ts +1 -1
- package/package.json +1 -1
- /package/features/dpe/infrastructure/{pontThermiqueTv.store.spec.js → enveloppe/pontThermiqueTv.store.spec.js} +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { SurfaceSudEquivalenteService } from './../surface-sud-equivalente.service.js';
|
|
3
|
+
import { ApportGratuitService } from './apport-gratuit.service.js';
|
|
4
|
+
import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
|
|
5
|
+
import { mois_liste } from '../../../../../utils.js';
|
|
6
|
+
|
|
7
|
+
/** @type {FrTvStore} **/
|
|
8
|
+
let frTvStore;
|
|
9
|
+
|
|
10
|
+
/** @type {SurfaceSudEquivalenteService} **/
|
|
11
|
+
let surfaceSudEquivalenteService;
|
|
12
|
+
|
|
13
|
+
/** @type {ApportGratuitService} **/
|
|
14
|
+
let service;
|
|
15
|
+
|
|
16
|
+
describe('Calcul des apports gratuits au logement', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
frTvStore = new FrTvStore();
|
|
19
|
+
surfaceSudEquivalenteService = new SurfaceSudEquivalenteService();
|
|
20
|
+
service = new ApportGratuitService(frTvStore, surfaceSudEquivalenteService);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test.each([
|
|
24
|
+
{
|
|
25
|
+
label: 'avec climatisation',
|
|
26
|
+
withClimatisation: true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'sans climatisation',
|
|
30
|
+
withClimatisation: false
|
|
31
|
+
}
|
|
32
|
+
])('Determination des apports gratuits pour un logement $label', ({ withClimatisation }) => {
|
|
33
|
+
vi.spyOn(surfaceSudEquivalenteService, 'ssdMois').mockReturnValue(18.5);
|
|
34
|
+
vi.spyOn(frTvStore, 'getData').mockReturnValue(10);
|
|
35
|
+
|
|
36
|
+
/** @type {Contexte} */
|
|
37
|
+
const ctx = {
|
|
38
|
+
zoneClimatique: { value: 'h1a' },
|
|
39
|
+
altitude: { value: '400-800m' },
|
|
40
|
+
inertie: { ilpa: 1 }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** @type { Logement } **/
|
|
44
|
+
const logement = { enveloppe: { porte_collection: {} } };
|
|
45
|
+
|
|
46
|
+
if (withClimatisation) {
|
|
47
|
+
logement.climatisation_collection = { climatisation: [{}] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
expect(service.apportSolaire(ctx, logement)).toStrictEqual({
|
|
51
|
+
apport_solaire_ch: 2220000,
|
|
52
|
+
apport_solaire_fr: withClimatisation ? 2220000 : 0
|
|
53
|
+
});
|
|
54
|
+
expect(surfaceSudEquivalenteService.ssdMois).toHaveBeenCalledWith(
|
|
55
|
+
ctx,
|
|
56
|
+
logement.enveloppe,
|
|
57
|
+
'Janvier'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
for (const mois of mois_liste) {
|
|
61
|
+
expect(frTvStore.getData).toHaveBeenCalledWith(
|
|
62
|
+
'e',
|
|
63
|
+
ctx.altitude.value,
|
|
64
|
+
ctx.zoneClimatique.value,
|
|
65
|
+
mois,
|
|
66
|
+
ctx.inertie.ilpa
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (withClimatisation) {
|
|
70
|
+
expect(frTvStore.getData).toHaveBeenCalledWith(
|
|
71
|
+
'e_fr_28',
|
|
72
|
+
ctx.altitude.value,
|
|
73
|
+
ctx.zoneClimatique.value,
|
|
74
|
+
mois
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
expect(frTvStore.getData).not.toHaveBeenCalledWith(
|
|
78
|
+
'e_fr_28',
|
|
79
|
+
ctx.altitude.value,
|
|
80
|
+
ctx.zoneClimatique.value,
|
|
81
|
+
mois
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test.each([
|
|
88
|
+
{
|
|
89
|
+
label: 'avec climatisation',
|
|
90
|
+
withClimatisation: true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: 'sans climatisation',
|
|
94
|
+
withClimatisation: false
|
|
95
|
+
}
|
|
96
|
+
])('Determination des apports internes pour un logement $label', ({ withClimatisation }) => {
|
|
97
|
+
vi.spyOn(frTvStore, 'getData').mockReturnValue(10);
|
|
98
|
+
|
|
99
|
+
/** @type {Contexte} */
|
|
100
|
+
const ctx = {
|
|
101
|
+
zoneClimatique: { value: 'h1a' },
|
|
102
|
+
altitude: { value: '400-800m' },
|
|
103
|
+
inertie: { ilpa: 1 },
|
|
104
|
+
surfaceHabitable: 12,
|
|
105
|
+
nadeq: 1.25
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** @type { Logement } **/
|
|
109
|
+
const logement = { enveloppe: { porte_collection: {} } };
|
|
110
|
+
|
|
111
|
+
if (withClimatisation) {
|
|
112
|
+
logement.climatisation_collection = { climatisation: [{}] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
expect(service.apportInterne(ctx, logement)).toStrictEqual({
|
|
116
|
+
apport_interne_ch: 15675.94285714286,
|
|
117
|
+
apport_interne_fr: withClimatisation ? 15675.94285714286 : 0
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
for (const mois of mois_liste) {
|
|
121
|
+
expect(frTvStore.getData).toHaveBeenCalledWith(
|
|
122
|
+
'nref19',
|
|
123
|
+
ctx.altitude.value,
|
|
124
|
+
ctx.zoneClimatique.value,
|
|
125
|
+
mois,
|
|
126
|
+
ctx.inertie.ilpa
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (withClimatisation) {
|
|
130
|
+
expect(frTvStore.getData).toHaveBeenCalledWith(
|
|
131
|
+
'nref28',
|
|
132
|
+
ctx.altitude.value,
|
|
133
|
+
ctx.zoneClimatique.value,
|
|
134
|
+
mois
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
expect(frTvStore.getData).not.toHaveBeenCalledWith(
|
|
138
|
+
'nref28',
|
|
139
|
+
ctx.altitude.value,
|
|
140
|
+
ctx.zoneClimatique.value,
|
|
141
|
+
mois
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('Determination des apports solaires pour un mois donné', () => {
|
|
148
|
+
vi.spyOn(surfaceSudEquivalenteService, 'ssdMois').mockReturnValue(18.5);
|
|
149
|
+
|
|
150
|
+
/** @type {Contexte} */
|
|
151
|
+
const ctx = { zoneClimatique: { id: 1 } };
|
|
152
|
+
|
|
153
|
+
/** @type { Enveloppe } **/
|
|
154
|
+
const enveloppe = {
|
|
155
|
+
porte_collection: {}
|
|
156
|
+
};
|
|
157
|
+
expect(service.apportSolaireMois(ctx, enveloppe, 'Janvier', 18)).toBe(333000);
|
|
158
|
+
expect(surfaceSudEquivalenteService.ssdMois).toHaveBeenCalledWith(ctx, enveloppe, 'Janvier');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('Determination des apports internes dans le logement pour un mois donné', () => {
|
|
162
|
+
/** @type {Contexte} */
|
|
163
|
+
const ctx = { surfaceHabitable: 88, nadeq: 2 };
|
|
164
|
+
|
|
165
|
+
expect(service.apportInterneMois(ctx, 18)).toBeCloseTo(8121.39, 2);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { mois_liste, Njj } from '../../../../../utils.js';
|
|
2
|
+
import { EcsTvStore } from '../../../../dpe/infrastructure/ecs/ecsTv.store.js';
|
|
3
|
+
import { inject } from 'dioma';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calcul du besoin en eau chaude sanitaire
|
|
7
|
+
* Chapitre 11.1 Calcul du besoin d’ECS
|
|
8
|
+
*
|
|
9
|
+
* Methode_de_calcul_3CL_DPE_2021 - Page 70
|
|
10
|
+
* Octobre 2021
|
|
11
|
+
* @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
|
|
12
|
+
*/
|
|
13
|
+
export class BesoinEcsService {
|
|
14
|
+
/**
|
|
15
|
+
* @type {EcsTvStore}
|
|
16
|
+
*/
|
|
17
|
+
#tvStore;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param tvStore {EcsTvStore}
|
|
21
|
+
*/
|
|
22
|
+
constructor(tvStore = inject(EcsTvStore)) {
|
|
23
|
+
this.#tvStore = tvStore;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param ctx {Contexte}
|
|
28
|
+
* @return {{besoin_ecs: number, besoin_ecs_depensier: number}}
|
|
29
|
+
*/
|
|
30
|
+
execute(ctx) {
|
|
31
|
+
return mois_liste.reduce(
|
|
32
|
+
(acc, mois) => {
|
|
33
|
+
acc.besoin_ecs += this.besoinEcsMois(ctx, mois, false);
|
|
34
|
+
acc.besoin_ecs_depensier += this.besoinEcsMois(ctx, mois, true);
|
|
35
|
+
return acc;
|
|
36
|
+
},
|
|
37
|
+
{ besoin_ecs: 0, besoin_ecs_depensier: 0 }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Calcul du besoin ecs pour un mois donné
|
|
43
|
+
*
|
|
44
|
+
* @param ctx {Contexte}
|
|
45
|
+
* @param mois {string}
|
|
46
|
+
* @param depensier {boolean}
|
|
47
|
+
* @returns {number}
|
|
48
|
+
*/
|
|
49
|
+
besoinEcsMois(ctx, mois, depensier) {
|
|
50
|
+
const tefs = this.#tvStore.getTefs(ctx.altitude.value, ctx.zoneClimatique.value, mois);
|
|
51
|
+
|
|
52
|
+
if (depensier) {
|
|
53
|
+
return (1.163 * ctx.nadeq * 79 * (40 - tefs) * Njj[mois]) / 1000;
|
|
54
|
+
} else {
|
|
55
|
+
return (1.163 * ctx.nadeq * 56 * (40 - tefs) * Njj[mois]) / 1000;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import corpus from '../../../../../../test/corpus-sano.json';
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { DpeNormalizerService } from '../../../../normalizer/domain/dpe-normalizer.service.js';
|
|
4
|
+
import { ContexteBuilder } from '../../contexte.builder.js';
|
|
5
|
+
import { getAdemeFileJson } from '../../../../../../test/test-helpers.js';
|
|
6
|
+
import { BesoinEcsService } from './besoin-ecs.service.js';
|
|
7
|
+
import { EcsTvStore } from '../../../../dpe/infrastructure/ecs/ecsTv.store.js';
|
|
8
|
+
|
|
9
|
+
/** @type {BesoinEcsService} **/
|
|
10
|
+
let service;
|
|
11
|
+
|
|
12
|
+
/** @type {DpeNormalizerService} **/
|
|
13
|
+
let normalizerService;
|
|
14
|
+
|
|
15
|
+
/** @type {ContexteBuilder} **/
|
|
16
|
+
let contexteBuilder;
|
|
17
|
+
|
|
18
|
+
/** @type {EcsTvStore} **/
|
|
19
|
+
let tvStore;
|
|
20
|
+
|
|
21
|
+
describe('Calcul du besoin en eau chaude sanitaire', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
tvStore = new EcsTvStore();
|
|
24
|
+
service = new BesoinEcsService(tvStore);
|
|
25
|
+
normalizerService = new DpeNormalizerService();
|
|
26
|
+
contexteBuilder = new ContexteBuilder();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('Determination du besoin en eau chaude sanitaire pour un mois donné', () => {
|
|
30
|
+
vi.spyOn(tvStore, 'getTefs').mockReturnValue(0.5);
|
|
31
|
+
|
|
32
|
+
/** @type {Contexte} */
|
|
33
|
+
const ctx = {
|
|
34
|
+
altitude: {
|
|
35
|
+
value: '400-800m'
|
|
36
|
+
},
|
|
37
|
+
zoneClimatique: {
|
|
38
|
+
value: 'h1a'
|
|
39
|
+
},
|
|
40
|
+
nadeq: 2
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(service.besoinEcsMois(ctx, 'Janvier', false)).toBeCloseTo(159.5, 2);
|
|
44
|
+
expect(service.besoinEcsMois(ctx, 'Janvier', true)).toBeCloseTo(225.01, 2);
|
|
45
|
+
expect(tvStore.getTefs).toHaveBeenCalledWith('400-800m', 'h1a', 'Janvier');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Test d'intégration pour le besoin en eau chaude sanitaire", () => {
|
|
49
|
+
test.each(corpus)('vérification des sorties besoin_ecs pour dpe %s', (ademeId) => {
|
|
50
|
+
/**
|
|
51
|
+
* @type {Dpe}
|
|
52
|
+
*/
|
|
53
|
+
let dpeRequest = getAdemeFileJson(ademeId);
|
|
54
|
+
dpeRequest = normalizerService.normalize(dpeRequest);
|
|
55
|
+
|
|
56
|
+
/** @type {Contexte} */
|
|
57
|
+
const ctx = contexteBuilder.fromDpe(dpeRequest);
|
|
58
|
+
|
|
59
|
+
const ecs = service.execute(ctx);
|
|
60
|
+
expect(ecs.besoin_ecs).toBeCloseTo(dpeRequest.logement.sortie.apport_et_besoin.besoin_ecs, 2);
|
|
61
|
+
expect(ecs.besoin_ecs_depensier).toBeCloseTo(
|
|
62
|
+
dpeRequest.logement.sortie.apport_et_besoin.besoin_ecs_depensier,
|
|
63
|
+
2
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { mois_liste } from '../../../../../utils.js';
|
|
2
|
+
import { inject } from 'dioma';
|
|
3
|
+
import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
|
|
4
|
+
import { ApportGratuitService } from '../apport_gratuit/apport-gratuit.service.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calcul du besoin en froid
|
|
8
|
+
* Chapitre 10.2 Calcul du besoin mensuel de froid
|
|
9
|
+
*
|
|
10
|
+
* Methode_de_calcul_3CL_DPE_2021 - Page 68
|
|
11
|
+
* Octobre 2021
|
|
12
|
+
* @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
|
|
13
|
+
*/
|
|
14
|
+
export class BesoinFroidService {
|
|
15
|
+
/**
|
|
16
|
+
* @type {ApportGratuitService}
|
|
17
|
+
*/
|
|
18
|
+
#apportGratuitService;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @type {FrTvStore}
|
|
22
|
+
*/
|
|
23
|
+
#tvStore;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param tvStore {FrTvStore}
|
|
27
|
+
* @param apportGratuitService {ApportGratuitService}
|
|
28
|
+
*/
|
|
29
|
+
constructor(tvStore = inject(FrTvStore), apportGratuitService = inject(ApportGratuitService)) {
|
|
30
|
+
this.#tvStore = tvStore;
|
|
31
|
+
this.#apportGratuitService = apportGratuitService;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param ctx {Contexte}
|
|
36
|
+
* @param logement {Logement}
|
|
37
|
+
* @return {{besoin_fr: number, besoin_fr_depensier: number}}
|
|
38
|
+
*/
|
|
39
|
+
execute(ctx, logement) {
|
|
40
|
+
const clim = logement.climatisation_collection?.climatisation || [];
|
|
41
|
+
if (clim.length === 0) {
|
|
42
|
+
return { besoin_fr: 0, besoin_fr_depensier: 0 };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return mois_liste.reduce(
|
|
46
|
+
(acc, mois) => {
|
|
47
|
+
acc.besoin_fr += this.besoinFrMois(ctx, logement, mois, false);
|
|
48
|
+
acc.besoin_fr_depensier += this.besoinFrMois(ctx, logement, mois, true);
|
|
49
|
+
return acc;
|
|
50
|
+
},
|
|
51
|
+
{ besoin_fr: 0, besoin_fr_depensier: 0 }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Calcul du besoin en froid pour un mois donné
|
|
57
|
+
*
|
|
58
|
+
* @param ctx {Contexte}
|
|
59
|
+
* @param logement {Logement}
|
|
60
|
+
* @param mois {string}
|
|
61
|
+
* @param depensier {boolean}
|
|
62
|
+
* @returns {number}
|
|
63
|
+
*/
|
|
64
|
+
besoinFrMois(ctx, logement, mois, depensier) {
|
|
65
|
+
const nref = this.#tvStore.getData(
|
|
66
|
+
depensier ? 'nref26' : 'nref28',
|
|
67
|
+
ctx.altitude.value,
|
|
68
|
+
ctx.zoneClimatique.value,
|
|
69
|
+
mois
|
|
70
|
+
);
|
|
71
|
+
if (nref === 0) return 0;
|
|
72
|
+
|
|
73
|
+
const eFr = this.#tvStore.getData(
|
|
74
|
+
depensier ? 'e_fr_26' : 'e_fr_28',
|
|
75
|
+
ctx.altitude.value,
|
|
76
|
+
ctx.zoneClimatique.value,
|
|
77
|
+
mois
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Température extérieure moyenne sur le mois j pendant les périodes de climatisation (°C)
|
|
81
|
+
const tempExtMoyClim = this.#tvStore.getData(
|
|
82
|
+
depensier ? 'textmoy_clim_26' : 'textmoy_clim_28',
|
|
83
|
+
ctx.altitude.value,
|
|
84
|
+
ctx.zoneClimatique.value,
|
|
85
|
+
mois
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Température de consigne en froid (°C)
|
|
89
|
+
const temperatureInterieure = depensier ? 26 : 28;
|
|
90
|
+
|
|
91
|
+
const aijFr = this.#apportGratuitService.apportInterneMois(ctx, nref);
|
|
92
|
+
const asjFr = this.#apportGratuitService.apportSolaireMois(ctx, logement.enveloppe, mois, eFr);
|
|
93
|
+
|
|
94
|
+
// Transfert thermique à travers l’enveloppe et le renouvellement d’air (W/K). Le GV prend en compte les
|
|
95
|
+
// échanges de chaleur par le renouvellement d‘air. Ces échanges sont calculés sur la période de refroidissement
|
|
96
|
+
// de la même façon que pour la période de chauffage
|
|
97
|
+
const GV = logement.sortie?.deperdition?.deperdition_enveloppe;
|
|
98
|
+
|
|
99
|
+
// Ratio de bilan thermique
|
|
100
|
+
const Rbth = (aijFr + asjFr) / (GV * (tempExtMoyClim - temperatureInterieure) * nref);
|
|
101
|
+
|
|
102
|
+
if (Rbth < 1 / 2) return 0;
|
|
103
|
+
|
|
104
|
+
// Constante de temps de la zone pour le refroidissement
|
|
105
|
+
const t = (this.#cin(ctx.inertie.id) * ctx.surfaceHabitable) / (3600 * GV);
|
|
106
|
+
const a = 1 + t / 15;
|
|
107
|
+
|
|
108
|
+
// Facteur d'utilisation des apports sur le mois
|
|
109
|
+
let fut = Rbth === 1 ? a / (a + 1) : (1 - Rbth ** -a) / (1 - Rbth ** (-a - 1));
|
|
110
|
+
return (
|
|
111
|
+
(aijFr + asjFr) / 1000 - ((fut * GV) / 1000) * (temperatureInterieure - tempExtMoyClim) * nref
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Capacité thermique intérieure efficace de la zone (J/K)
|
|
117
|
+
* @param inertie
|
|
118
|
+
* @returns {number}
|
|
119
|
+
*/
|
|
120
|
+
#cin(inertie) {
|
|
121
|
+
switch (inertie) {
|
|
122
|
+
case 1:
|
|
123
|
+
case 2:
|
|
124
|
+
return 260000;
|
|
125
|
+
case 3:
|
|
126
|
+
return 165000;
|
|
127
|
+
case 4:
|
|
128
|
+
return 110000;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { DpeNormalizerService } from '../../../../normalizer/domain/dpe-normalizer.service.js';
|
|
3
|
+
import { ContexteBuilder } from '../../contexte.builder.js';
|
|
4
|
+
import corpus from '../../../../../../test/corpus-sano.json';
|
|
5
|
+
import { getAdemeFileJson } from '../../../../../../test/test-helpers.js';
|
|
6
|
+
import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
|
|
7
|
+
import { ApportGratuitService } from '../apport_gratuit/apport-gratuit.service.js';
|
|
8
|
+
import { BesoinFroidService } from './besoin-froid.service.js';
|
|
9
|
+
|
|
10
|
+
/** @type {ApportGratuitService} **/
|
|
11
|
+
let apportGratuitService;
|
|
12
|
+
|
|
13
|
+
/** @type {BesoinFroidService} **/
|
|
14
|
+
let service;
|
|
15
|
+
|
|
16
|
+
/** @type {DpeNormalizerService} **/
|
|
17
|
+
let normalizerService;
|
|
18
|
+
|
|
19
|
+
/** @type {ContexteBuilder} **/
|
|
20
|
+
let contexteBuilder;
|
|
21
|
+
|
|
22
|
+
/** @type {FrTvStore} **/
|
|
23
|
+
let tvStore;
|
|
24
|
+
|
|
25
|
+
describe('Calcul des besoins en froid du logement', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tvStore = new FrTvStore();
|
|
28
|
+
apportGratuitService = new ApportGratuitService();
|
|
29
|
+
service = new BesoinFroidService(tvStore, apportGratuitService);
|
|
30
|
+
normalizerService = new DpeNormalizerService();
|
|
31
|
+
contexteBuilder = new ContexteBuilder();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Besoins en froid à 0 si aucune climatisation dans le logement', () => {
|
|
35
|
+
vi.spyOn(tvStore, 'getData').mockReturnValue(10);
|
|
36
|
+
|
|
37
|
+
/** @type {Contexte} */
|
|
38
|
+
const ctx = { zoneClimatique: { id: 1 } };
|
|
39
|
+
expect(service.execute(ctx, {})).toStrictEqual({ besoin_fr: 0, besoin_fr_depensier: 0 });
|
|
40
|
+
expect(service.execute(ctx, { enveloppe: {} })).toStrictEqual({
|
|
41
|
+
besoin_fr: 0,
|
|
42
|
+
besoin_fr_depensier: 0
|
|
43
|
+
});
|
|
44
|
+
expect(service.execute(ctx, { climatisation_collection: {} })).toStrictEqual({
|
|
45
|
+
besoin_fr: 0,
|
|
46
|
+
besoin_fr_depensier: 0
|
|
47
|
+
});
|
|
48
|
+
expect(service.execute(ctx, { climatisation_collection: { climatisation: [] } })).toStrictEqual(
|
|
49
|
+
{ besoin_fr: 0, besoin_fr_depensier: 0 }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(tvStore.getData).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Determination du besoin en froid pour un mois donné', () => {
|
|
56
|
+
test.each([
|
|
57
|
+
{
|
|
58
|
+
data: 0,
|
|
59
|
+
aijFr: 1,
|
|
60
|
+
asjFr: 0.2,
|
|
61
|
+
inertie: 4,
|
|
62
|
+
depensier: false,
|
|
63
|
+
expected: 0
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
data: 0,
|
|
67
|
+
aijFr: 1,
|
|
68
|
+
asjFr: 0.2,
|
|
69
|
+
inertie: 4,
|
|
70
|
+
depensier: true,
|
|
71
|
+
expected: 0
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
data: 58,
|
|
75
|
+
aijFr: 1,
|
|
76
|
+
asjFr: 0.2,
|
|
77
|
+
inertie: 4,
|
|
78
|
+
depensier: false,
|
|
79
|
+
expected: 0
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
data: 58,
|
|
83
|
+
aijFr: 1,
|
|
84
|
+
asjFr: 0.2,
|
|
85
|
+
inertie: 4,
|
|
86
|
+
depensier: true,
|
|
87
|
+
expected: 0
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
data: 30,
|
|
91
|
+
aijFr: 1000,
|
|
92
|
+
asjFr: 2580,
|
|
93
|
+
inertie: 4,
|
|
94
|
+
depensier: false,
|
|
95
|
+
expected: 6.25
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
data: 30,
|
|
99
|
+
aijFr: 1000,
|
|
100
|
+
asjFr: 2580,
|
|
101
|
+
inertie: 4,
|
|
102
|
+
depensier: true,
|
|
103
|
+
expected: 0
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
data: 30,
|
|
107
|
+
aijFr: 1500,
|
|
108
|
+
asjFr: 5580,
|
|
109
|
+
inertie: 4,
|
|
110
|
+
depensier: true,
|
|
111
|
+
expected: 12.38
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
data: 30,
|
|
115
|
+
aijFr: 10000,
|
|
116
|
+
asjFr: 2000,
|
|
117
|
+
inertie: 4,
|
|
118
|
+
depensier: false,
|
|
119
|
+
expected: 16.72
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
data: 30,
|
|
123
|
+
aijFr: 10000,
|
|
124
|
+
asjFr: 2000,
|
|
125
|
+
inertie: 1,
|
|
126
|
+
depensier: true,
|
|
127
|
+
expected: 20.25
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
data: 30,
|
|
131
|
+
aijFr: 10000,
|
|
132
|
+
asjFr: 2000,
|
|
133
|
+
inertie: 2,
|
|
134
|
+
depensier: true,
|
|
135
|
+
expected: 20.25
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
data: 30,
|
|
139
|
+
aijFr: 10000,
|
|
140
|
+
asjFr: 2000,
|
|
141
|
+
inertie: 3,
|
|
142
|
+
depensier: true,
|
|
143
|
+
expected: 19.66
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
data: 30,
|
|
147
|
+
aijFr: 10000,
|
|
148
|
+
asjFr: 2000,
|
|
149
|
+
inertie: 4,
|
|
150
|
+
depensier: true,
|
|
151
|
+
expected: 19.22
|
|
152
|
+
}
|
|
153
|
+
])(
|
|
154
|
+
'nref: $data, eFr: $data, tempExtMoyClim: $data, aijFr: $aijFr, asjFr: $asjFr,' +
|
|
155
|
+
'inertie: $inertie, depensier: $depensier',
|
|
156
|
+
({ data, aijFr, asjFr, inertie, depensier, expected }) => {
|
|
157
|
+
vi.spyOn(tvStore, 'getData').mockReturnValue(data);
|
|
158
|
+
vi.spyOn(apportGratuitService, 'apportInterneMois').mockReturnValue(aijFr);
|
|
159
|
+
vi.spyOn(apportGratuitService, 'apportSolaireMois').mockReturnValue(asjFr);
|
|
160
|
+
|
|
161
|
+
/** @type {Logement} **/
|
|
162
|
+
const logement = {
|
|
163
|
+
enveloppe: {},
|
|
164
|
+
sortie: { deperdition: { deperdition_enveloppe: 100 } }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** @type {Contexte} */
|
|
168
|
+
const ctx = {
|
|
169
|
+
altitude: { value: '400-800m' },
|
|
170
|
+
zoneClimatique: { value: 'h1a' },
|
|
171
|
+
inertie: { id: inertie },
|
|
172
|
+
surfaceHabitable: 25
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const ssd = service.besoinFrMois(ctx, logement, 'Janvier', depensier);
|
|
176
|
+
|
|
177
|
+
expect(tvStore.getData).toHaveBeenCalledWith(
|
|
178
|
+
depensier ? 'nref26' : 'nref28',
|
|
179
|
+
'400-800m',
|
|
180
|
+
'h1a',
|
|
181
|
+
'Janvier'
|
|
182
|
+
);
|
|
183
|
+
if (data > 0) {
|
|
184
|
+
expect(tvStore.getData).toHaveBeenCalledWith(
|
|
185
|
+
depensier ? 'e_fr_26' : 'e_fr_28',
|
|
186
|
+
'400-800m',
|
|
187
|
+
'h1a',
|
|
188
|
+
'Janvier'
|
|
189
|
+
);
|
|
190
|
+
expect(tvStore.getData).toHaveBeenCalledWith(
|
|
191
|
+
depensier ? 'textmoy_clim_26' : 'textmoy_clim_28',
|
|
192
|
+
'400-800m',
|
|
193
|
+
'h1a',
|
|
194
|
+
'Janvier'
|
|
195
|
+
);
|
|
196
|
+
expect(apportGratuitService.apportInterneMois).toHaveBeenCalledWith(ctx, data);
|
|
197
|
+
expect(apportGratuitService.apportSolaireMois).toHaveBeenCalledWith(
|
|
198
|
+
ctx,
|
|
199
|
+
{},
|
|
200
|
+
'Janvier',
|
|
201
|
+
data
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
expect(ssd).toBeCloseTo(expected, 2);
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("Test d'intégration pour le besoin en froid", () => {
|
|
211
|
+
test.each(corpus)('vérification des sorties besoin_fr pour dpe %s', (ademeId) => {
|
|
212
|
+
/**
|
|
213
|
+
* @type {Dpe}
|
|
214
|
+
*/
|
|
215
|
+
let dpeRequest = getAdemeFileJson(ademeId);
|
|
216
|
+
dpeRequest = normalizerService.normalize(dpeRequest);
|
|
217
|
+
|
|
218
|
+
/** @type {Contexte} */
|
|
219
|
+
const ctx = contexteBuilder.fromDpe(dpeRequest);
|
|
220
|
+
|
|
221
|
+
const ecs = service.execute(ctx, dpeRequest.logement);
|
|
222
|
+
expect(ecs.besoin_fr).toBeCloseTo(dpeRequest.logement.sortie.apport_et_besoin.besoin_fr, 2);
|
|
223
|
+
expect(ecs.besoin_fr_depensier).toBeCloseTo(
|
|
224
|
+
dpeRequest.logement.sortie.apport_et_besoin.besoin_fr_depensier,
|
|
225
|
+
2
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|