@open3cl/engine 1.0.11 → 1.0.13

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.
Files changed (25) hide show
  1. package/features/dpe/domain/models/dpe.model.ts +9 -0
  2. package/features/dpe/domain/models/installation-chauffage.model.ts +2 -2
  3. package/features/dpe/domain/models/installation-ecs.model.ts +15 -2
  4. package/features/dpe/domain/models/type-habitation.model.js +1 -0
  5. package/features/dpe/domain/models/type-stockage.model.js +8 -0
  6. package/features/dpe/infrastructure/ecs/ecsTv.store.js +43 -0
  7. package/features/dpe/infrastructure/ecs/ecsTv.store.spec.js +36 -0
  8. package/features/dpe/infrastructure/froid/frTv.store.js +4 -2
  9. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.js +69 -3
  10. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +123 -1
  11. package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.js +18 -2
  12. package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.spec.js +13 -2
  13. package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.js +150 -0
  14. package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.spec.js +145 -0
  15. package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.js +168 -0
  16. package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +313 -0
  17. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.js +127 -0
  18. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +220 -0
  19. package/features/engine/domain/contexte.builder.js +5 -1
  20. package/features/engine/domain/ecs/generateur-ecs.service.js +106 -0
  21. package/features/engine/domain/ecs/generateur-ecs.service.spec.js +136 -0
  22. package/features/engine/domain/ecs/installation-ecs.service.js +156 -0
  23. package/features/engine/domain/ecs/installation-ecs.service.spec.js +284 -0
  24. package/features/engine/domain/engine.service.js +13 -2
  25. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ import { TypeStockage } from '../../../dpe/domain/models/type-stockage.model.js';
2
+ import { inject } from 'dioma';
3
+ import { EcsTvStore } from '../../../dpe/infrastructure/ecs/ecsTv.store.js';
4
+
5
+ /**
6
+ * Calcul des données de calcul pour chacun des générateurs
7
+ * Données calculées
8
+ * — Qgw : pertes liées au stockage de l'ECS pour chaque générateur
9
+ */
10
+ export class GenerateurEcsService {
11
+ /**
12
+ * @type {EcsTvStore}
13
+ */
14
+ #tvStore;
15
+
16
+ /**
17
+ * @param tvStore {EcsTvStore}
18
+ */
19
+ constructor(tvStore = inject(EcsTvStore)) {
20
+ this.#tvStore = tvStore;
21
+ }
22
+
23
+ /**
24
+ * Détermination des apports et besoins pour le bien concerné
25
+ *
26
+ * @param installationEcs {InstallationEcs}
27
+ */
28
+ execute(installationEcs) {
29
+ const generateursEcs = installationEcs.generateur_ecs_collection.generateur_ecs || [];
30
+
31
+ generateursEcs.forEach((generateurEcs) => {
32
+ /** @type {GenerateurEcsDE}*/
33
+ const generateurEcsDE = generateurEcs.donnee_entree;
34
+
35
+ generateurEcs.donnee_utilisateur = {
36
+ Qgw: this.pertesStockage(generateurEcsDE)
37
+ };
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Pertes de stockage d'un générateur ECS.
43
+ * Seules les générateurs avec stockage indépendant et dissocié du générateur sont concernés
44
+ *
45
+ * 11.6.1 - Pertes de stockage des ballons d’accumulation
46
+ * 11.6.2 - Pertes des ballons électriques
47
+ *
48
+ * @param generateurEcsDE {GenerateurEcsDE}
49
+ * @return {number}
50
+ */
51
+ pertesStockage(generateurEcsDE) {
52
+ const electrique = this.generateurElectrique(generateurEcsDE);
53
+ const typeStockage = this.typeStockage(generateurEcsDE);
54
+
55
+ if (typeStockage === TypeStockage.INSTANTANE) {
56
+ return 0;
57
+ }
58
+
59
+ let Qgw;
60
+ let volumeStockage = generateurEcsDE.volume_stockage;
61
+
62
+ if (electrique) {
63
+ const cr = this.#tvStore.getPertesStockage(
64
+ generateurEcsDE.enum_type_generateur_ecs_id,
65
+ volumeStockage
66
+ );
67
+ // Les pertes de stockage des ballons électriques (Wh)
68
+ Qgw = ((8592 * 45) / 24) * volumeStockage * cr;
69
+ } else {
70
+ // La présence d’un ballon de préparation de l’ECS est responsable de pertes de stockage Qgw (Wh)
71
+ Qgw = 67662 * volumeStockage ** 0.55;
72
+ }
73
+
74
+ return Qgw;
75
+ }
76
+
77
+ /**
78
+ * Return true si le générateur ECS est électrique
79
+ *
80
+ * 1 - abscence de stockage d'ecs (production instantanée)
81
+ * 2 - stockage indépendant de la production
82
+ * 3 - stockage intégré à la production
83
+ *
84
+ * @param generateurEcsDE {GenerateurEcsDE}
85
+ * @return {boolean}
86
+ */
87
+ generateurElectrique(generateurEcsDE) {
88
+ return this.#tvStore
89
+ .getElectriqueEcsGenerateurs()
90
+ .includes(parseInt(generateurEcsDE.enum_type_generateur_ecs_id));
91
+ }
92
+
93
+ /**
94
+ * Return le type de stockage d'ECS
95
+ *
96
+ * @param generateurEcsDE {GenerateurEcsDE}
97
+ * @return TypeStockage
98
+ */
99
+ typeStockage(generateurEcsDE) {
100
+ return parseInt(generateurEcsDE.enum_type_stockage_ecs_id) === 1
101
+ ? TypeStockage.INSTANTANE
102
+ : parseInt(generateurEcsDE.enum_type_stockage_ecs_id) === 2
103
+ ? TypeStockage.INDEPENDANT
104
+ : TypeStockage.INTEGRE;
105
+ }
106
+ }
@@ -0,0 +1,136 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { GenerateurEcsService } from './generateur-ecs.service.js';
3
+ import { EcsTvStore } from '../../../dpe/infrastructure/ecs/ecsTv.store.js';
4
+ import { TypeStockage } from '../../../dpe/domain/models/type-stockage.model.js';
5
+
6
+ /** @type {GenerateurEcsService} **/
7
+ let service;
8
+
9
+ /** @type {EcsTvStore} **/
10
+ let tvStore;
11
+
12
+ describe('Calcul des informations techniques des générateurs ECS', () => {
13
+ beforeEach(() => {
14
+ tvStore = new EcsTvStore();
15
+ service = new GenerateurEcsService(tvStore);
16
+ });
17
+
18
+ describe("Calcul des pertes de stockage d'un générateur", () => {
19
+ test.each([
20
+ {
21
+ label: 'Générateur ECS avec production instantanée',
22
+ enumTypeGenerateurEcsId: 1,
23
+ enumTypeStockageEcsId: 1,
24
+ volumeStockage: 100,
25
+ expected: 0
26
+ },
27
+ {
28
+ label: 'Générateur ECS avec stockage intégré au générateur',
29
+ enumTypeGenerateurEcsId: 1,
30
+ enumTypeStockageEcsId: 2,
31
+ volumeStockage: 100,
32
+ expected: 851814.11
33
+ },
34
+ {
35
+ label: 'Générateur ECS électrique avec stockage indépendant de 100L',
36
+ enumTypeGenerateurEcsId: 2,
37
+ enumTypeStockageEcsId: 3,
38
+ electrique: true,
39
+ volumeStockage: 250,
40
+ expected: 1288800
41
+ },
42
+ {
43
+ label: 'Générateur ECS à gaz avec stockage indépendant de 200L',
44
+ enumTypeGenerateurEcsId: 1,
45
+ enumTypeStockageEcsId: 3,
46
+ volumeStockage: 200,
47
+ expected: 1247128.86
48
+ }
49
+ ])(
50
+ '$label',
51
+ ({
52
+ enumTypeGenerateurEcsId,
53
+ volumeStockage,
54
+ enumTypeStockageEcsId,
55
+ electrique = false,
56
+ expected
57
+ }) => {
58
+ vi.spyOn(tvStore, 'getPertesStockage').mockReturnValue(0.32);
59
+ vi.spyOn(tvStore, 'getElectriqueEcsGenerateurs').mockReturnValue([2]);
60
+
61
+ /** @type {GenerateurEcsDE} */
62
+ const generateurEcsDE = {
63
+ enum_type_generateur_ecs_id: enumTypeGenerateurEcsId,
64
+ volume_stockage: volumeStockage,
65
+ enum_type_stockage_ecs_id: enumTypeStockageEcsId
66
+ };
67
+
68
+ const pertesStockage = service.pertesStockage(generateurEcsDE);
69
+
70
+ if (electrique) {
71
+ expect(tvStore.getPertesStockage).toHaveBeenCalledWith(
72
+ enumTypeGenerateurEcsId,
73
+ volumeStockage
74
+ );
75
+ }
76
+ expect(pertesStockage).toBeCloseTo(expected, 2);
77
+ }
78
+ );
79
+
80
+ test('Ajout des données utilisateurs sur tous les générateurs', () => {
81
+ /** @type {InstallationEcs} */
82
+ const installationsEcs = {
83
+ generateur_ecs_collection: {
84
+ generateur_ecs: [
85
+ {
86
+ donnee_entree: {
87
+ enum_type_stockage_ecs_id: 2,
88
+ enum_type_energie_id: 2,
89
+ volume_stockage: 100
90
+ }
91
+ },
92
+ {
93
+ donnee_entree: {
94
+ enum_type_stockage_ecs_id: 2,
95
+ enum_type_energie_id: 2,
96
+ volume_stockage: 150
97
+ }
98
+ },
99
+ {
100
+ donnee_entree: {
101
+ enum_type_stockage_ecs_id: 1,
102
+ enum_type_energie_id: 2,
103
+ volume_stockage: 150
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ };
109
+
110
+ service.execute(installationsEcs);
111
+
112
+ expect(
113
+ installationsEcs.generateur_ecs_collection.generateur_ecs[0].donnee_utilisateur.Qgw
114
+ ).toBeCloseTo(851814.112128, 2);
115
+ expect(
116
+ installationsEcs.generateur_ecs_collection.generateur_ecs[1].donnee_utilisateur.Qgw
117
+ ).toBeCloseTo(1064620.98719, 2);
118
+ expect(
119
+ installationsEcs.generateur_ecs_collection.generateur_ecs[2].donnee_utilisateur.Qgw
120
+ ).toBe(0);
121
+ });
122
+ });
123
+
124
+ test("Determination du type d'énergie électrique du générateur", () => {
125
+ vi.spyOn(tvStore, 'getElectriqueEcsGenerateurs').mockReturnValue([1]);
126
+ expect(service.generateurElectrique({ enum_type_generateur_ecs_id: 1 })).toBeTruthy();
127
+ expect(service.generateurElectrique({ enum_type_generateur_ecs_id: 2 })).toBeFalsy();
128
+ expect(tvStore.getElectriqueEcsGenerateurs).toHaveBeenCalled();
129
+ });
130
+
131
+ test('Determination du type de stockage du générateur', () => {
132
+ expect(service.typeStockage({ enum_type_stockage_ecs_id: 1 })).toBe(TypeStockage.INSTANTANE);
133
+ expect(service.typeStockage({ enum_type_stockage_ecs_id: 2 })).toBe(TypeStockage.INDEPENDANT);
134
+ expect(service.typeStockage({ enum_type_stockage_ecs_id: 3 })).toBe(TypeStockage.INTEGRE);
135
+ });
136
+ });
@@ -0,0 +1,156 @@
1
+ import { GenerateurEcsService } from './generateur-ecs.service.js';
2
+ import { inject } from 'dioma';
3
+
4
+ /**
5
+ * Calcul des besoins et pertes des installations ECS
6
+ * Données calculées
7
+ * — besoin_ecs : besoin en eau chaude sanitaire en mode conventionnel proratisé pour l'installation
8
+ * — besoin_ecs_depensier : besoin en eau chaude sanitaire en mode dépensier proratisé pour l'installation
9
+ * — besoin_ecs_depensier : pertes liées au stockage de l'ECS pour chaque générateur
10
+ * — QdwIndVc: pertes de la distribution individuelle en volume chauffé pour le mois j (Wh)
11
+ * — QdwColVc: pertes de la distribution collective en volume chauffé pour le mois j (Wh)
12
+ * — QdwColHVc: pertes de la distribution collective hors volume chauffé pour le mois j (Wh)
13
+ *
14
+ * @see Méthode de calcul 3CL-DPE 2021 (cotobre 2021) chapitre 3
15
+ */
16
+ export class InstallationEcsService {
17
+ /**
18
+ * @type {GenerateurEcsService}
19
+ */
20
+ #generateurEcsService;
21
+
22
+ /**
23
+ * @param generateurEcsService {GenerateurEcsService}
24
+ */
25
+ constructor(generateurEcsService = inject(GenerateurEcsService)) {
26
+ this.#generateurEcsService = generateurEcsService;
27
+ }
28
+
29
+ /**
30
+ * Détermination des besoins et pertes des installations ECS
31
+ *
32
+ * @param ctx {Contexte}
33
+ * @param logement {Logement}
34
+ * @param besoinEcs {{besoin_ecs: number, besoin_ecs_depensier: number}}
35
+ */
36
+ execute(ctx, logement, besoinEcs) {
37
+ const installationsEcs = logement.installation_ecs_collection?.installation_ecs || [];
38
+
39
+ installationsEcs.forEach((installationEcs) => {
40
+ /**
41
+ * Calcul des données nécessaires au calcul pour chaque générateur (perteStockage)
42
+ */
43
+ this.#generateurEcsService.execute(installationEcs);
44
+
45
+ const { besoinEcsInstallation, besoinEcsDepensierInstallation } = this.besoinEcsInstallation(
46
+ ctx,
47
+ installationEcs,
48
+ besoinEcs
49
+ );
50
+
51
+ installationEcs.donnee_intermediaire = {
52
+ besoin_ecs: besoinEcsInstallation,
53
+ besoin_ecs_depensier: besoinEcsDepensierInstallation
54
+ };
55
+
56
+ /**
57
+ * Calcul des pertes de stockage et génération au niveau de l'installation
58
+ */
59
+ this.pertesDistributionStockageEcsInstallation(
60
+ installationEcs,
61
+ besoinEcsInstallation * 1000,
62
+ besoinEcsDepensierInstallation * 1000
63
+ );
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Return true si l'installation est individuelle
69
+ * @param installationEcsDE {InstallationEcsDE}
70
+ */
71
+ isInstallationIndividuelle(installationEcsDE) {
72
+ return parseInt(installationEcsDE.enum_type_installation_id) === 1;
73
+ }
74
+
75
+ /**
76
+ * Return le besoin rationalisé de l'installation par rapport au besoin global du logement
77
+ *
78
+ * @param ctx {Contexte}
79
+ * @param installationEcs {InstallationEcs}
80
+ * @param besoinEcs {{besoin_ecs: number, besoin_ecs_depensier: number}} besoin ECS total du logement
81
+ *
82
+ * @returns {{besoinEcsInstallation: number, besoinEcsDepensierInstallation: number}}
83
+ */
84
+ besoinEcsInstallation(ctx, installationEcs, besoinEcs) {
85
+ /** @type {InstallationEcsDE} */
86
+ const installationEcsDE = installationEcs.donnee_entree;
87
+
88
+ let besoinEcsInstallation;
89
+ let besoinEcsDepensierInstallation;
90
+
91
+ const ratio =
92
+ (installationEcsDE.surface_habitable || ctx.surfaceHabitable) / ctx.surfaceHabitable;
93
+ besoinEcsInstallation = besoinEcs.besoin_ecs * ratio;
94
+ besoinEcsDepensierInstallation = besoinEcs.besoin_ecs_depensier * ratio;
95
+
96
+ return { besoinEcsInstallation, besoinEcsDepensierInstallation };
97
+ }
98
+
99
+ /**
100
+ * Pertes de distribution et stockage ECS au niveau de l'installation
101
+ * 15.2.3 - Consommation des auxiliaires de distribution d’ECS
102
+ *
103
+ * QdwIndVc: pertes de la distribution individuelle en volume chauffé pour le mois j (Wh)
104
+ * QdwColVc: pertes de la distribution collective en volume chauffé pour le mois j (Wh)
105
+ * QdwColHVc: pertes de la distribution collective hors volume chauffé pour le mois j (Wh)
106
+ *
107
+ * 11.6.1 - Pertes de stockage des ballons d’accumulation
108
+ * 11.6.2 - Pertes des ballons électriques
109
+ * Qgw: pertes brutes annuelles de stockage (Wh)
110
+ *
111
+ * @param installationEcs {InstallationEcs}
112
+ * @param besoinEcsInstallation {number} // en Wh
113
+ * @param besoinEcsDepensierInstallation {number} // en Wh
114
+ */
115
+ pertesDistributionStockageEcsInstallation(
116
+ installationEcs,
117
+ besoinEcsInstallation,
118
+ besoinEcsDepensierInstallation
119
+ ) {
120
+ /** @type {InstallationEcsDE}*/
121
+ const installationEcsDE = installationEcs.donnee_entree;
122
+ const installationIndividuelle = this.isInstallationIndividuelle(installationEcsDE);
123
+
124
+ installationEcs.donnee_utilisateur = {
125
+ QdwIndVc: {
126
+ conventionnel: 0.1 * besoinEcsInstallation,
127
+ depensier: 0.1 * besoinEcsDepensierInstallation
128
+ },
129
+ QdwColVc: {
130
+ conventionnel: installationIndividuelle ? 0 : 0.112 * besoinEcsInstallation,
131
+ depensier: installationIndividuelle ? 0 : 0.112 * besoinEcsDepensierInstallation
132
+ },
133
+ QdwColHVc: {
134
+ conventionnel: installationIndividuelle ? 0 : 0.028 * besoinEcsInstallation,
135
+ depensier: installationIndividuelle ? 0 : 0.028 * besoinEcsDepensierInstallation
136
+ }
137
+ };
138
+
139
+ // 17.2.1.1 Calcul des consommations de chauffage, de refroidissement, d’ECS et d’auxiliaires
140
+ // Pour les installations ECS collectives, pas de récupération de stockage d'ECS
141
+ installationEcs.donnee_utilisateur.QgwRecuperable = 0;
142
+ if (installationIndividuelle) {
143
+ installationEcs.donnee_utilisateur.QgwRecuperable =
144
+ installationEcs.generateur_ecs_collection?.generateur_ecs
145
+ .filter(
146
+ (generateurEcs) =>
147
+ parseInt(generateurEcs.donnee_entree.position_volume_chauffe_stockage) === 1 ||
148
+ (generateurEcs.donnee_entree.position_volume_chauffe_stockage === undefined &&
149
+ parseInt(generateurEcs.donnee_entree.position_volume_chauffe) === 1)
150
+ )
151
+ .reduce((acc, generateurEcs) => {
152
+ return acc + generateurEcs.donnee_utilisateur.Qgw;
153
+ }, 0);
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,284 @@
1
+ import { beforeEach, describe, expect, test } from 'vitest';
2
+ import { GenerateurEcsService } from './generateur-ecs.service.js';
3
+ import { InstallationEcsService } from './installation-ecs.service.js';
4
+ import corpus from '../../../../../test/corpus-sano.json';
5
+ import { expect_or, getAdemeFileJson } from '../../../../../test/test-helpers.js';
6
+ import { DpeNormalizerService } from '../../../normalizer/domain/dpe-normalizer.service.js';
7
+ import { ContexteBuilder } from '../contexte.builder.js';
8
+
9
+ /** @type {InstallationEcsService} **/
10
+ let service;
11
+
12
+ /** @type {GenerateurEcsService} **/
13
+ let generateurEcsService;
14
+
15
+ /** @type {DpeNormalizerService} **/
16
+ let normalizerService;
17
+
18
+ /** @type {ContexteBuilder} **/
19
+ let contexteBuilder;
20
+
21
+ describe('Calcul des besoins et des pertes des installations ECS', () => {
22
+ beforeEach(() => {
23
+ generateurEcsService = new GenerateurEcsService();
24
+ service = new InstallationEcsService(generateurEcsService);
25
+ normalizerService = new DpeNormalizerService();
26
+ contexteBuilder = new ContexteBuilder();
27
+ });
28
+
29
+ test.each([
30
+ {
31
+ label: "Installation ECS sans surface habitable pour l'installation",
32
+ surfaceHabitableLogement: 40,
33
+ besoin_ecs: 850,
34
+ besoin_ecs_depensier: 1050,
35
+ expected: { besoinEcsDepensierInstallation: 1050, besoinEcsInstallation: 850 }
36
+ },
37
+ {
38
+ label: "Installation ECS avec surface habitable pour l'installation",
39
+ surfaceHabitableLogement: 40,
40
+ surfaceHabitableInstallation: 50,
41
+ besoin_ecs: 850,
42
+ besoin_ecs_depensier: 1050,
43
+ expected: { besoinEcsDepensierInstallation: 1312.5, besoinEcsInstallation: 1062.5 }
44
+ }
45
+ ])(
46
+ 'Calcul des besoins proratisés pour $label',
47
+ ({
48
+ surfaceHabitableLogement,
49
+ surfaceHabitableInstallation = undefined,
50
+ besoin_ecs,
51
+ besoin_ecs_depensier,
52
+ expected
53
+ }) => {
54
+ /** @type {Contexte} */
55
+ const ctx = { surfaceHabitable: surfaceHabitableLogement };
56
+
57
+ /** @type {{besoin_ecs: number, besoin_ecs_depensier: number}} */
58
+ const besoinEcs = { besoin_ecs, besoin_ecs_depensier };
59
+
60
+ /** @type {InstallationEcs} */
61
+ const installationEcs = {
62
+ donnee_entree: { surface_habitable: surfaceHabitableInstallation }
63
+ };
64
+
65
+ const besoinEcsInstallation = service.besoinEcsInstallation(ctx, installationEcs, besoinEcs);
66
+
67
+ expect(besoinEcsInstallation).toStrictEqual(expected);
68
+ }
69
+ );
70
+
71
+ test.each([
72
+ {
73
+ label: 'Installation individuelle avec générateur hors volume chauffé',
74
+ enumTypeInstallationId: 1,
75
+ positionVolumeChauffeStockage1: 0,
76
+ positionVolumeChauffeStockage2: 0,
77
+ positionVolumeChauffeStockage3: 0,
78
+ expected: {
79
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
80
+ QdwColVc: { conventionnel: 0, depensier: 0 },
81
+ QdwColHVc: { conventionnel: 0, depensier: 0 },
82
+ QgwRecuperable: 0
83
+ }
84
+ },
85
+ {
86
+ label: 'Installation individuelle avec générateur hors volume chauffé',
87
+ enumTypeInstallationId: 1,
88
+ positionVolumeChauffeStockage2: 0,
89
+ positionVolumeChauffe2: 0,
90
+ positionVolumeChauffeStockage3: 0,
91
+ expected: {
92
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
93
+ QdwColVc: { conventionnel: 0, depensier: 0 },
94
+ QdwColHVc: { conventionnel: 0, depensier: 0 },
95
+ QgwRecuperable: 0
96
+ }
97
+ },
98
+ {
99
+ label: 'Installation individuelle avec 1 générateur en volume chauffé',
100
+ enumTypeInstallationId: 1,
101
+ positionVolumeChauffeStockage1: 0,
102
+ positionVolumeChauffeStockage2: 1,
103
+ positionVolumeChauffeStockage3: 0,
104
+ expected: {
105
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
106
+ QdwColVc: { conventionnel: 0, depensier: 0 },
107
+ QdwColHVc: { conventionnel: 0, depensier: 0 },
108
+ QgwRecuperable: 120
109
+ }
110
+ },
111
+ {
112
+ label: 'Installation individuelle avec 1 générateur en volume chauffé',
113
+ enumTypeInstallationId: 1,
114
+ positionVolumeChauffeStockage1: 0,
115
+ positionVolumeChauffe2: 1,
116
+ positionVolumeChauffeStockage3: 0,
117
+ expected: {
118
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
119
+ QdwColVc: { conventionnel: 0, depensier: 0 },
120
+ QdwColHVc: { conventionnel: 0, depensier: 0 },
121
+ QgwRecuperable: 120
122
+ }
123
+ },
124
+ {
125
+ label: 'Installation individuelle avec 2 générateurs en volume chauffé',
126
+ enumTypeInstallationId: 1,
127
+ positionVolumeChauffeStockage1: 0,
128
+ positionVolumeChauffeStockage2: 1,
129
+ positionVolumeChauffeStockage3: 1,
130
+ expected: {
131
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
132
+ QdwColVc: { conventionnel: 0, depensier: 0 },
133
+ QdwColHVc: { conventionnel: 0, depensier: 0 },
134
+ QgwRecuperable: 260
135
+ }
136
+ },
137
+ {
138
+ label: 'Installation collective avec générateur hors volume chauffé',
139
+ enumTypeInstallationId: 2,
140
+ positionVolumeChauffeStockage1: 0,
141
+ positionVolumeChauffeStockage2: 0,
142
+ positionVolumeChauffeStockage3: 0,
143
+ expected: {
144
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
145
+ QdwColVc: { conventionnel: 16.8, depensier: 30.24 },
146
+ QdwColHVc: { conventionnel: 4.2, depensier: 7.56 },
147
+ QgwRecuperable: 0
148
+ }
149
+ },
150
+ {
151
+ label: 'Installation collective avec 1 générateur en volume chauffé',
152
+ enumTypeInstallationId: 2,
153
+ positionVolumeChauffeStockage1: 0,
154
+ positionVolumeChauffeStockage2: 0,
155
+ positionVolumeChauffeStockage3: 1,
156
+ expected: {
157
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
158
+ QdwColVc: { conventionnel: 16.8, depensier: 30.24 },
159
+ QdwColHVc: { conventionnel: 4.2, depensier: 7.56 },
160
+ QgwRecuperable: 0
161
+ }
162
+ },
163
+ {
164
+ label: 'Installation collective avec 2 générateurs en volume chauffé',
165
+ enumTypeInstallationId: 2,
166
+ positionVolumeChauffeStockage1: 1,
167
+ positionVolumeChauffeStockage2: 1,
168
+ positionVolumeChauffeStockage3: 0,
169
+ expected: {
170
+ QdwIndVc: { conventionnel: 15, depensier: 27 },
171
+ QdwColVc: { conventionnel: 16.8, depensier: 30.24 },
172
+ QdwColHVc: { conventionnel: 4.2, depensier: 7.56 },
173
+ QgwRecuperable: 0
174
+ }
175
+ }
176
+ ])(
177
+ 'Calcul des pertes de distribution et de stockage $label',
178
+ ({
179
+ enumTypeInstallationId,
180
+ positionVolumeChauffeStockage1,
181
+ positionVolumeChauffeStockage2,
182
+ positionVolumeChauffe2 = undefined,
183
+ positionVolumeChauffeStockage3,
184
+ expected
185
+ }) => {
186
+ /** @type {InstallationEcs} */
187
+ const installationEcs = {
188
+ donnee_entree: { enum_type_installation_id: enumTypeInstallationId },
189
+ generateur_ecs_collection: {
190
+ generateur_ecs: [
191
+ {
192
+ donnee_entree: { position_volume_chauffe_stockage: positionVolumeChauffeStockage1 },
193
+ donnee_utilisateur: { Qgw: 100 }
194
+ },
195
+ {
196
+ donnee_entree: {
197
+ position_volume_chauffe_stockage: positionVolumeChauffeStockage2,
198
+ position_volume_chauffe: positionVolumeChauffe2
199
+ },
200
+ donnee_utilisateur: { Qgw: 120 }
201
+ },
202
+ {
203
+ donnee_entree: { position_volume_chauffe_stockage: positionVolumeChauffeStockage3 },
204
+ donnee_utilisateur: { Qgw: 140 }
205
+ }
206
+ ]
207
+ }
208
+ };
209
+
210
+ service.pertesDistributionStockageEcsInstallation(installationEcs, 150, 270);
211
+
212
+ expect(installationEcs.donnee_utilisateur.QdwIndVc.conventionnel).toBeCloseTo(
213
+ expected.QdwIndVc.conventionnel,
214
+ 5
215
+ );
216
+ expect(installationEcs.donnee_utilisateur.QdwIndVc.depensier).toBeCloseTo(
217
+ expected.QdwIndVc.depensier,
218
+ 5
219
+ );
220
+ expect(installationEcs.donnee_utilisateur.QdwColVc.conventionnel).toBeCloseTo(
221
+ expected.QdwColVc.conventionnel,
222
+ 5
223
+ );
224
+ expect(installationEcs.donnee_utilisateur.QdwColVc.depensier).toBeCloseTo(
225
+ expected.QdwColVc.depensier,
226
+ 5
227
+ );
228
+ expect(installationEcs.donnee_utilisateur.QdwColHVc.conventionnel).toBeCloseTo(
229
+ expected.QdwColHVc.conventionnel,
230
+ 5
231
+ );
232
+ expect(installationEcs.donnee_utilisateur.QdwColHVc.depensier).toBeCloseTo(
233
+ expected.QdwColHVc.depensier,
234
+ 5
235
+ );
236
+ expect(installationEcs.donnee_utilisateur.QgwRecuperable).toBeCloseTo(
237
+ expected.QgwRecuperable,
238
+ 5
239
+ );
240
+ }
241
+ );
242
+
243
+ test("Determination du caractère individuelle de l'installation", () => {
244
+ expect(service.isInstallationIndividuelle({ enum_type_installation_id: 1 })).toBeTruthy();
245
+ expect(service.isInstallationIndividuelle({ enum_type_installation_id: 2 })).toBeFalsy();
246
+ expect(service.isInstallationIndividuelle({ enum_type_installation_id: 3 })).toBeFalsy();
247
+ expect(service.isInstallationIndividuelle({ enum_type_installation_id: 4 })).toBeFalsy();
248
+ });
249
+
250
+ describe("Test d'intégration des installations ECS", () => {
251
+ test.each(corpus)('vérification des DI des installations ECS pour dpe %s', (ademeId) => {
252
+ let dpeRequest = getAdemeFileJson(ademeId);
253
+ dpeRequest = normalizerService.normalize(dpeRequest);
254
+
255
+ /** @type {Contexte} */
256
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
257
+
258
+ const installationsECS = structuredClone(
259
+ dpeRequest.logement.installation_ecs_collection?.installation_ecs || []
260
+ );
261
+ service.execute(ctx, dpeRequest.logement, {
262
+ besoin_ecs: dpeRequest.logement.sortie.apport_et_besoin.besoin_ecs,
263
+ besoin_ecs_depensier: dpeRequest.logement.sortie.apport_et_besoin.besoin_ecs_depensier
264
+ });
265
+
266
+ installationsECS.forEach((installationECS, i) => {
267
+ expect_or(
268
+ () =>
269
+ expect(installationECS.donnee_intermediaire.besoin_ecs).toBeCloseTo(
270
+ dpeRequest.logement.installation_ecs_collection.installation_ecs[i]
271
+ .donnee_intermediaire.besoin_ecs,
272
+ 2
273
+ ),
274
+ () =>
275
+ expect(installationECS.donnee_intermediaire.besoin_ecs).toBeCloseTo(
276
+ dpeRequest.logement.installation_ecs_collection.installation_ecs[i]
277
+ .donnee_intermediaire.besoin_ecs * 1000,
278
+ 2
279
+ )
280
+ );
281
+ });
282
+ });
283
+ });
284
+ });