@open3cl/engine 1.0.10 → 1.0.12

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 (40) hide show
  1. package/features/dpe/domain/models/installation-ecs.model.ts +15 -2
  2. package/features/dpe/domain/models/type-habitation.model.js +1 -0
  3. package/features/dpe/domain/models/type-stockage.model.js +8 -0
  4. package/features/dpe/infrastructure/ecs/ecsTv.store.js +65 -0
  5. package/features/dpe/infrastructure/ecs/ecsTv.store.spec.js +69 -0
  6. package/features/dpe/infrastructure/{baieVitreeTv.store.js → enveloppe/baieVitreeTv.store.js} +5 -5
  7. package/features/dpe/infrastructure/{pontThermiqueTv.store.js → enveloppe/pontThermiqueTv.store.js} +3 -3
  8. package/features/dpe/infrastructure/froid/frTv.store.js +36 -0
  9. package/features/dpe/infrastructure/froid/frTv.store.spec.js +52 -0
  10. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.js +100 -0
  11. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +106 -0
  12. package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.js +134 -0
  13. package/features/engine/domain/apport_et_besoin/apport_gratuit/apport-gratuit.service.spec.js +167 -0
  14. package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.js +58 -0
  15. package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +67 -0
  16. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.js +123 -0
  17. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +205 -0
  18. package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.js +131 -0
  19. package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +229 -0
  20. package/features/engine/domain/{logement → apport_et_besoin}/surface-sud-equivalente.service.js +9 -10
  21. package/features/engine/domain/{logement → apport_et_besoin}/surface-sud-equivalente.service.spec.js +63 -43
  22. package/features/engine/domain/contexte.builder.js +53 -6
  23. package/features/engine/domain/contexte.builder.spec.js +59 -3
  24. package/features/engine/domain/ecs/generateur-ecs.service.js +106 -0
  25. package/features/engine/domain/ecs/generateur-ecs.service.spec.js +136 -0
  26. package/features/engine/domain/ecs/installation-ecs.service.js +156 -0
  27. package/features/engine/domain/ecs/installation-ecs.service.spec.js +284 -0
  28. package/features/engine/domain/engine.service.js +10 -13
  29. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.js +1 -1
  30. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +1 -1
  31. package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.js +1 -1
  32. package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +1 -1
  33. package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.js +1 -1
  34. package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +1 -1
  35. package/features/engine/domain/logement/nadeq.service.js +8 -7
  36. package/features/engine/domain/logement/nadeq.service.spec.js +6 -16
  37. package/features/engine/domain/models/contexte.model.ts +9 -0
  38. package/package.json +1 -1
  39. /package/features/dpe/infrastructure/{baieVitreeTv.store.spec.js → enveloppe/baieVitreeTv.store.spec.js} +0 -0
  40. /package/features/dpe/infrastructure/{pontThermiqueTv.store.spec.js → enveloppe/pontThermiqueTv.store.spec.js} +0 -0
@@ -3,6 +3,7 @@ import { DE } from './dpe.model';
3
3
  export interface InstallationEcs {
4
4
  donnee_entree?: InstallationEcsDE;
5
5
  donnee_intermediaire?: InstallationEcsDI;
6
+ donnee_utilisateur?: InstallationEcsDU;
6
7
  generateur_ecs_collection?: { generateur_ecs: GenerateurEcs[] };
7
8
  }
8
9
 
@@ -35,8 +36,16 @@ export interface InstallationEcsDI {
35
36
  conso_ecs_depensier: number;
36
37
  }
37
38
 
39
+ export interface InstallationEcsDU {
40
+ QdwIndVc: { conventionnel: number; depensier: number };
41
+ QdwColVc: { conventionnel: number; depensier: number };
42
+ QdwColHVc: { conventionnel: number; depensier: number };
43
+ QgwRecuperable: number;
44
+ }
45
+
38
46
  export interface GenerateurEcs {
39
47
  donnee_entree?: GenerateurEcsDE;
48
+ donnee_utilisateur?: GenerateurEcsDU;
40
49
  donnee_intermediaire?: GenerateurEcsDI;
41
50
  }
42
51
 
@@ -55,8 +64,8 @@ export interface GenerateurEcsDE extends DE {
55
64
  date_arrete_reseau_chaleur?: string;
56
65
  tv_reseau_chaleur_id?: number;
57
66
  enum_type_stockage_ecs_id: number;
58
- position_volume_chauffe: boolean;
59
- position_volume_chauffe_stockage?: boolean;
67
+ position_volume_chauffe: number;
68
+ position_volume_chauffe_stockage?: number;
60
69
  volume_stockage: number;
61
70
  presence_ventouse?: boolean;
62
71
  }
@@ -74,3 +83,7 @@ export interface GenerateurEcsDI {
74
83
  conso_ecs_depensier: number;
75
84
  rendement_stockage?: number;
76
85
  }
86
+
87
+ export interface GenerateurEcsDU {
88
+ Qgw?: number;
89
+ }
@@ -12,5 +12,6 @@ export const TypeHabitation = {
12
12
  export const TypeDpe = {
13
13
  MAISON: 'MAISON',
14
14
  APPARTEMENT: 'APPARTEMENT',
15
+ APPARTEMENT_A_PARTIR_IMMEUBLE: 'APPARTEMENT_A_PARTIR_IMMEUBLE',
15
16
  IMMEUBLE: 'IMMEUBLE'
16
17
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @type {{[key: string]: string}}
3
+ */
4
+ export const TypeStockage = {
5
+ INSTANTANE: 'INSTANTANE',
6
+ INTEGRE: 'INTEGRE',
7
+ INDEPENDANT: 'INDEPENDANT'
8
+ };
@@ -0,0 +1,65 @@
1
+ import { tvs as tv } from '../../../../tv-v2.js';
2
+ import { TvStore } from './../tv.store.js';
3
+ import { logger } from '../../../../core/util/logger/log-service.js';
4
+
5
+ /**
6
+ * Accès aux données des tables de valeurs pour le besoin en eau chaude sanitaire
7
+ */
8
+ export class EcsTvStore extends TvStore {
9
+ /**
10
+ * Température moyenne d’eau froide sanitaire sur le mois j (°C).
11
+ * La température d’eau froide est une donnée climatique mensuelle pour chacune des 8 zones climatiques
12
+ * @see 11.1 Calcul du besoin d’ECS : Tefs
13
+ *
14
+ * @param classeAltitude {string}
15
+ * @param zoneClimatique {string}
16
+ * @param mois {string}
17
+ *
18
+ * @return {number|undefined}
19
+ */
20
+ getTefs(classeAltitude, zoneClimatique, mois) {
21
+ return tv['tefs'][classeAltitude][mois][zoneClimatique];
22
+ }
23
+
24
+ /**
25
+ * Coefficient de perte du ballon de stockage
26
+ * @see 11.6.2 Pertes des ballons électriques
27
+ *
28
+ * @param enumTypeGenerateurEcsId {string}
29
+ * @param volumeStockage {number}
30
+ *
31
+ * @return {number|undefined}
32
+ */
33
+ getPertesStockage(enumTypeGenerateurEcsId, volumeStockage) {
34
+ let volumeBallon;
35
+ if (volumeStockage <= 100) volumeBallon = '≤ 100';
36
+ else if (volumeStockage <= 200) volumeBallon = '100 < ≤ 200';
37
+ else if (volumeStockage <= 300) volumeBallon = '200 < ≤ 300';
38
+ else volumeBallon = '> 300';
39
+
40
+ const cr = tv['pertes_stockage'].find(
41
+ (v) =>
42
+ v.enum_type_generateur_ecs_id.split('|').includes(enumTypeGenerateurEcsId) &&
43
+ v.volume_ballon === volumeBallon
44
+ )?.cr;
45
+
46
+ if (!cr) {
47
+ logger.error(
48
+ `Pas de valeur forfaitaire cr pour enumTypeGenerateurEcsId:${enumTypeGenerateurEcsId}, volumeBallon:${volumeBallon}`
49
+ );
50
+ return;
51
+ }
52
+
53
+ return parseFloat(cr);
54
+ }
55
+
56
+ /**
57
+ * Récupération des ids des générateurs électriques ECS
58
+ * @return {[number]}
59
+ */
60
+ getElectriqueEcsGenerateurs() {
61
+ return tv['pertes_stockage'].flatMap((v) =>
62
+ v.enum_type_generateur_ecs_id.split('|').map(Number)
63
+ );
64
+ }
65
+ }
@@ -0,0 +1,69 @@
1
+ import { beforeEach, describe, expect, test } from 'vitest';
2
+ import { EcsTvStore } from './ecsTv.store.js';
3
+
4
+ /** @type {EcsTvStore} **/
5
+ let ecsTvStore;
6
+
7
+ describe('Lecture des tables de valeurs', () => {
8
+ beforeEach(() => {
9
+ ecsTvStore = new EcsTvStore();
10
+ });
11
+
12
+ describe('Lecture des valeurs de tefs', () => {
13
+ test.each([
14
+ {
15
+ label: 'température moyenne eau froide sanitaire en janvier sur zone h1a inférieure à 400m',
16
+ classeAltitude: 'inférieur à 400m',
17
+ zoneClimatique: 'h1a',
18
+ mois: 'Janvier',
19
+ expected: 7.8
20
+ },
21
+ {
22
+ label:
23
+ 'température moyenne eau froide sanitaire en Décembre sur zone h3 inférieure entre 400-800m',
24
+ classeAltitude: '400-800m',
25
+ zoneClimatique: 'h3',
26
+ mois: 'Décembre',
27
+ expected: 10.1
28
+ }
29
+ ])(`$label`, ({ classeAltitude, zoneClimatique, mois, expected }) => {
30
+ expect(ecsTvStore.getTefs(classeAltitude, zoneClimatique, mois)).toBe(expected);
31
+ });
32
+ });
33
+
34
+ describe('Lecture des valeurs de pertes_stockage', () => {
35
+ test.each([
36
+ {
37
+ label: 'Ballon électrique à accumulation horizontal < 100L',
38
+ enumTypeGenerateurEcsId: '68',
39
+ volumeStockage: 50,
40
+ expected: 0.39
41
+ },
42
+ {
43
+ label: 'Ballon électrique à accumulation horizontal entre 100L et 200L',
44
+ enumTypeGenerateurEcsId: '68',
45
+ volumeStockage: 150,
46
+ expected: 0.33
47
+ },
48
+ {
49
+ label: 'Ballon électrique à accumulation horizontal entre 200L et 300L',
50
+ enumTypeGenerateurEcsId: '68',
51
+ volumeStockage: 250,
52
+ expected: 0.3
53
+ },
54
+ {
55
+ label: 'Ballon électrique à accumulation horizontal > 300L',
56
+ enumTypeGenerateurEcsId: '68',
57
+ volumeStockage: 301,
58
+ expected: 0.3
59
+ }
60
+ ])(`$label`, ({ enumTypeGenerateurEcsId, volumeStockage, expected }) => {
61
+ expect(ecsTvStore.getPertesStockage(enumTypeGenerateurEcsId, volumeStockage)).toBe(expected);
62
+ });
63
+
64
+ test('pas de valeur de pertes_stockage', () => {
65
+ const pertes_stockage = ecsTvStore.getPertesStockage('298', 600);
66
+ expect(pertes_stockage).toBeUndefined();
67
+ });
68
+ });
69
+ });
@@ -1,8 +1,8 @@
1
- import { tvs as tv } from '../../../tv-v2.js';
2
- import { getRange } from '../../../utils.js';
3
- import { logger } from '../../../core/util/logger/log-service.js';
4
- import { TvStore } from './tv.store.js';
5
- import enums from '../../../enums.js';
1
+ import { tvs as tv } from '../../../../tv-v2.js';
2
+ import { getRange } from '../../../../utils.js';
3
+ import { logger } from '../../../../core/util/logger/log-service.js';
4
+ import { TvStore } from './../tv.store.js';
5
+ import enums from '../../../../enums.js';
6
6
 
7
7
  /**
8
8
  * Accès aux données des tables de valeurs pour les baies vitrées
@@ -1,6 +1,6 @@
1
- import { tvs as tv } from '../../../tv-v2.js';
2
- import { logger } from '../../../core/util/logger/log-service.js';
3
- import { TvStore } from './tv.store.js';
1
+ import { tvs as tv } from '../../../../tv-v2.js';
2
+ import { logger } from '../../../../core/util/logger/log-service.js';
3
+ import { TvStore } from './../tv.store.js';
4
4
 
5
5
  /**
6
6
  * Accès aux données des tables de valeurs pour les baies vitrées
@@ -0,0 +1,36 @@
1
+ import { tvs as tv } from '../../../../tv-v2.js';
2
+ import { TvStore } from './../tv.store.js';
3
+
4
+ /**
5
+ * Accès aux données des tables de valeurs pour le besoin en froid
6
+ */
7
+ export class FrTvStore extends TvStore {
8
+ /**
9
+ * Recherche des valeurs nécessaires au calcul du besoin en froid
10
+ * @see 10.2 Calcul du besoin mensuel de froid
11
+ * — e : nombre d’heures de chauffage pour un mois donné et une consigne de refroidissement à 26°C (comportement dépensier).
12
+ * — nref26 : nombre d’heures de chauffage pour un mois donné et une consigne de refroidissement à 26°C (comportement dépensier).
13
+ * — nref28 : nombre d’heures de chauffage pour un mois donné et une consigne de refroidissement à 28°C (comportement conventionnel).
14
+ * — e_fr_26 : ensoleillement reçu en période de refroidissement pour un mois donné et une consigne de refroidissement à 26°C (comportement conventionnel).
15
+ * — e_fr_28 : ensoleillement reçu en période de refroidissement pour un mois donné et une consigne de refroidissement à 28°C (comportement conventionnel).
16
+ * — textmoy_clim_26 : Température extérieure moyenne pour un mois donné et une consigne de refroidissement à 26°C (comportement conventionnel).
17
+ * — textmoy_clim_28 : nombre d’heures de chauffage pour un mois donné et une consigne de refroidissement à 28°C (comportement conventionnel).
18
+ *
19
+ * @param type {'e', nref26' | 'nref28' | 'e_fr_26' | 'e_fr_28' | 'textmoy_clim_26' | 'textmoy_clim_28'}
20
+ * @param classeAltitude {string}
21
+ * @param zoneClimatique {string}
22
+ * @param mois {string}
23
+ * @param ilpa {number|undefined} 1 si bien à inertie lourde, 0 sinon
24
+ *
25
+ * @return {number|undefined}
26
+ */
27
+ getData(type, classeAltitude, zoneClimatique, mois, ilpa = undefined) {
28
+ let values = tv[type];
29
+
30
+ if (ilpa !== undefined) {
31
+ values = values[ilpa];
32
+ }
33
+
34
+ return values[classeAltitude][mois][zoneClimatique];
35
+ }
36
+ }
@@ -0,0 +1,52 @@
1
+ import { beforeEach, describe, expect, test } from 'vitest';
2
+ import { FrTvStore } from './frTv.store.js';
3
+
4
+ /** @type {FrTvStore} **/
5
+ let tvStore;
6
+
7
+ describe('Lecture des tables de valeurs', () => {
8
+ beforeEach(() => {
9
+ tvStore = new FrTvStore();
10
+ });
11
+
12
+ describe('lecture des valeurs par zone climatique et altitude', () => {
13
+ test.each([
14
+ {
15
+ type: 'e',
16
+ ilpa: 1,
17
+ expected: 60.45
18
+ },
19
+ {
20
+ type: 'e',
21
+ ilpa: 0,
22
+ expected: 84.66
23
+ },
24
+ {
25
+ type: 'nref26',
26
+ expected: 5.98
27
+ },
28
+ {
29
+ type: 'nref28',
30
+ expected: 1.14
31
+ },
32
+ {
33
+ type: 'e_fr_26',
34
+ expected: 5.98
35
+ },
36
+ {
37
+ type: 'e_fr_28',
38
+ expected: 1.14
39
+ },
40
+ {
41
+ type: 'textmoy_clim_26',
42
+ expected: 28.4
43
+ },
44
+ {
45
+ type: 'textmoy_clim_28',
46
+ expected: 28.4
47
+ }
48
+ ])(`type: $type, ilpa: $ilpa`, ({ type, ilpa = undefined, expected }) => {
49
+ expect(tvStore.getData(type, '400-800m', 'h1a', 'Juin', ilpa)).toBe(expected);
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,100 @@
1
+ import { inject } from 'dioma';
2
+ import { BesoinEcsService } from './ecs/besoin-ecs.service.js';
3
+ import { SurfaceSudEquivalenteService } from './surface-sud-equivalente.service.js';
4
+ import { BesoinFroidService } from './froid/besoin-froid.service.js';
5
+ import { ApportGratuitService } from './apport_gratuit/apport-gratuit.service.js';
6
+ import { InstallationEcsService } from '../ecs/installation-ecs.service.js';
7
+ import { PerteEcsRecupService } from './ecs/perte-ecs-recup.service.js';
8
+
9
+ /**
10
+ * Calcul des déperditions de l’enveloppe GV
11
+ * @see Méthode de calcul 3CL-DPE 2021 (cotobre 2021) chapitre 3
12
+ */
13
+ export class ApportEtBesoinService {
14
+ /**
15
+ * @type {SurfaceSudEquivalenteService}
16
+ */
17
+ #surfaceSudEquivalenteService;
18
+
19
+ /**
20
+ * @type {BesoinEcsService}
21
+ */
22
+ #besoinEcsService;
23
+
24
+ /**
25
+ * @type {InstallationEcsService}
26
+ */
27
+ #installationEcsService;
28
+
29
+ /**
30
+ * @type {PerteEcsRecupService}
31
+ */
32
+ #perteEcsRecupService;
33
+
34
+ /**
35
+ * @type {BesoinFroidService}
36
+ */
37
+ #besoinFroidService;
38
+
39
+ /**
40
+ * @type {ApportGratuitService}
41
+ */
42
+ #apportGratuitService;
43
+
44
+ /**
45
+ * @param besoinEcsService {BesoinEcsService}
46
+ * @param installationEcsService {InstallationEcsService}
47
+ * @param perteEcsRecupService {PerteEcsRecupService}
48
+ * @param besoinFroidService {BesoinFroidService}
49
+ * @param surfaceSudEquivalenteService {SurfaceSudEquivalenteService}
50
+ * @param apportGratuitService {ApportGratuitService}
51
+ */
52
+ constructor(
53
+ besoinEcsService = inject(BesoinEcsService),
54
+ installationEcsService = inject(InstallationEcsService),
55
+ perteEcsRecupService = inject(PerteEcsRecupService),
56
+ besoinFroidService = inject(BesoinFroidService),
57
+ surfaceSudEquivalenteService = inject(SurfaceSudEquivalenteService),
58
+ apportGratuitService = inject(ApportGratuitService)
59
+ ) {
60
+ this.#besoinEcsService = besoinEcsService;
61
+ this.#installationEcsService = installationEcsService;
62
+ this.#perteEcsRecupService = perteEcsRecupService;
63
+ this.#besoinFroidService = besoinFroidService;
64
+ this.#surfaceSudEquivalenteService = surfaceSudEquivalenteService;
65
+ this.#apportGratuitService = apportGratuitService;
66
+ }
67
+
68
+ /**
69
+ * Détermination des apports et besoins pour le bien concerné
70
+ *
71
+ * @param ctx {Contexte}
72
+ * @param logement {Logement}
73
+ * @return {ApportEtBesoin}
74
+ */
75
+ execute(ctx, logement) {
76
+ const besoinEcs = this.#besoinEcsService.execute(ctx);
77
+
78
+ /**
79
+ * Détermination des besoins et pertes des installations ECS
80
+ */
81
+ this.#installationEcsService.execute(ctx, logement, besoinEcs);
82
+
83
+ return {
84
+ ...besoinEcs,
85
+ ...{
86
+ surface_sud_equivalente: this.#surfaceSudEquivalenteService.execute(
87
+ ctx,
88
+ logement.enveloppe
89
+ ),
90
+ nadeq: ctx.nadeq,
91
+ v40_ecs_journalier: ctx.nadeq * 56,
92
+ v40_ecs_journalier_depensier: ctx.nadeq * 79
93
+ },
94
+ ...this.#besoinFroidService.execute(ctx, logement),
95
+ ...this.#apportGratuitService.apportInterne(ctx, logement),
96
+ ...this.#apportGratuitService.apportSolaire(ctx, logement),
97
+ ...this.#perteEcsRecupService.execute(ctx, logement)
98
+ };
99
+ }
100
+ }
@@ -0,0 +1,106 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { SurfaceSudEquivalenteService } from './surface-sud-equivalente.service.js';
3
+ import { BaieVitreeTvStore } from '../../../dpe/infrastructure/enveloppe/baieVitreeTv.store.js';
4
+ import { ApportEtBesoinService } from './apport-et-besoin.service.js';
5
+ import { BesoinEcsService } from './ecs/besoin-ecs.service.js';
6
+ import { BesoinFroidService } from './froid/besoin-froid.service.js';
7
+ import { ApportGratuitService } from './apport_gratuit/apport-gratuit.service.js';
8
+ import { PerteEcsRecupService } from './ecs/perte-ecs-recup.service.js';
9
+ import { InstallationEcsService } from '../ecs/installation-ecs.service.js';
10
+
11
+ /** @type {SurfaceSudEquivalenteService} **/
12
+ let surfaceSudEquivalenteService;
13
+
14
+ /** @type {BesoinEcsService} **/
15
+ let besoinEcsService;
16
+
17
+ /** @type {BesoinFroidService} **/
18
+ let besoinFroidService;
19
+
20
+ /** @type {InstallationEcsService} **/
21
+ let installationEcsService;
22
+
23
+ /** @type {PerteEcsRecupService} **/
24
+ let perteEcsRecupService;
25
+
26
+ /** @type {ApportGratuitService} **/
27
+ let apportGratuitService;
28
+
29
+ /** @type {ApportEtBesoinService} **/
30
+ let service;
31
+
32
+ /** @type {BaieVitreeTvStore} **/
33
+ let tvStore;
34
+
35
+ describe('Calcul des apports et besoin du logement', () => {
36
+ beforeEach(() => {
37
+ tvStore = new BaieVitreeTvStore();
38
+ surfaceSudEquivalenteService = new SurfaceSudEquivalenteService(tvStore);
39
+ besoinEcsService = new BesoinEcsService();
40
+ besoinFroidService = new BesoinFroidService();
41
+ installationEcsService = new InstallationEcsService();
42
+ perteEcsRecupService = new PerteEcsRecupService();
43
+ apportGratuitService = new ApportGratuitService();
44
+ service = new ApportEtBesoinService(
45
+ besoinEcsService,
46
+ installationEcsService,
47
+ perteEcsRecupService,
48
+ besoinFroidService,
49
+ surfaceSudEquivalenteService,
50
+ apportGratuitService
51
+ );
52
+ });
53
+
54
+ test('Determination des apports et besoin du logement', () => {
55
+ vi.spyOn(surfaceSudEquivalenteService, 'execute').mockReturnValue(18.5);
56
+ vi.spyOn(besoinEcsService, 'execute').mockReturnValue({
57
+ besoin_ecs: 1526,
58
+ besoin_ecs_depensier: 2685.3
59
+ });
60
+ vi.spyOn(besoinFroidService, 'execute').mockReturnValue({
61
+ besoin_fr: 896,
62
+ besoin_fr_depensier: 1025.3
63
+ });
64
+ vi.spyOn(apportGratuitService, 'apportSolaire').mockReturnValue({
65
+ apport_solaire_ch: 5236.9,
66
+ apport_solaire_fr: 145.2
67
+ });
68
+ vi.spyOn(apportGratuitService, 'apportInterne').mockReturnValue({
69
+ apport_interne_ch: 1236.9,
70
+ apport_interne_fr: 3345.2
71
+ });
72
+ vi.spyOn(installationEcsService, 'execute').mockReturnThis();
73
+ vi.spyOn(perteEcsRecupService, 'execute').mockReturnValue({
74
+ pertes_distribution_ecs_recup: 354.2,
75
+ pertes_distribution_ecs_recup_depensier: 532.6,
76
+ pertes_stockage_ecs_recup: 25.9
77
+ });
78
+
79
+ /** @type {Contexte} */
80
+ const ctx = { zoneClimatique: { id: 1 }, nadeq: 2.5 };
81
+ /** @type { Logement } **/
82
+ const logement = { enveloppe: {} };
83
+ expect(service.execute(ctx, logement)).toStrictEqual({
84
+ surface_sud_equivalente: 18.5,
85
+ besoin_ecs: 1526,
86
+ besoin_ecs_depensier: 2685.3,
87
+ besoin_fr: 896,
88
+ besoin_fr_depensier: 1025.3,
89
+ apport_solaire_ch: 5236.9,
90
+ apport_solaire_fr: 145.2,
91
+ apport_interne_ch: 1236.9,
92
+ apport_interne_fr: 3345.2,
93
+ nadeq: 2.5,
94
+ v40_ecs_journalier: 140,
95
+ v40_ecs_journalier_depensier: 197.5,
96
+ pertes_distribution_ecs_recup: 354.2,
97
+ pertes_distribution_ecs_recup_depensier: 532.6,
98
+ pertes_stockage_ecs_recup: 25.9
99
+ });
100
+ expect(surfaceSudEquivalenteService.execute).toHaveBeenCalledWith(ctx, logement.enveloppe);
101
+ expect(besoinEcsService.execute).toHaveBeenCalledWith(ctx);
102
+ expect(besoinFroidService.execute).toHaveBeenCalledWith(ctx, logement);
103
+ expect(apportGratuitService.apportSolaire).toHaveBeenCalledWith(ctx, logement);
104
+ expect(apportGratuitService.apportInterne).toHaveBeenCalledWith(ctx, logement);
105
+ });
106
+ });
@@ -0,0 +1,134 @@
1
+ import { inject } from 'dioma';
2
+ import { SurfaceSudEquivalenteService } from '../surface-sud-equivalente.service.js';
3
+ import { mois_liste } from '../../../../../utils.js';
4
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
5
+
6
+ /**
7
+ * Calcul des apports gratuits
8
+ * Chapitre 6 Détermination des apports gratuits
9
+ *
10
+ * Methode_de_calcul_3CL_DPE_2021 - Page 42
11
+ * Octobre 2021
12
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
13
+ */
14
+ export class ApportGratuitService {
15
+ /**
16
+ * @type {FrTvStore}
17
+ */
18
+ #frTvStore;
19
+
20
+ /**
21
+ * @type {SurfaceSudEquivalenteService}
22
+ */
23
+ #surfaceSudEquivalenteService;
24
+
25
+ /**
26
+ * @param frTvStore {FrTvStore}
27
+ * @param surfaceSudEquivalenteService {SurfaceSudEquivalenteService}
28
+ */
29
+ constructor(
30
+ frTvStore = inject(FrTvStore),
31
+ surfaceSudEquivalenteService = inject(SurfaceSudEquivalenteService)
32
+ ) {
33
+ this.#frTvStore = frTvStore;
34
+ this.#surfaceSudEquivalenteService = surfaceSudEquivalenteService;
35
+ }
36
+
37
+ /**
38
+ * Apports solaires gratuits du logement
39
+ *
40
+ * @param ctx {Contexte}
41
+ * @param logement {Logement}
42
+ * @returns {number}
43
+ */
44
+ apportSolaire(ctx, logement) {
45
+ const clim = logement.climatisation_collection?.climatisation || [];
46
+
47
+ return mois_liste.reduce(
48
+ (acc, mois) => {
49
+ acc.apport_solaire_ch += this.apportSolaireMois(
50
+ ctx,
51
+ logement.enveloppe,
52
+ mois,
53
+ this.#frTvStore.getData(
54
+ 'e',
55
+ ctx.altitude.value,
56
+ ctx.zoneClimatique.value,
57
+ mois,
58
+ ctx.inertie.ilpa
59
+ )
60
+ );
61
+ if (clim.length > 0) {
62
+ acc.apport_solaire_fr += this.apportSolaireMois(
63
+ ctx,
64
+ logement.enveloppe,
65
+ mois,
66
+ this.#frTvStore.getData('e_fr_28', ctx.altitude.value, ctx.zoneClimatique.value, mois)
67
+ );
68
+ }
69
+ return acc;
70
+ },
71
+ { apport_solaire_ch: 0, apport_solaire_fr: 0 }
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Apports internes gratuits du logement
77
+ *
78
+ * @param ctx {Contexte}
79
+ * @param logement {Logement}
80
+ * @returns {number}
81
+ */
82
+ apportInterne(ctx, logement) {
83
+ const clim = logement.climatisation_collection?.climatisation || [];
84
+
85
+ return mois_liste.reduce(
86
+ (acc, mois) => {
87
+ acc.apport_interne_ch += this.apportInterneMois(
88
+ ctx,
89
+ this.#frTvStore.getData(
90
+ 'nref19',
91
+ ctx.altitude.value,
92
+ ctx.zoneClimatique.value,
93
+ mois,
94
+ ctx.inertie.ilpa
95
+ )
96
+ );
97
+ if (clim.length > 0) {
98
+ acc.apport_interne_fr += this.apportInterneMois(
99
+ ctx,
100
+ this.#frTvStore.getData('nref28', ctx.altitude.value, ctx.zoneClimatique.value, mois)
101
+ );
102
+ }
103
+ return acc;
104
+ },
105
+ { apport_interne_ch: 0, apport_interne_fr: 0 }
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Apports solaires pour un mois donné
111
+ *
112
+ * @param ctx {Contexte}
113
+ * @param enveloppe {Enveloppe}
114
+ * @param mois {string}
115
+ * @param e {number} ensoleillement reçu, pour le mois, par une paroi verticale orientée au sud en absence d'ombrage (kWh/m²)
116
+ * @returns {number}
117
+ */
118
+ apportSolaireMois(ctx, enveloppe, mois, e) {
119
+ return 1000 * this.#surfaceSudEquivalenteService.ssdMois(ctx, enveloppe, mois) * e;
120
+ }
121
+
122
+ /**
123
+ * Apports internes dans le logement pour un mois donné
124
+ * Les apports internes de chaleur dus aux équipements prennent en compte l’ensemble des équipements
125
+ * « mobiliers » (cuisson, audiovisuel, informatique, lavage, froid, appareils ménagers)
126
+ *
127
+ * @param ctx {Contexte}
128
+ * @param nref {number} nombre d’heures de chauffage pour le mois
129
+ * @returns {number}
130
+ */
131
+ apportInterneMois(ctx, nref) {
132
+ return ((3.18 + 0.34) * ctx.surfaceHabitable + 90 * (132 / 168) * ctx.nadeq) * nref;
133
+ }
134
+ }