@open3cl/engine 1.0.12 → 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.
@@ -159,6 +159,15 @@ export interface Logement {
159
159
  installation_ecs_collection: { installation_ecs: InstallationEcs[] };
160
160
  installation_chauffage_collection: { installation_chauffage: InstallationChauffage[] };
161
161
  sortie: Sortie;
162
+ donnees_de_calcul: LogementDonneesCalcul;
163
+ }
164
+
165
+ export interface LogementDonneesCalcul {
166
+ apportsInterneCh: { [key: string]: number };
167
+ apportsInterneDepensier: { [key: string]: number };
168
+ apportsSolaire: { [key: string]: number };
169
+ besoinChauffageHP: { [key: string]: number };
170
+ besoinChauffageDepensierHP: { [key: string]: number };
162
171
  }
163
172
 
164
173
  export interface Tertiaire extends Logement {}
@@ -71,7 +71,7 @@ export interface GenerateurChauffageDE extends DE {
71
71
  enum_type_generateur_ch_id: number;
72
72
  enum_usage_generateur_id: number;
73
73
  enum_type_energie_id: number;
74
- position_volume_chauffe: boolean;
74
+ position_volume_chauffe: number;
75
75
  tv_rendement_generation_id?: number;
76
76
  tv_scop_id?: number;
77
77
  tv_temp_fonc_100_id?: number;
@@ -81,7 +81,7 @@ export interface GenerateurChauffageDE extends DE {
81
81
  identifiant_reseau_chaleur?: string;
82
82
  date_arrete_reseau_chaleur?: string;
83
83
  priorite_generateur_cascade?: number;
84
- presence_ventouse?: boolean;
84
+ presence_ventouse?: number;
85
85
  presence_regulation_combustion?: boolean;
86
86
  enum_methode_saisie_carac_sys_id: number;
87
87
  enum_lien_generateur_emetteur_id: number;
@@ -15,8 +15,10 @@ export class FrTvStore extends TvStore {
15
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
16
  * — textmoy_clim_26 : Température extérieure moyenne pour un mois donné et une consigne de refroidissement à 26°C (comportement conventionnel).
17
17
  * — textmoy_clim_28 : nombre d’heures de chauffage pour un mois donné et une consigne de refroidissement à 28°C (comportement conventionnel).
18
+ * — dh19 : degrés heures de chauffage pour un mois donné à 19°C (comportement conventionnel).
19
+ * — dh21 : degrés heures de chauffage pour un mois donné à 21°C (comportement dépensier).
18
20
  *
19
- * @param type {'e', nref26' | 'nref28' | 'e_fr_26' | 'e_fr_28' | 'textmoy_clim_26' | 'textmoy_clim_28'}
21
+ * @param type {'e', nref26' | 'nref28' | 'e_fr_26' | 'e_fr_28' | 'textmoy_clim_26' | 'textmoy_clim_28'| 'dh19'| 'dh21'}
20
22
  * @param classeAltitude {string}
21
23
  * @param zoneClimatique {string}
22
24
  * @param mois {string}
@@ -31,6 +33,6 @@ export class FrTvStore extends TvStore {
31
33
  values = values[ilpa];
32
34
  }
33
35
 
34
- return values[classeAltitude][mois][zoneClimatique];
36
+ return parseFloat(values[classeAltitude][mois][zoneClimatique]);
35
37
  }
36
38
  }
@@ -5,6 +5,8 @@ import { BesoinFroidService } from './froid/besoin-froid.service.js';
5
5
  import { ApportGratuitService } from './apport_gratuit/apport-gratuit.service.js';
6
6
  import { InstallationEcsService } from '../ecs/installation-ecs.service.js';
7
7
  import { PerteEcsRecupService } from './ecs/perte-ecs-recup.service.js';
8
+ import { BesoinChService } from './ch/besoin-ch.service.js';
9
+ import { PerteChRecupService } from './ch/perte-ch-recup.service.js';
8
10
 
9
11
  /**
10
12
  * Calcul des déperditions de l’enveloppe GV
@@ -21,6 +23,11 @@ export class ApportEtBesoinService {
21
23
  */
22
24
  #besoinEcsService;
23
25
 
26
+ /**
27
+ * @type {BesoinChService}
28
+ */
29
+ #besoinChService;
30
+
24
31
  /**
25
32
  * @type {InstallationEcsService}
26
33
  */
@@ -36,6 +43,11 @@ export class ApportEtBesoinService {
36
43
  */
37
44
  #besoinFroidService;
38
45
 
46
+ /**
47
+ * @type {PerteChRecupService}
48
+ */
49
+ #perteChRecupService;
50
+
39
51
  /**
40
52
  * @type {ApportGratuitService}
41
53
  */
@@ -43,25 +55,31 @@ export class ApportEtBesoinService {
43
55
 
44
56
  /**
45
57
  * @param besoinEcsService {BesoinEcsService}
58
+ * @param besoinChService {BesoinChService}
46
59
  * @param installationEcsService {InstallationEcsService}
47
60
  * @param perteEcsRecupService {PerteEcsRecupService}
48
61
  * @param besoinFroidService {BesoinFroidService}
49
62
  * @param surfaceSudEquivalenteService {SurfaceSudEquivalenteService}
63
+ * @param perteChRecupService {PerteChRecupService}
50
64
  * @param apportGratuitService {ApportGratuitService}
51
65
  */
52
66
  constructor(
53
67
  besoinEcsService = inject(BesoinEcsService),
68
+ besoinChService = inject(BesoinChService),
54
69
  installationEcsService = inject(InstallationEcsService),
55
70
  perteEcsRecupService = inject(PerteEcsRecupService),
56
71
  besoinFroidService = inject(BesoinFroidService),
57
72
  surfaceSudEquivalenteService = inject(SurfaceSudEquivalenteService),
73
+ perteChRecupService = inject(PerteChRecupService),
58
74
  apportGratuitService = inject(ApportGratuitService)
59
75
  ) {
60
76
  this.#besoinEcsService = besoinEcsService;
77
+ this.#besoinChService = besoinChService;
61
78
  this.#installationEcsService = installationEcsService;
62
79
  this.#perteEcsRecupService = perteEcsRecupService;
63
80
  this.#besoinFroidService = besoinFroidService;
64
81
  this.#surfaceSudEquivalenteService = surfaceSudEquivalenteService;
82
+ this.#perteChRecupService = perteChRecupService;
65
83
  this.#apportGratuitService = apportGratuitService;
66
84
  }
67
85
 
@@ -80,6 +98,26 @@ export class ApportEtBesoinService {
80
98
  */
81
99
  this.#installationEcsService.execute(ctx, logement, besoinEcs);
82
100
 
101
+ const apportsInternes = this.#apportGratuitService.apportInterne(ctx, logement);
102
+ const apportsSolaires = this.#apportGratuitService.apportSolaire(ctx, logement);
103
+
104
+ const pertesEcsRecup = this.#perteEcsRecupService.execute(ctx, logement);
105
+ // Besoin de chauffage hors pertes récupérées
106
+ const besoinChHP = this.#besoinChService.execute(ctx, logement);
107
+ const pertesChauffageRecup = this.#perteChRecupService.execute(ctx, logement);
108
+
109
+ const besoinCh = {
110
+ besoin_ch: besoinChHP.besoin_ch_hp,
111
+ besoin_ch_depensier: besoinChHP.besoin_ch_depensier_hp
112
+ };
113
+ besoinCh.besoin_ch -= pertesEcsRecup.pertes_distribution_ecs_recup;
114
+ besoinCh.besoin_ch -= pertesEcsRecup.pertes_stockage_ecs_recup;
115
+ besoinCh.besoin_ch -= pertesChauffageRecup.pertes_generateur_ch_recup;
116
+
117
+ besoinCh.besoin_ch_depensier -= pertesEcsRecup.pertes_distribution_ecs_recup_depensier;
118
+ besoinCh.besoin_ch_depensier -= pertesEcsRecup.pertes_stockage_ecs_recup_depensier;
119
+ besoinCh.besoin_ch_depensier -= pertesChauffageRecup.pertes_generateur_ch_recup_depensier;
120
+
83
121
  return {
84
122
  ...besoinEcs,
85
123
  ...{
@@ -92,9 +130,11 @@ export class ApportEtBesoinService {
92
130
  v40_ecs_journalier_depensier: ctx.nadeq * 79
93
131
  },
94
132
  ...this.#besoinFroidService.execute(ctx, logement),
95
- ...this.#apportGratuitService.apportInterne(ctx, logement),
96
- ...this.#apportGratuitService.apportSolaire(ctx, logement),
97
- ...this.#perteEcsRecupService.execute(ctx, logement)
133
+ ...apportsInternes,
134
+ ...apportsSolaires,
135
+ ...pertesEcsRecup,
136
+ ...pertesChauffageRecup,
137
+ ...besoinCh
98
138
  };
99
139
  }
100
140
  }
@@ -7,6 +7,13 @@ import { BesoinFroidService } from './froid/besoin-froid.service.js';
7
7
  import { ApportGratuitService } from './apport_gratuit/apport-gratuit.service.js';
8
8
  import { PerteEcsRecupService } from './ecs/perte-ecs-recup.service.js';
9
9
  import { InstallationEcsService } from '../ecs/installation-ecs.service.js';
10
+ import { PerteChRecupService } from './ch/perte-ch-recup.service.js';
11
+ import { BesoinChService } from './ch/besoin-ch.service.js';
12
+ import corpus from '../../../../../test/corpus-sano.json';
13
+ import { getAdemeFileJson } from '../../../../../test/test-helpers.js';
14
+ import { PRECISION_PERCENT } from '../../../../../test/constant.js';
15
+ import { DpeNormalizerService } from '../../../normalizer/domain/dpe-normalizer.service.js';
16
+ import { ContexteBuilder } from '../contexte.builder.js';
10
17
 
11
18
  /** @type {SurfaceSudEquivalenteService} **/
12
19
  let surfaceSudEquivalenteService;
@@ -26,6 +33,18 @@ let perteEcsRecupService;
26
33
  /** @type {ApportGratuitService} **/
27
34
  let apportGratuitService;
28
35
 
36
+ /** @type {PerteChRecupService} **/
37
+ let perteChRecupService;
38
+
39
+ /** @type {BesoinChService} **/
40
+ let besoinChService;
41
+
42
+ /** @type {DpeNormalizerService} **/
43
+ let normalizerService;
44
+
45
+ /** @type {ContexteBuilder} **/
46
+ let contexteBuilder;
47
+
29
48
  /** @type {ApportEtBesoinService} **/
30
49
  let service;
31
50
 
@@ -37,18 +56,24 @@ describe('Calcul des apports et besoin du logement', () => {
37
56
  tvStore = new BaieVitreeTvStore();
38
57
  surfaceSudEquivalenteService = new SurfaceSudEquivalenteService(tvStore);
39
58
  besoinEcsService = new BesoinEcsService();
59
+ besoinChService = new BesoinChService();
40
60
  besoinFroidService = new BesoinFroidService();
41
61
  installationEcsService = new InstallationEcsService();
42
62
  perteEcsRecupService = new PerteEcsRecupService();
43
63
  apportGratuitService = new ApportGratuitService();
64
+ perteChRecupService = new PerteChRecupService();
44
65
  service = new ApportEtBesoinService(
45
66
  besoinEcsService,
67
+ besoinChService,
46
68
  installationEcsService,
47
69
  perteEcsRecupService,
48
70
  besoinFroidService,
49
71
  surfaceSudEquivalenteService,
72
+ perteChRecupService,
50
73
  apportGratuitService
51
74
  );
75
+ normalizerService = new DpeNormalizerService();
76
+ contexteBuilder = new ContexteBuilder();
52
77
  });
53
78
 
54
79
  test('Determination des apports et besoin du logement', () => {
@@ -57,6 +82,10 @@ describe('Calcul des apports et besoin du logement', () => {
57
82
  besoin_ecs: 1526,
58
83
  besoin_ecs_depensier: 2685.3
59
84
  });
85
+ vi.spyOn(besoinChService, 'execute').mockReturnValue({
86
+ besoin_ch_hp: 2563,
87
+ besoin_ch_depensier_hp: 3258.6
88
+ });
60
89
  vi.spyOn(besoinFroidService, 'execute').mockReturnValue({
61
90
  besoin_fr: 896,
62
91
  besoin_fr_depensier: 1025.3
@@ -73,7 +102,12 @@ describe('Calcul des apports et besoin du logement', () => {
73
102
  vi.spyOn(perteEcsRecupService, 'execute').mockReturnValue({
74
103
  pertes_distribution_ecs_recup: 354.2,
75
104
  pertes_distribution_ecs_recup_depensier: 532.6,
76
- pertes_stockage_ecs_recup: 25.9
105
+ pertes_stockage_ecs_recup: 25.9,
106
+ pertes_stockage_ecs_recup_depensier: 12.9
107
+ });
108
+ vi.spyOn(perteChRecupService, 'execute').mockReturnValue({
109
+ pertes_generateur_ch_recup: 102.2,
110
+ pertes_generateur_ch_recup_depensier: 153.4
77
111
  });
78
112
 
79
113
  /** @type {Contexte} */
@@ -84,6 +118,8 @@ describe('Calcul des apports et besoin du logement', () => {
84
118
  surface_sud_equivalente: 18.5,
85
119
  besoin_ecs: 1526,
86
120
  besoin_ecs_depensier: 2685.3,
121
+ besoin_ch: 2080.7000000000003,
122
+ besoin_ch_depensier: 2559.7,
87
123
  besoin_fr: 896,
88
124
  besoin_fr_depensier: 1025.3,
89
125
  apport_solaire_ch: 5236.9,
@@ -95,7 +131,10 @@ describe('Calcul des apports et besoin du logement', () => {
95
131
  v40_ecs_journalier_depensier: 197.5,
96
132
  pertes_distribution_ecs_recup: 354.2,
97
133
  pertes_distribution_ecs_recup_depensier: 532.6,
98
- pertes_stockage_ecs_recup: 25.9
134
+ pertes_generateur_ch_recup: 102.2,
135
+ pertes_generateur_ch_recup_depensier: 153.4,
136
+ pertes_stockage_ecs_recup: 25.9,
137
+ pertes_stockage_ecs_recup_depensier: 12.9
99
138
  });
100
139
  expect(surfaceSudEquivalenteService.execute).toHaveBeenCalledWith(ctx, logement.enveloppe);
101
140
  expect(besoinEcsService.execute).toHaveBeenCalledWith(ctx);
@@ -103,4 +142,66 @@ describe('Calcul des apports et besoin du logement', () => {
103
142
  expect(apportGratuitService.apportSolaire).toHaveBeenCalledWith(ctx, logement);
104
143
  expect(apportGratuitService.apportInterne).toHaveBeenCalledWith(ctx, logement);
105
144
  });
145
+
146
+ describe("Test d'intégration pour les besoins en chauffage", () => {
147
+ test.each(corpus)(
148
+ 'Vérification de la DI besoin_ch des besoins en chauffage pour dpe %s',
149
+ (ademeId) => {
150
+ /**
151
+ * @type {Dpe}
152
+ */
153
+ let dpeRequest = getAdemeFileJson(ademeId);
154
+ dpeRequest = normalizerService.normalize(dpeRequest);
155
+
156
+ dpeRequest.logement.donnees_de_calcul = {
157
+ apportsInterneDepensier: [],
158
+ apportsInterneCh: [],
159
+ apportsSolaire: [],
160
+ besoinChauffageHP: [],
161
+ besoinChauffageDepensierHP: []
162
+ };
163
+
164
+ /** @type {Contexte} */
165
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
166
+ const apportEtBesoin = service.execute(ctx, dpeRequest.logement);
167
+
168
+ expect(
169
+ Math.abs(
170
+ apportEtBesoin.besoin_ch - dpeRequest.logement.sortie.apport_et_besoin.besoin_ch
171
+ ) / (dpeRequest.logement.sortie.apport_et_besoin.besoin_ch || 1)
172
+ ).toBeLessThan(PRECISION_PERCENT);
173
+ }
174
+ );
175
+
176
+ test.each(corpus)(
177
+ 'Vérification de la DI besoin_ch_depensier des besoins en chauffage pour dpe %s',
178
+ (ademeId) => {
179
+ /**
180
+ * @type {Dpe}
181
+ */
182
+ let dpeRequest = getAdemeFileJson(ademeId);
183
+ dpeRequest = normalizerService.normalize(dpeRequest);
184
+
185
+ dpeRequest.logement.donnees_de_calcul = {
186
+ apportsInterneDepensier: [],
187
+ apportsInterneCh: [],
188
+ apportsSolaire: [],
189
+ besoinChauffageHP: [],
190
+ besoinChauffageDepensierHP: []
191
+ };
192
+
193
+ /** @type {Contexte} */
194
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
195
+
196
+ const apportEtBesoin = service.execute(ctx, dpeRequest.logement);
197
+
198
+ expect(
199
+ Math.abs(
200
+ apportEtBesoin.besoin_ch_depensier -
201
+ dpeRequest.logement.sortie.apport_et_besoin.besoin_ch_depensier
202
+ ) / (dpeRequest.logement.sortie.apport_et_besoin.besoin_ch_depensier || 1)
203
+ ).toBeLessThan(PRECISION_PERCENT);
204
+ }
205
+ );
206
+ });
106
207
  });
@@ -46,7 +46,7 @@ export class ApportGratuitService {
46
46
 
47
47
  return mois_liste.reduce(
48
48
  (acc, mois) => {
49
- acc.apport_solaire_ch += this.apportSolaireMois(
49
+ const apportSolaire = this.apportSolaireMois(
50
50
  ctx,
51
51
  logement.enveloppe,
52
52
  mois,
@@ -58,6 +58,9 @@ export class ApportGratuitService {
58
58
  ctx.inertie.ilpa
59
59
  )
60
60
  );
61
+ acc.apport_solaire_ch += apportSolaire;
62
+ logement.donnees_de_calcul.apportsSolaire[mois] = apportSolaire;
63
+
61
64
  if (clim.length > 0) {
62
65
  acc.apport_solaire_fr += this.apportSolaireMois(
63
66
  ctx,
@@ -84,7 +87,7 @@ export class ApportGratuitService {
84
87
 
85
88
  return mois_liste.reduce(
86
89
  (acc, mois) => {
87
- acc.apport_interne_ch += this.apportInterneMois(
90
+ const apportInterne = this.apportInterneMois(
88
91
  ctx,
89
92
  this.#frTvStore.getData(
90
93
  'nref19',
@@ -94,6 +97,19 @@ export class ApportGratuitService {
94
97
  ctx.inertie.ilpa
95
98
  )
96
99
  );
100
+ acc.apport_interne_ch += apportInterne;
101
+ logement.donnees_de_calcul.apportsInterneCh[mois] = apportInterne;
102
+ logement.donnees_de_calcul.apportsInterneDepensier[mois] = this.apportInterneMois(
103
+ ctx,
104
+ this.#frTvStore.getData(
105
+ 'nref21',
106
+ ctx.altitude.value,
107
+ ctx.zoneClimatique.value,
108
+ mois,
109
+ ctx.inertie.ilpa
110
+ )
111
+ );
112
+
97
113
  if (clim.length > 0) {
98
114
  acc.apport_interne_fr += this.apportInterneMois(
99
115
  ctx,
@@ -41,7 +41,10 @@ describe('Calcul des apports gratuits au logement', () => {
41
41
  };
42
42
 
43
43
  /** @type { Logement } **/
44
- const logement = { enveloppe: { porte_collection: {} } };
44
+ const logement = {
45
+ enveloppe: { porte_collection: {} },
46
+ donnees_de_calcul: { apportsSolaire: {} }
47
+ };
45
48
 
46
49
  if (withClimatisation) {
47
50
  logement.climatisation_collection = { climatisation: [{}] };
@@ -81,6 +84,8 @@ describe('Calcul des apports gratuits au logement', () => {
81
84
  mois
82
85
  );
83
86
  }
87
+
88
+ expect(logement.donnees_de_calcul.apportsSolaire[mois]).toBe(185000);
84
89
  }
85
90
  });
86
91
 
@@ -106,7 +111,10 @@ describe('Calcul des apports gratuits au logement', () => {
106
111
  };
107
112
 
108
113
  /** @type { Logement } **/
109
- const logement = { enveloppe: { porte_collection: {} } };
114
+ const logement = {
115
+ enveloppe: { porte_collection: {} },
116
+ donnees_de_calcul: { apportsInterneCh: {}, apportsInterneDepensier: {} }
117
+ };
110
118
 
111
119
  if (withClimatisation) {
112
120
  logement.climatisation_collection = { climatisation: [{}] };
@@ -141,6 +149,9 @@ describe('Calcul des apports gratuits au logement', () => {
141
149
  mois
142
150
  );
143
151
  }
152
+
153
+ expect(logement.donnees_de_calcul.apportsInterneCh[mois]).toBeCloseTo(1306.33, 2);
154
+ expect(logement.donnees_de_calcul.apportsInterneDepensier[mois]).toBeCloseTo(1306.33, 2);
144
155
  }
145
156
  });
146
157
 
@@ -0,0 +1,150 @@
1
+ import { mois_liste } from '../../../../../utils.js';
2
+ import { inject } from 'dioma';
3
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
4
+
5
+ /**
6
+ * Calcul du besoin en chauffage
7
+ * Chapitre 2 - Expression du besoin de chauffage
8
+ *
9
+ * Methode_de_calcul_3CL_DPE_2021 - Page 7
10
+ * Octobre 2021
11
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
12
+ */
13
+ export class BesoinChService {
14
+ /**
15
+ * @type {FrTvStore}
16
+ */
17
+ #tvStore;
18
+
19
+ /**
20
+ * @param tvStore {FrTvStore}
21
+ */
22
+ constructor(tvStore = inject(FrTvStore)) {
23
+ this.#tvStore = tvStore;
24
+ }
25
+
26
+ /**
27
+ * Besoin de chauffage hors pertes récupérées
28
+ * 9.1.1 - Consommation de chauffage
29
+ *
30
+ * @param ctx {Contexte}
31
+ * @param logement {Logement}
32
+ * @return {{besoin_ch_hp: number, besoin_ch_depensier_hp: number, fraction_apport_gratuit_ch: number, fraction_apport_gratuit_depensier_ch: number}}
33
+ */
34
+ execute(ctx, logement) {
35
+ const besoinCh = mois_liste.reduce(
36
+ (acc, mois) => {
37
+ const dh19 = this.#tvStore.getData(
38
+ 'dh19',
39
+ ctx.altitude.value,
40
+ ctx.zoneClimatique.value,
41
+ mois,
42
+ ctx.inertie.ilpa
43
+ );
44
+ const dh21 = this.#tvStore.getData(
45
+ 'dh21',
46
+ ctx.altitude.value,
47
+ ctx.zoneClimatique.value,
48
+ mois,
49
+ ctx.inertie.ilpa
50
+ );
51
+
52
+ const besoinCh = this.besoinChHorsPertesRecuperees(ctx, logement, mois, dh19);
53
+ acc.besoin_ch_hp += besoinCh.besoinChMoisHP;
54
+ acc.fraction_apport_gratuit_ch += besoinCh.fractionApportGratuitMois;
55
+ logement.donnees_de_calcul.besoinChauffageHP[mois] = besoinCh.besoinChMoisHP;
56
+
57
+ const besoinChDepensier = this.besoinChHorsPertesRecuperees(ctx, logement, mois, dh21);
58
+ acc.besoin_ch_depensier_hp += besoinChDepensier.besoinChMoisHP;
59
+ acc.fraction_apport_gratuit_depensier_ch += besoinChDepensier.fractionApportGratuitMois;
60
+ logement.donnees_de_calcul.besoinChauffageDepensierHP[mois] =
61
+ besoinChDepensier.besoinChMoisHP;
62
+
63
+ acc.dh19 += dh19;
64
+ acc.dh21 += dh21;
65
+ return acc;
66
+ },
67
+ {
68
+ besoin_ch_hp: 0,
69
+ besoin_ch_depensier_hp: 0,
70
+ fraction_apport_gratuit_ch: 0,
71
+ fraction_apport_gratuit_depensier_ch: 0,
72
+ dh19: 0,
73
+ dh21: 0
74
+ }
75
+ );
76
+
77
+ besoinCh.fraction_apport_gratuit_ch /= besoinCh.dh19;
78
+ besoinCh.fraction_apport_gratuit_depensier_ch /= besoinCh.dh21;
79
+
80
+ delete besoinCh.dh19;
81
+ delete besoinCh.dh19;
82
+
83
+ return besoinCh;
84
+ }
85
+
86
+ /**
87
+ * Fraction des besoins de chauffage couverts par les apports gratuits pour un mois donné
88
+ * 6.1 Calcul de F
89
+ *
90
+ * @param ctx {Contexte}
91
+ * @param logement {Logement}
92
+ * @param mois {string}
93
+ * @param dh {number} - degrés-heures de chauffage sur le mois j (°Ch)
94
+ * @returns {number}
95
+ */
96
+ fractionBesoinCh(ctx, logement, mois, dh) {
97
+ // Apports internes dans le logement sur le mois
98
+ const Ai = logement.donnees_de_calcul.apportsInterneCh[mois];
99
+ // Apports solaires dans le logement sur le mois durant la période de chauffe
100
+ const As = logement.donnees_de_calcul.apportsSolaire[mois];
101
+
102
+ if (dh === 0) return 0;
103
+
104
+ let pow;
105
+ switch (ctx.inertie.id) {
106
+ // Inertie très lourde ou lourde
107
+ case 1:
108
+ case 2:
109
+ pow = 3.6;
110
+ break;
111
+ // Inertie moyenne
112
+ case 3:
113
+ pow = 2.9;
114
+ break;
115
+ // Inertie légère
116
+ case 4:
117
+ pow = 2.5;
118
+ break;
119
+ }
120
+
121
+ const Xj = (As + Ai) / (logement.sortie.deperdition.deperdition_enveloppe * dh);
122
+ return (Xj - Xj ** pow) / (1 - Xj ** pow);
123
+ }
124
+
125
+ /**
126
+ * Besoin de chauffage hors pertes récupérées sur le mois (kWh) :
127
+ * 9.1.1 - Consommation de chauffage
128
+ *
129
+ * Fraction des besoins de chauffage couverts par les apports gratuits sur le mois
130
+ * 6.1 - Calcul de F
131
+ *
132
+ * @param ctx {Contexte}
133
+ * @param logement {Logement}
134
+ * @param mois {string}
135
+ * @param dh {number} - degrés-heures de chauffage sur le mois j (°Ch)
136
+ * @returns {{besoinChMoisHP: number, fractionApportGratuitMois: number}}
137
+ */
138
+ besoinChHorsPertesRecuperees(ctx, logement, mois, dh) {
139
+ // Fraction des besoins de chauffage couverts par les apports gratuits pour un mois donné
140
+ const F = this.fractionBesoinCh(ctx, logement, mois, dh);
141
+
142
+ // Besoin de chauffage d’un logement par kelvin sur le mois j (W/K)
143
+ const BV = logement.sortie.deperdition.deperdition_enveloppe * (1 - F);
144
+
145
+ return {
146
+ besoinChMoisHP: (BV * dh) / 1000,
147
+ fractionApportGratuitMois: F * dh
148
+ };
149
+ }
150
+ }