@open3cl/engine 1.0.14 → 1.0.15

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.
@@ -8,6 +8,7 @@ export interface Climatisation {
8
8
  export interface ClimatisationDE extends DE {
9
9
  tv_seer_id?: number;
10
10
  nombre_logement_echantillon?: number;
11
+ surface_clim?: number;
11
12
  enum_methode_calcul_conso_id: number;
12
13
  enum_periode_installation_fr_id: number;
13
14
  cle_repartition_clim?: number;
@@ -24,13 +24,13 @@ export class ChTvStore extends TvStore {
24
24
  }
25
25
 
26
26
  /**
27
- * @param tv {string}
27
+ * @param tvType {string}
28
28
  * @returns {any[]}
29
29
  */
30
- #getTypeGenerateurChId(tv) {
30
+ #getTypeGenerateurChId(tvType) {
31
31
  return [
32
32
  ...new Set(
33
- tv[tv].flatMap((v) =>
33
+ tv[tvType].flatMap((v) =>
34
34
  (v.enum_type_generateur_ch_id
35
35
  ? v.enum_type_generateur_ch_id.split('|').map(Number)
36
36
  : []
@@ -1,5 +1,6 @@
1
1
  import { tvs as tv } from '../../../../tv-v2.js';
2
2
  import { TvStore } from './../tv.store.js';
3
+ import { logger } from '../../../../core/util/logger/log-service.js';
3
4
 
4
5
  /**
5
6
  * Accès aux données des tables de valeurs pour le besoin en froid
@@ -35,4 +36,30 @@ export class FrTvStore extends TvStore {
35
36
 
36
37
  return parseFloat(values[classeAltitude][mois][zoneClimatique]);
37
38
  }
39
+
40
+ /**
41
+ * Coefficient d’efficience énergétique
42
+ * @see 10.3 - Les consommations de refroidissement
43
+ *
44
+ * @param zoneClimatiqueId {string}
45
+ * @param periodeInstallationId {number}
46
+ *
47
+ * @return {number|undefined}
48
+ */
49
+ getEer(zoneClimatiqueId, periodeInstallationId) {
50
+ const eer = tv['seer'].find(
51
+ (v) =>
52
+ v.enum_zone_climatique_id.split('|').includes(zoneClimatiqueId) &&
53
+ parseInt(v.enum_periode_installation_fr_id) === periodeInstallationId
54
+ )?.eer;
55
+
56
+ if (!eer) {
57
+ logger.error(
58
+ `Pas de valeur forfaitaire eer pour zoneClimatiqueId:${zoneClimatiqueId}, periodeInstallationId:${periodeInstallationId}`
59
+ );
60
+ return;
61
+ }
62
+
63
+ return parseFloat(eer);
64
+ }
38
65
  }
@@ -49,4 +49,26 @@ describe('Lecture des tables de valeurs', () => {
49
49
  expect(tvStore.getData(type, '400-800m', 'h1a', 'Juin', ilpa)).toBe(expected);
50
50
  });
51
51
  });
52
+
53
+ describe('Lecture des valeurs de coefficient d’efficience énergétique eer', () => {
54
+ test.each([
55
+ {
56
+ zoneClimatiqueId: '2',
57
+ periodeInstallationId: 1,
58
+ expected: 3.6
59
+ },
60
+ {
61
+ zoneClimatiqueId: '8',
62
+ periodeInstallationId: 2,
63
+ expected: 5.415
64
+ }
65
+ ])(`type: $type, ilpa: $ilpa`, ({ zoneClimatiqueId, periodeInstallationId, expected }) => {
66
+ expect(tvStore.getEer(zoneClimatiqueId, periodeInstallationId)).toBe(expected);
67
+ });
68
+
69
+ test('pas de valeur de eer', () => {
70
+ const eer = tvStore.getEer('8', 8);
71
+ expect(eer).toBeUndefined();
72
+ });
73
+ });
52
74
  });
@@ -1,9 +1,9 @@
1
1
  import { inject } from 'dioma';
2
2
  import { ChTvStore } from '../../../dpe/infrastructure/ch/chTv.store.js';
3
- import { TypeGenerateur } from '../../../dpe/domain/models/installation-chauffage.model.js';
4
3
  import { excel_to_js_exec } from '../../../../utils.js';
5
4
  import { TvStore } from '../../../dpe/infrastructure/tv.store.js';
6
5
  import { EmetteurChService } from './emetteur-ch.service.js';
6
+ import { TypeGenerateur } from '../../../dpe/domain/models/type-generateur.model.js';
7
7
 
8
8
  /**
9
9
  * Calcul des données de calcul pour chacun des générateurs
@@ -0,0 +1,26 @@
1
+ import { inject } from 'dioma';
2
+ import { ConsoFroidService } from './froid/conso-froid.service.js';
3
+
4
+ export class ConsoService {
5
+ /**
6
+ * @type {ConsoFroidService}
7
+ */
8
+ #consoFroidService;
9
+
10
+ /**
11
+ * @param consoFroidService {ConsoFroidService}
12
+ */
13
+ constructor(consoFroidService = inject(ConsoFroidService)) {
14
+ this.#consoFroidService = consoFroidService;
15
+ }
16
+
17
+ /**
18
+ * Calcul des consommations
19
+ *
20
+ * @param ctx {Contexte}
21
+ * @param logement {Logement}
22
+ */
23
+ execute(ctx, logement) {
24
+ this.#consoFroidService.execute(ctx, logement);
25
+ }
26
+ }
@@ -0,0 +1,29 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { ConsoFroidService } from './froid/conso-froid.service.js';
3
+ import { ConsoService } from './conso.service.js';
4
+
5
+ /** @type {ConsoService} **/
6
+ let service;
7
+
8
+ /** @type {ConsoFroidService} **/
9
+ let consoFroidService;
10
+
11
+ describe('Calcul des consos du logement', () => {
12
+ beforeEach(() => {
13
+ consoFroidService = new ConsoFroidService();
14
+ service = new ConsoService(consoFroidService);
15
+ });
16
+
17
+ test('Determination des consommmations du logement', () => {
18
+ vi.spyOn(consoFroidService, 'execute').mockReturnThis();
19
+
20
+ /** @type {Contexte} */
21
+ const ctx = { zoneClimatique: { id: 1 }, nadeq: 2.5 };
22
+ /** @type { Logement } **/
23
+ const logement = { enveloppe: {} };
24
+
25
+ service.execute(ctx, logement);
26
+
27
+ expect(consoFroidService.execute).toHaveBeenCalledWith(ctx, logement);
28
+ });
29
+ });
@@ -0,0 +1,94 @@
1
+ import { inject } from 'dioma';
2
+ import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
3
+
4
+ /**
5
+ * Calcul de la consommation en froid
6
+ * Chapitre 10.3 - Les consommations de refroidissement
7
+ *
8
+ * Methode_de_calcul_3CL_DPE_2021 - Page 69
9
+ * Octobre 2021
10
+ * @see consolide_anne…arrete_du_31_03_2021_relatif_aux_methodes_et_procedures_applicables.pdf
11
+ */
12
+ export class ConsoFroidService {
13
+ /**
14
+ * @type {FrTvStore}
15
+ */
16
+ #frStore;
17
+
18
+ /**
19
+ * @param frStore {FrTvStore}
20
+ */
21
+ constructor(frStore = inject(FrTvStore)) {
22
+ this.#frStore = frStore;
23
+ }
24
+
25
+ /**
26
+ * @param ctx {Contexte}
27
+ * @param logement {Logement}
28
+ * @return {{besoin_fr: number, besoin_fr_depensier: number}}
29
+ */
30
+ execute(ctx, logement) {
31
+ const climatisations = logement.climatisation_collection?.climatisation || [];
32
+
33
+ climatisations.forEach((climatisation) => {
34
+ const consos = this.consoFroid(ctx, logement.sortie.apport_et_besoin, climatisation);
35
+
36
+ climatisation.donnee_intermediaire ??= {};
37
+ climatisation.donnee_intermediaire.besoin_fr = consos.besoin_fr;
38
+ climatisation.donnee_intermediaire.besoin_fr_depensier = consos.besoin_fr_depensier;
39
+ climatisation.donnee_intermediaire.conso_fr = consos.conso_fr;
40
+ climatisation.donnee_intermediaire.conso_fr_depensier = consos.conso_fr_depensier;
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Calcul de la consommation en froid d'un système de refroidissement
46
+ *
47
+ * @param ctx {Contexte}
48
+ * @param apportEtBesoin {ApportEtBesoin}
49
+ * @param climatisation {Climatisation}
50
+ * @returns {{besoin_fr: number, besoin_fr_depensier: number, conso_fr: number, conso_fr_depensier: number}}
51
+ */
52
+ consoFroid(ctx, apportEtBesoin, climatisation) {
53
+ let eer;
54
+
55
+ /** @type {ClimatisationDE} **/
56
+ const climatisationDE = climatisation.donnee_entree;
57
+
58
+ /** @type {ClimatisationDI} **/
59
+ const climatisationDI = climatisation.donnee_intermediaire || {};
60
+
61
+ const ratioSurfaceClimatisation =
62
+ (climatisationDE.surface_clim || ctx.surfaceHabitable) / ctx.surfaceHabitable;
63
+ const besoin_fr = apportEtBesoin.besoin_fr * ratioSurfaceClimatisation;
64
+ const besoin_fr_depensier = apportEtBesoin.besoin_fr_depensier * ratioSurfaceClimatisation;
65
+
66
+ /**
67
+ * Si la méthode de saisie n'est pas "Valeur forfaitaire" mais "caractéristiques saisies"
68
+ * Documentation 3CL : "Pour les installations récentes ou recommandées, les caractéristiques réelles des chaudières présentées sur les bases
69
+ * de données professionnelles peuvent être utilisées."
70
+ *
71
+ * 6 - caractéristiques saisies à partir de la plaque signalétique ou d'une documentation technique du système thermodynamique : scop/cop/eer
72
+ * 7 - déterminé à partir du rset/rsee( etude rt2012/re2020)
73
+ * 8 - seer saisi pour permettre la saisie de réseau de froid ou de système de climatisations qui ne sont pas éléctriques
74
+ */
75
+ if (
76
+ ![6, 7, 8].includes(parseInt(climatisationDE.enum_methode_saisie_carac_sys_id)) ||
77
+ !climatisationDI.eer
78
+ ) {
79
+ eer = this.#frStore.getEer(
80
+ ctx.zoneClimatique.id,
81
+ parseInt(climatisationDE.enum_periode_installation_fr_id)
82
+ );
83
+ } else {
84
+ eer = climatisationDI.eer;
85
+ }
86
+
87
+ return {
88
+ besoin_fr,
89
+ besoin_fr_depensier,
90
+ conso_fr: (0.9 * besoin_fr) / eer,
91
+ conso_fr_depensier: (0.9 * besoin_fr_depensier) / eer
92
+ };
93
+ }
94
+ }
@@ -0,0 +1,152 @@
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 { ConsoFroidService } from './conso-froid.service.js';
8
+
9
+ /** @type {ConsoFroidService} **/
10
+ let service;
11
+
12
+ /** @type {DpeNormalizerService} **/
13
+ let normalizerService;
14
+
15
+ /** @type {ContexteBuilder} **/
16
+ let contexteBuilder;
17
+
18
+ /** @type {FrTvStore} **/
19
+ let tvStore;
20
+
21
+ describe('Calcul des consos en froid du logement', () => {
22
+ beforeEach(() => {
23
+ tvStore = new FrTvStore();
24
+ service = new ConsoFroidService(tvStore);
25
+ normalizerService = new DpeNormalizerService();
26
+ contexteBuilder = new ContexteBuilder();
27
+ });
28
+
29
+ test.each([
30
+ {
31
+ label: 'Climatisation avec méthode de saisie 6, sans surface clim',
32
+ enumMethodeSaisieCaracSysId: 6,
33
+ eerInitial: 125,
34
+ expected: { conso_fr: 7.2, conso_fr_depensier: 10.8 }
35
+ },
36
+ {
37
+ label: 'Climatisation avec méthode de saisie 7, sans surface clim',
38
+ enumMethodeSaisieCaracSysId: 7,
39
+ eerInitial: 125,
40
+ expected: { conso_fr: 7.2, conso_fr_depensier: 10.8 }
41
+ },
42
+ {
43
+ label: 'Climatisation avec méthode de saisie 8, sans surface clim',
44
+ enumMethodeSaisieCaracSysId: 8,
45
+ eerInitial: 125,
46
+ expected: { conso_fr: 7.2, conso_fr_depensier: 10.8 }
47
+ },
48
+ {
49
+ label: 'Climatisation avec méthode de saisie 6, avec surface clim',
50
+ enumMethodeSaisieCaracSysId: 6,
51
+ eerInitial: 125,
52
+ surfaceClim: 80,
53
+ expected: { conso_fr: 5.76, conso_fr_depensier: 8.64 }
54
+ },
55
+ {
56
+ label: 'Climatisation avec méthode de saisie 7, avec surface clim',
57
+ enumMethodeSaisieCaracSysId: 7,
58
+ eerInitial: 125,
59
+ surfaceClim: 80,
60
+ expected: { conso_fr: 5.76, conso_fr_depensier: 8.64 }
61
+ },
62
+ {
63
+ label: 'Climatisation avec méthode de saisie 8, avec surface clim',
64
+ enumMethodeSaisieCaracSysId: 8,
65
+ eerInitial: 125,
66
+ surfaceClim: 80,
67
+ expected: { conso_fr: 5.76, conso_fr_depensier: 8.64 }
68
+ },
69
+ {
70
+ label: 'Climatisation avec méthode de saisie 5, sans surface clim',
71
+ enumMethodeSaisieCaracSysId: 5,
72
+ eer: 90,
73
+ expected: { conso_fr: 10, conso_fr_depensier: 15 }
74
+ },
75
+ {
76
+ label: 'Climatisation avec méthode de saisie 5, avec surface clim',
77
+ enumMethodeSaisieCaracSysId: 5,
78
+ eer: 90,
79
+ surfaceClim: 80,
80
+ expected: { conso_fr: 8, conso_fr_depensier: 12 }
81
+ }
82
+ ])(
83
+ 'Détermination des consommations des systèmes de refroidissement pour $label',
84
+ ({
85
+ enumMethodeSaisieCaracSysId,
86
+ surfaceClim = undefined,
87
+ eer = undefined,
88
+ eerInitial,
89
+ expected
90
+ }) => {
91
+ vi.spyOn(tvStore, 'getEer').mockReturnValue(eer);
92
+
93
+ /** @type {Contexte} */
94
+ const contexte = {
95
+ zoneClimatique: { id: 1 },
96
+ surfaceHabitable: 100
97
+ };
98
+
99
+ /** @type {Climatisation} */
100
+ const climatisation = {
101
+ donnee_entree: {
102
+ enum_methode_saisie_carac_sys_id: enumMethodeSaisieCaracSysId,
103
+ surface_clim: surfaceClim
104
+ },
105
+ donnee_intermediaire: { eer: eerInitial }
106
+ };
107
+
108
+ /** @type {ApportEtBesoin} */
109
+ const apportEtBesoin = {
110
+ besoin_fr: 1000,
111
+ besoin_fr_depensier: 1500
112
+ };
113
+
114
+ const consoFroid = service.consoFroid(contexte, apportEtBesoin, climatisation);
115
+ expect(consoFroid.conso_fr).toBeCloseTo(expected.conso_fr, 2);
116
+ expect(consoFroid.conso_fr_depensier).toBeCloseTo(expected.conso_fr_depensier, 2);
117
+ }
118
+ );
119
+
120
+ describe("Test d'intégration pour le besoin en froid", () => {
121
+ test.each(corpus)('vérification des sorties besoin_fr et conso_fr pour dpe %s', (ademeId) => {
122
+ /**
123
+ * @type {Dpe}
124
+ */
125
+ let dpeRequest = getAdemeFileJson(ademeId);
126
+ dpeRequest = normalizerService.normalize(dpeRequest);
127
+
128
+ /** @type {Contexte} */
129
+ const ctx = contexteBuilder.fromDpe(dpeRequest);
130
+
131
+ const climatisations = structuredClone(
132
+ dpeRequest.logement.climatisation_collection?.climatisation || []
133
+ );
134
+ service.execute(ctx, dpeRequest.logement);
135
+
136
+ climatisations.forEach((climatisation, i) => {
137
+ console.log('climatisation.donnee_intermediaire.besoin_fr');
138
+ expect(climatisation.donnee_intermediaire.besoin_fr).toBeCloseTo(
139
+ dpeRequest.logement.climatisation_collection.climatisation[i].donnee_intermediaire
140
+ .besoin_fr,
141
+ 2
142
+ );
143
+ console.log(climatisation.donnee_intermediaire.conso_fr);
144
+ expect(climatisation.donnee_intermediaire.conso_fr).toBeCloseTo(
145
+ dpeRequest.logement.climatisation_collection.climatisation[i].donnee_intermediaire
146
+ .conso_fr,
147
+ 2
148
+ );
149
+ });
150
+ });
151
+ });
152
+ });
@@ -3,6 +3,7 @@ import { ContexteBuilder } from './contexte.builder.js';
3
3
  import { DeperditionEnveloppeService } from './enveloppe/deperdition-enveloppe.service.js';
4
4
  import { logger } from '../../../core/util/logger/log-service.js';
5
5
  import { ApportEtBesoinService } from './apport_et_besoin/apport-et-besoin.service.js';
6
+ import { ConsoService } from './conso/conso.service.js';
6
7
 
7
8
  export class EngineService {
8
9
  /**
@@ -15,6 +16,11 @@ export class EngineService {
15
16
  */
16
17
  #apportEtBesoinService;
17
18
 
19
+ /**
20
+ * @type {ConsoService}
21
+ */
22
+ #consoService;
23
+
18
24
  /**
19
25
  * @type {ContexteBuilder}
20
26
  */
@@ -23,15 +29,18 @@ export class EngineService {
23
29
  /**
24
30
  * @param deperditionService {DeperditionEnveloppeService}
25
31
  * @param apportEtBesoinService {ApportEtBesoinService}
32
+ * @param consoService {ConsoService}
26
33
  * @param contextBuilder {ContexteBuilder}
27
34
  */
28
35
  constructor(
29
36
  deperditionService = inject(DeperditionEnveloppeService),
30
37
  apportEtBesoinService = inject(ApportEtBesoinService),
38
+ consoService = inject(ConsoService),
31
39
  contextBuilder = inject(ContexteBuilder)
32
40
  ) {
33
41
  this.#deperditionService = deperditionService;
34
42
  this.#apportEtBesoinService = apportEtBesoinService;
43
+ this.#consoService = consoService;
35
44
  this.#contextBuilder = contextBuilder;
36
45
  }
37
46
 
@@ -98,6 +107,7 @@ export class EngineService {
98
107
  // Calcul des consommations chauffage
99
108
 
100
109
  // Calcul des consommations de froid
110
+ this.#consoService.execute(ctx, proceededDpe.logement);
101
111
 
102
112
  // Calcul des consommations ECS
103
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {