@open3cl/engine 1.6.1 → 1.6.2
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/10_besoin_fr.spec.js +122 -0
- package/10_clim.spec.js +116 -0
- package/11_besoin_ecs.spec.js +69 -0
- package/11_ecs.spec.js +282 -0
- package/11_nadeq.spec.js +37 -0
- package/12.4_pac.spec.js +131 -0
- package/13.2_generateur_combustion.js +2 -0
- package/13.2_generateur_combustion.spec.js +537 -0
- package/13.2_generateur_combustion_bouilleur.js +4 -1
- package/13.2_generateur_combustion_bouilleur.spec.js +74 -0
- package/13.2_generateur_combustion_ch.spec.js +323 -0
- package/13.2_generateur_combustion_chaudiere.spec.js +85 -0
- package/13.2_generateur_pac.spec.js +64 -0
- package/13_rendement_distribution_ecs.spec.js +38 -0
- package/14_generateur_ecs.js +4 -4
- package/14_generateur_ecs.spec.js +745 -0
- package/15_conso_aux.spec.js +225 -0
- package/16.2_production_enr.spec.js +188 -0
- package/16_conso_eclairage.spec.js +62 -0
- package/2021_04_13_confort_ete.spec.js +159 -0
- package/2021_04_13_qualite_isolation.js +1 -1
- package/2021_04_13_qualite_isolation.spec.js +286 -0
- package/3.1_b.js +1 -1
- package/3.1_b.spec.js +367 -0
- package/3.2.1_mur.spec.js +812 -39
- package/3.2.2_plancher_bas.spec.js +643 -78
- package/3.2.3_plancher_haut.spec.js +452 -0
- package/3.3.1.4_porte.spec.js +114 -0
- package/3.3_baie_vitree.spec.js +449 -295
- package/3.4_pont_thermique.spec.js +890 -0
- package/3_deperdition.spec.js +397 -0
- package/4_ventilation.js +1 -1
- package/4_ventilation.spec.js +406 -0
- package/5_conso_ventilation.spec.js +186 -0
- package/6.2_surface_sud_equivalente.spec.js +138 -0
- package/8_intermittence.spec.js +33 -0
- package/9_besoin_ch.js +2 -2
- package/9_besoin_ch.spec.js +381 -0
- package/9_chauffage.js +4 -0
- package/9_chauffage.spec.js +478 -92
- package/9_conso_ch.spec.js +502 -248
- package/9_emetteur_ch.spec.js +339 -0
- package/9_generateur_ch.spec.js +467 -81
- package/apport_et_besoin.spec.js +126 -0
- package/conso.js +1 -1
- package/conso.spec.js +471 -82
- package/core/assets/domain/add-additionnal-ue-values-tables.spec.js +53 -0
- package/core/assets/domain/synchronize-solicitations-tables.spec.js +25 -0
- package/core/assets/domain/synchronize-valeur-tables.spec.js +48 -0
- package/core/conf/infrastructure/application.config.spec.js +35 -0
- package/core/file/infrastructure/adapter/csv-parser.store.spec.js +31 -13
- package/core/file/infrastructure/adapter/file.store.spec.js +148 -12
- package/core/tv/infrastructure/assets/additional-ue-values.spec.js +36 -0
- package/core/util/infrastructure/object-util.spec.js +20 -0
- package/core/util/logger/log-service.js +16 -7
- package/core/util/logger/log-service.spec.js +101 -0
- package/dpe-sanitizer.service.spec.js +138 -0
- package/engine.js +4 -1
- package/engine.spec.js +854 -0
- package/enums.spec.js +46 -0
- package/features/dpe/domain/models/type-generateur.model.js +1 -2
- package/features/dpe/domain/models/type-generateur.model.spec.js +25 -0
- package/features/dpe/domain/models/type-habitation.model.spec.js +41 -0
- package/features/dpe/domain/models/type-stockage.model.spec.js +18 -0
- package/features/dpe/domain/models/type-ventilation.model.spec.js +19 -0
- package/features/dpe/infrastructure/ch/chTv.store.spec.js +4 -4
- package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.js +14 -2
- package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.spec.js +16 -0
- package/features/dpe/infrastructure/tv.store.js +28 -9
- package/features/dpe/infrastructure/tv.store.spec.js +44 -0
- package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +2 -1
- package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.spec.js +18 -0
- package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +33 -1
- package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +21 -1
- package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +41 -1
- package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +44 -1
- package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +2 -1
- package/features/engine/domain/ch/generateur-ch.service.js +2 -0
- package/features/engine/domain/ch/generateur-ch.service.spec.js +117 -2
- package/features/engine/domain/ch/installation-ch.service.spec.js +14 -0
- package/features/engine/domain/conso/froid/conso-froid.service.spec.js +82 -1
- package/features/engine/domain/constants.spec.js +14 -0
- package/features/engine/domain/contexte.builder.spec.js +21 -0
- package/features/engine/domain/ecs/generateur-ecs.service.spec.js +11 -0
- package/features/engine/domain/ecs/installation-ecs.service.spec.js +55 -2
- package/features/engine/domain/engine.service.spec.js +227 -4
- package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +124 -1
- package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +270 -2
- package/features/engine/domain/enveloppe/deperdition.service.spec.js +141 -0
- package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +2 -1
- package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +117 -1
- package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +134 -1
- package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +126 -1
- package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +18 -1
- package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +2 -1
- package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +67 -1
- package/features/normalizer/domain/dpe-normalizer.service.js +1 -1
- package/features/normalizer/domain/dpe-normalizer.service.spec.js +53 -2
- package/ficheTechnique.spec.js +26 -0
- package/index.spec.js +54 -0
- package/package.json +5 -2
- package/preprocessor/index.js +20 -0
- package/preprocessor/index.spec.js +15 -0
- package/tv.spec.js +38 -0
- package/utils.js +43 -1
- package/utils.spec.js +518 -2
- package/output.js +0 -1
|
@@ -5,6 +5,7 @@ import { ContexteBuilder } from '../contexte.builder.js';
|
|
|
5
5
|
import { SurfaceSudEquivalenteService } from './surface-sud-equivalente.service.js';
|
|
6
6
|
import { getAdemeFileJson } from '../../../../../test/test-helpers.js';
|
|
7
7
|
import { BaieVitreeTvStore } from '../../../dpe/infrastructure/enveloppe/baieVitreeTv.store.js';
|
|
8
|
+
import { describeIntegration } from '../../../../../test/helpers/integration-test.js';
|
|
8
9
|
|
|
9
10
|
/** @type {SurfaceSudEquivalenteService} **/
|
|
10
11
|
let service;
|
|
@@ -206,7 +207,7 @@ describe('Calcul de la surface sud équivalente du logement', () => {
|
|
|
206
207
|
});
|
|
207
208
|
});
|
|
208
209
|
|
|
209
|
-
|
|
210
|
+
describeIntegration("Test d'intégration pour surface sud équivalente", () => {
|
|
210
211
|
test.each(corpus)(
|
|
211
212
|
'vérification des DI de la surface sud équivalente pour dpe %s',
|
|
212
213
|
(ademeId) => {
|
|
@@ -79,6 +79,8 @@ export class GenerateurChService {
|
|
|
79
79
|
generateurChauffage.donnee_utilisateur.generateur = this.#chTvStore.getGenerateurCombustion(
|
|
80
80
|
generateurChauffageDE.enum_type_generateur_ch_id,
|
|
81
81
|
generateurChauffage.donnee_intermediaire.pn /
|
|
82
|
+
/* c8 ignore next 2 -- repli défensif inatteignable : donnee_utilisateur.ratio_virtualisation
|
|
83
|
+
est déjà défini juste au-dessus (valeur de l'installation ou 1), donc toujours truthy ici. */
|
|
82
84
|
((generateurChauffage.donnee_utilisateur.ratio_virtualisation || 1) * 1000)
|
|
83
85
|
);
|
|
84
86
|
|
|
@@ -4,11 +4,12 @@ import { GenerateurChService } from './generateur-ch.service.js';
|
|
|
4
4
|
import { DpeNormalizerService } from '../../../normalizer/domain/dpe-normalizer.service.js';
|
|
5
5
|
import corpus from '../../../../../test/corpus-sano.json';
|
|
6
6
|
import { getAdemeFileJson } from '../../../../../test/test-helpers.js';
|
|
7
|
-
import { TypeGenerateur } from '../../../dpe/domain/models/
|
|
7
|
+
import { TypeGenerateur } from '../../../dpe/domain/models/type-generateur.model.js';
|
|
8
8
|
import { ChTvStore } from '../../../dpe/infrastructure/ch/chTv.store.js';
|
|
9
9
|
import { ContexteBuilder } from '../contexte.builder.js';
|
|
10
10
|
import { TvStore } from '../../../dpe/infrastructure/tv.store.js';
|
|
11
11
|
import { EmetteurChService } from './emetteur-ch.service.js';
|
|
12
|
+
import { describeIntegration } from '../../../../../test/helpers/integration-test.js';
|
|
12
13
|
|
|
13
14
|
/** @type {GenerateurChService} **/
|
|
14
15
|
let service;
|
|
@@ -563,7 +564,121 @@ describe('Calcul des caractéristiques des générateurs de chauffage', () => {
|
|
|
563
564
|
});
|
|
564
565
|
});
|
|
565
566
|
|
|
566
|
-
|
|
567
|
+
test('Générateur à combustion sans pn : calcul de la puissance nominale et filtrage des émetteurs', () => {
|
|
568
|
+
// Type 76 (chaudière fioul) : générateur à combustion ET chaudière, pour que le calcul
|
|
569
|
+
// des températures de fonctionnement (utilisant les émetteurs filtrés) soit exécuté.
|
|
570
|
+
vi.spyOn(chTvStore, 'getCombustionGenerateurs').mockReturnValue([76]);
|
|
571
|
+
vi.spyOn(chTvStore, 'getPacGenerateurs').mockReturnValue([2]);
|
|
572
|
+
vi.spyOn(chTvStore, 'getGenerateurCombustion').mockReturnValue({});
|
|
573
|
+
// pn absent dans donnee_intermediaire -> execute doit appeler this.pn(ctx, logement).
|
|
574
|
+
const pnSpy = vi.spyOn(service, 'pn').mockReturnValue(20000);
|
|
575
|
+
vi.spyOn(emetteurChService, 'temperatureFonctionnement').mockReturnValue({
|
|
576
|
+
temp_fonc_30: 18,
|
|
577
|
+
temp_fonc_100: 25
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/** @type {GenerateurChauffage} */
|
|
581
|
+
const generateurChauffage = {
|
|
582
|
+
donnee_entree: {
|
|
583
|
+
enum_methode_saisie_carac_sys_id: 4,
|
|
584
|
+
enum_type_generateur_ch_id: 76,
|
|
585
|
+
enum_lien_generateur_emetteur_id: 'L1',
|
|
586
|
+
presence_ventouse: 1
|
|
587
|
+
},
|
|
588
|
+
// pas de pn : oblige au calcul de la puissance nominale
|
|
589
|
+
donnee_intermediaire: { rpn: 0.95, rpint: 0.68, qp0: 125 }
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// Deux émetteurs : un seul est lié au générateur (même enum_lien_generateur_emetteur_id).
|
|
593
|
+
const emetteurLie = {
|
|
594
|
+
donnee_entree: { enum_lien_generateur_emetteur_id: 'L1', surface_chauffee: 80 },
|
|
595
|
+
donnee_intermediaire: { rendement_emission: 0.86 }
|
|
596
|
+
};
|
|
597
|
+
const emetteurNonLie = {
|
|
598
|
+
donnee_entree: { enum_lien_generateur_emetteur_id: 'L2', surface_chauffee: 40 },
|
|
599
|
+
donnee_intermediaire: { rendement_emission: 0.9 }
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const installationChauffage = {
|
|
603
|
+
donnee_entree: { ratio_virtualisation: 1 },
|
|
604
|
+
generateur_chauffage_collection: { generateur_chauffage: [generateurChauffage] },
|
|
605
|
+
emetteur_chauffage_collection: { emetteur_chauffage: [emetteurLie, emetteurNonLie] }
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
/** @type {Logement} */
|
|
609
|
+
const logement = {
|
|
610
|
+
installation_chauffage_collection: { installation_chauffage: [installationChauffage] },
|
|
611
|
+
sortie: { deperdition: { deperdition_enveloppe: 300 } }
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
/** @type {Contexte} */
|
|
615
|
+
const ctx = { altitude: { id: 1 }, zoneClimatique: { id: 1 } };
|
|
616
|
+
|
|
617
|
+
service.execute(ctx, logement, installationChauffage);
|
|
618
|
+
|
|
619
|
+
// La puissance nominale a été calculée puis affectée
|
|
620
|
+
expect(pnSpy).toHaveBeenCalledWith(ctx, logement);
|
|
621
|
+
expect(generateurChauffage.donnee_intermediaire.pn).toBe(20000);
|
|
622
|
+
|
|
623
|
+
// getGenerateurCombustion utilise pn / (ratio * 1000) = 20000 / 1000 = 20
|
|
624
|
+
expect(chTvStore.getGenerateurCombustion).toHaveBeenCalledWith(76, 20);
|
|
625
|
+
|
|
626
|
+
// Seul l'émetteur lié est transmis au calcul des températures de fonctionnement
|
|
627
|
+
expect(emetteurChService.temperatureFonctionnement).toHaveBeenCalledWith(
|
|
628
|
+
ctx,
|
|
629
|
+
generateurChauffage.donnee_entree,
|
|
630
|
+
[emetteurLie]
|
|
631
|
+
);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('execute : installation sans collection de générateurs ne fait rien', () => {
|
|
635
|
+
// Valeur de repli [] quand generateur_chauffage_collection est absente.
|
|
636
|
+
const combustionSpy = vi.spyOn(chTvStore, 'getCombustionGenerateurs');
|
|
637
|
+
|
|
638
|
+
/** @type {Contexte} */
|
|
639
|
+
const ctx = { zoneClimatique: { id: 1 } };
|
|
640
|
+
|
|
641
|
+
expect(() => service.execute(ctx, {}, { donnee_entree: {} })).not.toThrow();
|
|
642
|
+
expect(combustionSpy).not.toHaveBeenCalled();
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test('execute : ratio de virtualisation par défaut à 1 quand non renseigné', () => {
|
|
646
|
+
vi.spyOn(chTvStore, 'getCombustionGenerateurs').mockReturnValue([1]);
|
|
647
|
+
vi.spyOn(chTvStore, 'getPacGenerateurs').mockReturnValue([2]);
|
|
648
|
+
vi.spyOn(chTvStore, 'getGenerateurCombustion').mockReturnValue({});
|
|
649
|
+
|
|
650
|
+
/** @type {GenerateurChauffage} */
|
|
651
|
+
const generateurChauffage = {
|
|
652
|
+
donnee_entree: {
|
|
653
|
+
enum_methode_saisie_carac_sys_id: 4,
|
|
654
|
+
enum_type_generateur_ch_id: 1,
|
|
655
|
+
presence_ventouse: 1
|
|
656
|
+
},
|
|
657
|
+
donnee_intermediaire: { rpn: 0.95, rpint: 0.68, qp0: 125, pn: 15000 }
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const installationChauffage = {
|
|
661
|
+
// donnee_entree sans ratio_virtualisation : repli sur 1
|
|
662
|
+
donnee_entree: {},
|
|
663
|
+
generateur_chauffage_collection: { generateur_chauffage: [generateurChauffage] }
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
/** @type {Logement} */
|
|
667
|
+
const logement = {
|
|
668
|
+
installation_chauffage_collection: { installation_chauffage: [installationChauffage] }
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
/** @type {Contexte} */
|
|
672
|
+
const ctx = { zoneClimatique: { id: 1 } };
|
|
673
|
+
|
|
674
|
+
service.execute(ctx, logement, installationChauffage);
|
|
675
|
+
|
|
676
|
+
expect(generateurChauffage.donnee_utilisateur.ratio_virtualisation).toBe(1);
|
|
677
|
+
// pn / (ratio * 1000) = 15000 / 1000 = 15
|
|
678
|
+
expect(chTvStore.getGenerateurCombustion).toHaveBeenCalledWith(1, 15);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
describeIntegration("Test d'intégration des installations de chauffage", () => {
|
|
567
682
|
test.each(corpus)('vérification des DI qp0 des installations CH pour dpe %s', (ademeId) => {
|
|
568
683
|
let dpeRequest = getAdemeFileJson(ademeId);
|
|
569
684
|
dpeRequest = normalizerService.normalize(dpeRequest);
|
|
@@ -38,4 +38,18 @@ describe('Calcul des installations de chauffage', () => {
|
|
|
38
38
|
service.execute(ctx, logement);
|
|
39
39
|
expect(generateurChService.execute).toHaveBeenCalledWith(ctx, logement, installationCh);
|
|
40
40
|
});
|
|
41
|
+
|
|
42
|
+
test("Aucun générateur traité en l'absence d'installation de chauffage", () => {
|
|
43
|
+
vi.spyOn(generateurChService, 'execute').mockReturnThis();
|
|
44
|
+
|
|
45
|
+
/** @type {Contexte} */
|
|
46
|
+
const ctx = { zoneClimatique: { id: 1 } };
|
|
47
|
+
|
|
48
|
+
// Collection absente : la valeur de repli [] doit être utilisée (aucun appel au service générateur)
|
|
49
|
+
service.execute(ctx, {});
|
|
50
|
+
// Collection présente mais sans tableau d'installations
|
|
51
|
+
service.execute(ctx, { installation_chauffage_collection: {} });
|
|
52
|
+
|
|
53
|
+
expect(generateurChService.execute).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
41
55
|
});
|
|
@@ -5,6 +5,7 @@ import corpus from '../../../../../../test/corpus-sano.json';
|
|
|
5
5
|
import { getAdemeFileJson } from '../../../../../../test/test-helpers.js';
|
|
6
6
|
import { FrTvStore } from '../../../../dpe/infrastructure/froid/frTv.store.js';
|
|
7
7
|
import { ConsoFroidService } from './conso-froid.service.js';
|
|
8
|
+
import { describeIntegration } from '../../../../../../test/helpers/integration-test.js';
|
|
8
9
|
|
|
9
10
|
/** @type {ConsoFroidService} **/
|
|
10
11
|
let service;
|
|
@@ -117,7 +118,87 @@ describe('Calcul des consos en froid du logement', () => {
|
|
|
117
118
|
}
|
|
118
119
|
);
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
test('consoFroid : données intermédiaires absentes -> EER lu dans la table (repli {})', () => {
|
|
122
|
+
// climatisation.donnee_intermediaire absente : repli sur {} puis lecture de l'EER forfaitaire.
|
|
123
|
+
vi.spyOn(tvStore, 'getEer').mockReturnValue(90);
|
|
124
|
+
|
|
125
|
+
/** @type {Contexte} */
|
|
126
|
+
const contexte = { zoneClimatique: { id: 1 }, surfaceHabitable: 100 };
|
|
127
|
+
|
|
128
|
+
/** @type {Climatisation} */
|
|
129
|
+
const climatisation = {
|
|
130
|
+
donnee_entree: { enum_methode_saisie_carac_sys_id: 6, enum_periode_installation_fr_id: 2 }
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** @type {ApportEtBesoin} */
|
|
134
|
+
const apportEtBesoin = { besoin_fr: 1000, besoin_fr_depensier: 1500 };
|
|
135
|
+
|
|
136
|
+
const consoFroid = service.consoFroid(contexte, apportEtBesoin, climatisation);
|
|
137
|
+
|
|
138
|
+
expect(tvStore.getEer).toHaveBeenCalledWith(1, 2);
|
|
139
|
+
// conso = 0.9 * besoin / eer
|
|
140
|
+
expect(consoFroid.conso_fr).toBeCloseTo((0.9 * 1000) / 90, 5);
|
|
141
|
+
expect(consoFroid.conso_fr_depensier).toBeCloseTo((0.9 * 1500) / 90, 5);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('execute : renseigne les données intermédiaires de chaque climatisation', () => {
|
|
145
|
+
// On isole execute du calcul détaillé : consoFroid est doublée pour ne vérifier que
|
|
146
|
+
// l'itération sur les climatisations et l'affectation des champs (dont l'initialisation ??=).
|
|
147
|
+
vi.spyOn(service, 'consoFroid').mockReturnValue({
|
|
148
|
+
besoin_fr: 10,
|
|
149
|
+
besoin_fr_depensier: 15,
|
|
150
|
+
conso_fr: 2,
|
|
151
|
+
conso_fr_depensier: 3
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** @type {Contexte} */
|
|
155
|
+
const ctx = { zoneClimatique: { id: 1 }, surfaceHabitable: 100 };
|
|
156
|
+
|
|
157
|
+
// Première climatisation sans donnee_intermediaire (branche ??= {}),
|
|
158
|
+
// seconde avec un objet préexistant (branche ??= conservée).
|
|
159
|
+
const climSansDI = { donnee_entree: {} };
|
|
160
|
+
const climAvecDI = { donnee_entree: {}, donnee_intermediaire: { eer: 42 } };
|
|
161
|
+
|
|
162
|
+
/** @type {Logement} */
|
|
163
|
+
const logement = {
|
|
164
|
+
sortie: { apport_et_besoin: { besoin_fr: 1000, besoin_fr_depensier: 1500 } },
|
|
165
|
+
climatisation_collection: { climatisation: [climSansDI, climAvecDI] }
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
service.execute(ctx, logement);
|
|
169
|
+
|
|
170
|
+
expect(service.consoFroid).toHaveBeenCalledTimes(2);
|
|
171
|
+
expect(service.consoFroid).toHaveBeenCalledWith(
|
|
172
|
+
ctx,
|
|
173
|
+
logement.sortie.apport_et_besoin,
|
|
174
|
+
climSansDI
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
for (const clim of [climSansDI, climAvecDI]) {
|
|
178
|
+
expect(clim.donnee_intermediaire).toMatchObject({
|
|
179
|
+
besoin_fr: 10,
|
|
180
|
+
besoin_fr_depensier: 15,
|
|
181
|
+
conso_fr: 2,
|
|
182
|
+
conso_fr_depensier: 3
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
// L'objet préexistant a été conservé (eer non écrasé par ??=)
|
|
186
|
+
expect(climAvecDI.donnee_intermediaire.eer).toBe(42);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("execute : aucune conso calculée en l'absence de climatisation", () => {
|
|
190
|
+
// Valeur de repli [] quand climatisation_collection est absente.
|
|
191
|
+
vi.spyOn(service, 'consoFroid');
|
|
192
|
+
|
|
193
|
+
/** @type {Contexte} */
|
|
194
|
+
const ctx = { zoneClimatique: { id: 1 }, surfaceHabitable: 100 };
|
|
195
|
+
|
|
196
|
+
service.execute(ctx, { sortie: { apport_et_besoin: {} } });
|
|
197
|
+
|
|
198
|
+
expect(service.consoFroid).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describeIntegration("Test d'intégration pour le besoin en froid", () => {
|
|
121
202
|
test.each(corpus)('vérification des sorties besoin_fr et conso_fr pour dpe %s', (ademeId) => {
|
|
122
203
|
/**
|
|
123
204
|
* @type {Dpe}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { PRECISION } from './constants.js';
|
|
3
|
+
|
|
4
|
+
describe('Constantes du moteur', () => {
|
|
5
|
+
test('PRECISION vaut 100000', () => {
|
|
6
|
+
expect(PRECISION).toBe(100000);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('PRECISION est un entier positif utilisable comme facteur d’arrondi', () => {
|
|
10
|
+
expect(typeof PRECISION).toBe('number');
|
|
11
|
+
expect(Number.isInteger(PRECISION)).toBe(true);
|
|
12
|
+
expect(PRECISION).toBeGreaterThan(0);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -160,6 +160,27 @@ describe('Generateur du contexte du calcul', () => {
|
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
test("DPE appartement généré à partir des données d'un DPE immeuble", () => {
|
|
164
|
+
// Méthode 10 : appartement issu des données immeuble.
|
|
165
|
+
// -> typeDpe APPARTEMENT_A_PARTIR_IMMEUBLE et surface habitable = celle de l'immeuble.
|
|
166
|
+
const dpe = {
|
|
167
|
+
logement: {
|
|
168
|
+
meteo: {},
|
|
169
|
+
caracteristique_generale: {
|
|
170
|
+
enum_methode_application_dpe_log_id: '10',
|
|
171
|
+
surface_habitable_logement: 48.9,
|
|
172
|
+
surface_habitable_immeuble: 105
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
expect(contexteBuilder.fromDpe(dpe)).toMatchObject({
|
|
178
|
+
typeHabitation: TypeHabitation.APPARTEMENT,
|
|
179
|
+
typeDpe: TypeDpe.APPARTEMENT_A_PARTIR_IMMEUBLE,
|
|
180
|
+
surfaceHabitable: 105
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
163
184
|
test('Contexte avec calcul ilpa', () => {
|
|
164
185
|
const dpe = {
|
|
165
186
|
logement: {
|
|
@@ -121,6 +121,17 @@ describe('Calcul des informations techniques des générateurs ECS', () => {
|
|
|
121
121
|
});
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
test("execute ne fait rien quand l'installation n'a pas de collection de générateurs", () => {
|
|
125
|
+
// Valeur de repli [] quand generateur_ecs_collection est absente : aucune mutation.
|
|
126
|
+
const pertesStockageSpy = vi.spyOn(service, 'pertesStockage');
|
|
127
|
+
|
|
128
|
+
/** @type {InstallationEcs} */
|
|
129
|
+
const installationEcs = {};
|
|
130
|
+
expect(() => service.execute(installationEcs)).not.toThrow();
|
|
131
|
+
expect(installationEcs).toStrictEqual({});
|
|
132
|
+
expect(pertesStockageSpy).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
124
135
|
test("Determination du type d'énergie électrique du générateur", () => {
|
|
125
136
|
vi.spyOn(tvStore, 'getElectriqueEcsGenerateurs').mockReturnValue([1]);
|
|
126
137
|
expect(service.generateurElectrique({ enum_type_generateur_ecs_id: 1 })).toBeTruthy();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
2
|
import { GenerateurEcsService } from './generateur-ecs.service.js';
|
|
3
3
|
import { InstallationEcsService } from './installation-ecs.service.js';
|
|
4
4
|
import corpus from '../../../../../test/corpus-sano.json';
|
|
5
5
|
import { expect_or, getAdemeFileJson } from '../../../../../test/test-helpers.js';
|
|
6
6
|
import { DpeNormalizerService } from '../../../normalizer/domain/dpe-normalizer.service.js';
|
|
7
7
|
import { ContexteBuilder } from '../contexte.builder.js';
|
|
8
|
+
import { describeIntegration } from '../../../../../test/helpers/integration-test.js';
|
|
8
9
|
|
|
9
10
|
/** @type {InstallationEcsService} **/
|
|
10
11
|
let service;
|
|
@@ -247,7 +248,59 @@ describe('Calcul des besoins et des pertes des installations ECS', () => {
|
|
|
247
248
|
expect(service.isInstallationIndividuelle({ enum_type_installation_id: 4 })).toBeFalsy();
|
|
248
249
|
});
|
|
249
250
|
|
|
250
|
-
|
|
251
|
+
test('execute : parcourt les installations ECS et renseigne besoins et pertes', () => {
|
|
252
|
+
// Le calcul des Qgw par générateur est délégué au GenerateurEcsService : on le double
|
|
253
|
+
// pour isoler la logique d'orchestration de l'installation.
|
|
254
|
+
vi.spyOn(generateurEcsService, 'execute').mockReturnValue(undefined);
|
|
255
|
+
|
|
256
|
+
/** @type {Contexte} */
|
|
257
|
+
const ctx = { surfaceHabitable: 100 };
|
|
258
|
+
|
|
259
|
+
/** @type {InstallationEcs} */
|
|
260
|
+
const installationEcs = {
|
|
261
|
+
donnee_entree: { enum_type_installation_id: 1, surface_habitable: 100 },
|
|
262
|
+
generateur_ecs_collection: {
|
|
263
|
+
generateur_ecs: [
|
|
264
|
+
{
|
|
265
|
+
donnee_entree: { position_volume_chauffe_stockage: 1 },
|
|
266
|
+
donnee_utilisateur: { Qgw: 500 }
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/** @type {Logement} */
|
|
273
|
+
const logement = { installation_ecs_collection: { installation_ecs: [installationEcs] } };
|
|
274
|
+
|
|
275
|
+
service.execute(ctx, logement, { besoin_ecs: 1000, besoin_ecs_depensier: 1500 });
|
|
276
|
+
|
|
277
|
+
// Délégation du calcul par générateur
|
|
278
|
+
expect(generateurEcsService.execute).toHaveBeenCalledWith(installationEcs);
|
|
279
|
+
|
|
280
|
+
// Besoins proratisés (surface installation === surface logement -> ratio 1)
|
|
281
|
+
expect(installationEcs.donnee_intermediaire).toStrictEqual({
|
|
282
|
+
besoin_ecs: 1000,
|
|
283
|
+
besoin_ecs_depensier: 1500
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Pertes de distribution individuelle : 0.1 * besoin (en Wh) ; installation individuelle -> QdwCol* = 0
|
|
287
|
+
expect(installationEcs.donnee_utilisateur.QdwIndVc.conventionnel).toBeCloseTo(100000, 5);
|
|
288
|
+
expect(installationEcs.donnee_utilisateur.QdwIndVc.depensier).toBeCloseTo(150000, 5);
|
|
289
|
+
expect(installationEcs.donnee_utilisateur.QdwColVc.conventionnel).toBe(0);
|
|
290
|
+
// Générateur en volume chauffé -> stockage récupérable = Qgw
|
|
291
|
+
expect(installationEcs.donnee_utilisateur.QgwRecuperable).toBe(500);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('execute : aucune installation traitée quand la collection ECS est absente', () => {
|
|
295
|
+
// Valeur de repli [] quand installation_ecs_collection est absente.
|
|
296
|
+
const generateurSpy = vi.spyOn(generateurEcsService, 'execute');
|
|
297
|
+
|
|
298
|
+
service.execute({ surfaceHabitable: 100 }, {}, { besoin_ecs: 0, besoin_ecs_depensier: 0 });
|
|
299
|
+
|
|
300
|
+
expect(generateurSpy).not.toHaveBeenCalled();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describeIntegration("Test d'intégration des installations ECS", () => {
|
|
251
304
|
test.each(corpus)('vérification des DI des installations ECS pour dpe %s', (ademeId) => {
|
|
252
305
|
let dpeRequest = getAdemeFileJson(ademeId);
|
|
253
306
|
dpeRequest = normalizerService.normalize(dpeRequest);
|
|
@@ -1,7 +1,230 @@
|
|
|
1
|
-
import { describe, test } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { EngineService } from './engine.service.js';
|
|
3
|
+
import { logger } from '../../../core/util/logger/log-service.js';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Tests unitaires isolés de `EngineService`.
|
|
7
|
+
*
|
|
8
|
+
* Les quatre services injectés (`DeperditionEnveloppeService`,
|
|
9
|
+
* `ApportEtBesoinService`, `ConsoService` et `ContexteBuilder`) sont remplacés
|
|
10
|
+
* par des doubles de test (spies `vi.fn`) afin de n'exercer que la logique
|
|
11
|
+
* d'orchestration du moteur : nettoyage des données calculées, construction du
|
|
12
|
+
* contexte, délégation aux services et agrégation des sorties. Aucune donnée
|
|
13
|
+
* réelle (tables, DPE complet) n'est nécessaire.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @type {{ deperditions: import('vitest').Mock }} */
|
|
17
|
+
let deperditionService;
|
|
18
|
+
|
|
19
|
+
/** @type {{ execute: import('vitest').Mock }} */
|
|
20
|
+
let apportEtBesoinService;
|
|
21
|
+
|
|
22
|
+
/** @type {{ execute: import('vitest').Mock }} */
|
|
23
|
+
let consoService;
|
|
24
|
+
|
|
25
|
+
/** @type {{ fromDpe: import('vitest').Mock }} */
|
|
26
|
+
let contextBuilder;
|
|
27
|
+
|
|
28
|
+
/** @type {EngineService} */
|
|
29
|
+
let service;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Construit une enveloppe minimale : toutes les collections existent mais leurs
|
|
33
|
+
* tableaux internes sont absents (exerce le court-circuit des `?.`).
|
|
34
|
+
*/
|
|
35
|
+
function enveloppeMinimale() {
|
|
36
|
+
return {
|
|
37
|
+
mur_collection: {},
|
|
38
|
+
baie_vitree_collection: {},
|
|
39
|
+
ets_collection: {},
|
|
40
|
+
plancher_bas_collection: {},
|
|
41
|
+
plancher_haut_collection: {},
|
|
42
|
+
pont_thermique_collection: {},
|
|
43
|
+
porte_collection: {}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Construit un DPE minimal : collections présentes mais vides. Utilisé pour
|
|
49
|
+
* exercer les branches « tableau absent » de `#removeComputedData`.
|
|
50
|
+
*/
|
|
51
|
+
function dpeMinimal() {
|
|
52
|
+
return {
|
|
53
|
+
numero_dpe: 'DPE-MIN',
|
|
54
|
+
logement: {
|
|
55
|
+
caracteristique_generale: {},
|
|
56
|
+
enveloppe: enveloppeMinimale(),
|
|
57
|
+
climatisation_collection: {},
|
|
58
|
+
ventilation_collection: {},
|
|
59
|
+
installation_ecs_collection: {},
|
|
60
|
+
installation_chauffage_collection: {}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Construit un DPE complet : chaque collection possède un tableau peuplé avec
|
|
67
|
+
* des `donnee_intermediaire`. Exerce la branche « tableau présent » de
|
|
68
|
+
* `#removeComputedData` ainsi que les `?.` imbriqués.
|
|
69
|
+
*/
|
|
70
|
+
function dpeComplet() {
|
|
71
|
+
return {
|
|
72
|
+
numero_dpe: 'DPE-FULL',
|
|
73
|
+
logement: {
|
|
74
|
+
caracteristique_generale: {},
|
|
75
|
+
sortie: { deperdition: 'ancienne valeur' },
|
|
76
|
+
enveloppe: {
|
|
77
|
+
mur_collection: { mur: [{ donnee_intermediaire: {} }] },
|
|
78
|
+
baie_vitree_collection: {
|
|
79
|
+
baie_vitree: [
|
|
80
|
+
{
|
|
81
|
+
donnee_intermediaire: {},
|
|
82
|
+
baie_vitree_double_fenetre: { donnee_intermediaire: {} }
|
|
83
|
+
},
|
|
84
|
+
// baie sans double fenêtre : exerce la branche falsy du `?.`
|
|
85
|
+
{ donnee_intermediaire: {} }
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
ets_collection: { ets: { donnee_intermediaire: {} } },
|
|
89
|
+
plancher_bas_collection: { plancher_bas: [{ donnee_intermediaire: {} }] },
|
|
90
|
+
plancher_haut_collection: { plancher_haut: [{ donnee_intermediaire: {} }] },
|
|
91
|
+
pont_thermique_collection: { pont_thermique: [{ donnee_intermediaire: {} }] },
|
|
92
|
+
porte_collection: { porte: [{ donnee_intermediaire: {} }] }
|
|
93
|
+
},
|
|
94
|
+
climatisation_collection: { climatisation: [{ donnee_intermediaire: {} }] },
|
|
95
|
+
ventilation_collection: { ventilation: [{ donnee_intermediaire: {} }] },
|
|
96
|
+
installation_ecs_collection: {
|
|
97
|
+
installation_ecs: [
|
|
98
|
+
{
|
|
99
|
+
donnee_intermediaire: {},
|
|
100
|
+
generateur_ecs_collection: {
|
|
101
|
+
generateur_ecs: [{ donnee_intermediaire: {} }]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
installation_chauffage_collection: {
|
|
107
|
+
installation_chauffage: [
|
|
108
|
+
{
|
|
109
|
+
donnee_intermediaire: {},
|
|
110
|
+
emetteur_chauffage_collection: {
|
|
111
|
+
emetteur_chauffage: [{ donnee_intermediaire: {} }]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
production_elec_enr: { donnee_intermediaire: {} }
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
describe('EngineService (orchestration de la méthode 3CL)', () => {
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
deperditionService = { deperditions: vi.fn().mockReturnValue('DEPERDITION') };
|
|
124
|
+
apportEtBesoinService = { execute: vi.fn().mockReturnValue('APPORT_ET_BESOIN') };
|
|
125
|
+
consoService = { execute: vi.fn() };
|
|
126
|
+
contextBuilder = { fromDpe: vi.fn().mockReturnValue('CTX') };
|
|
127
|
+
service = new EngineService(
|
|
128
|
+
deperditionService,
|
|
129
|
+
apportEtBesoinService,
|
|
130
|
+
consoService,
|
|
131
|
+
contextBuilder
|
|
132
|
+
);
|
|
133
|
+
vi.spyOn(logger, 'info').mockImplementation(() => {});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('trace le numéro de DPE traité', () => {
|
|
137
|
+
service.execute(dpeMinimal());
|
|
138
|
+
expect(logger.info).toHaveBeenCalledWith('Process DPE DPE-MIN');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('construit le contexte à partir du DPE nettoyé puis délègue aux services', () => {
|
|
142
|
+
const resultat = service.execute(dpeMinimal());
|
|
143
|
+
|
|
144
|
+
// Le contexte est construit une fois à partir du DPE traité
|
|
145
|
+
expect(contextBuilder.fromDpe).toHaveBeenCalledTimes(1);
|
|
146
|
+
const dpeTraite = contextBuilder.fromDpe.mock.calls[0][0];
|
|
147
|
+
|
|
148
|
+
// Les services reçoivent le contexte construit et le logement traité
|
|
149
|
+
expect(deperditionService.deperditions).toHaveBeenCalledWith('CTX', dpeTraite.logement);
|
|
150
|
+
expect(apportEtBesoinService.execute).toHaveBeenCalledWith('CTX', dpeTraite.logement);
|
|
151
|
+
expect(consoService.execute).toHaveBeenCalledWith('CTX', dpeTraite.logement);
|
|
152
|
+
|
|
153
|
+
// Les résultats des services sont agrégés dans la sortie
|
|
154
|
+
expect(resultat.logement.sortie.deperdition).toBe('DEPERDITION');
|
|
155
|
+
expect(resultat.logement.sortie.apport_et_besoin).toBe('APPORT_ET_BESOIN');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('initialise la structure de sortie avec toutes les clés attendues', () => {
|
|
159
|
+
const resultat = service.execute(dpeMinimal());
|
|
160
|
+
|
|
161
|
+
expect(resultat.logement.sortie).toEqual({
|
|
162
|
+
deperdition: 'DEPERDITION',
|
|
163
|
+
apport_et_besoin: 'APPORT_ET_BESOIN',
|
|
164
|
+
ef_conso: undefined,
|
|
165
|
+
ep_conso: undefined,
|
|
166
|
+
emission_ges: undefined,
|
|
167
|
+
cout: undefined,
|
|
168
|
+
production_electricite: undefined,
|
|
169
|
+
sortie_par_energie_collection: undefined,
|
|
170
|
+
confort_ete: undefined,
|
|
171
|
+
qualite_isolation: undefined
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('supprime les données de calcul intermédiaires du DPE retourné', () => {
|
|
176
|
+
const resultat = service.execute(dpeMinimal());
|
|
177
|
+
expect(resultat.logement.donnees_de_calcul).toBeUndefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("n'altère pas le DPE d'entrée (travail sur une copie profonde)", () => {
|
|
181
|
+
const original = dpeComplet();
|
|
182
|
+
const resultat = service.execute(original);
|
|
183
|
+
|
|
184
|
+
// L'entrée conserve ses données calculées d'origine
|
|
185
|
+
expect(original.logement.sortie).toEqual({ deperdition: 'ancienne valeur' });
|
|
186
|
+
expect(original.logement.enveloppe.mur_collection.mur[0].donnee_intermediaire).toBeDefined();
|
|
187
|
+
// La sortie est bien un nouvel objet
|
|
188
|
+
expect(resultat).not.toBe(original);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('nettoie toutes les données intermédiaires quand les collections sont peuplées', () => {
|
|
192
|
+
const resultat = service.execute(dpeComplet());
|
|
193
|
+
const env = resultat.logement.enveloppe;
|
|
194
|
+
|
|
195
|
+
expect(env.mur_collection.mur[0].donnee_intermediaire).toBeUndefined();
|
|
196
|
+
expect(env.baie_vitree_collection.baie_vitree[0].donnee_intermediaire).toBeUndefined();
|
|
197
|
+
expect(
|
|
198
|
+
env.baie_vitree_collection.baie_vitree[0].baie_vitree_double_fenetre.donnee_intermediaire
|
|
199
|
+
).toBeUndefined();
|
|
200
|
+
expect(env.ets_collection.ets.donnee_intermediaire).toBeUndefined();
|
|
201
|
+
expect(env.plancher_bas_collection.plancher_bas[0].donnee_intermediaire).toBeUndefined();
|
|
202
|
+
expect(env.plancher_haut_collection.plancher_haut[0].donnee_intermediaire).toBeUndefined();
|
|
203
|
+
expect(env.pont_thermique_collection.pont_thermique[0].donnee_intermediaire).toBeUndefined();
|
|
204
|
+
expect(env.porte_collection.porte[0].donnee_intermediaire).toBeUndefined();
|
|
205
|
+
|
|
206
|
+
expect(
|
|
207
|
+
resultat.logement.climatisation_collection.climatisation[0].donnee_intermediaire
|
|
208
|
+
).toBeUndefined();
|
|
209
|
+
expect(
|
|
210
|
+
resultat.logement.ventilation_collection.ventilation[0].donnee_intermediaire
|
|
211
|
+
).toBeUndefined();
|
|
212
|
+
|
|
213
|
+
const ecs = resultat.logement.installation_ecs_collection.installation_ecs[0];
|
|
214
|
+
expect(ecs.donnee_intermediaire).toBeUndefined();
|
|
215
|
+
expect(ecs.generateur_ecs_collection.generateur_ecs[0].donnee_intermediaire).toBeUndefined();
|
|
216
|
+
|
|
217
|
+
const ch = resultat.logement.installation_chauffage_collection.installation_chauffage[0];
|
|
218
|
+
expect(ch.donnee_intermediaire).toBeUndefined();
|
|
219
|
+
expect(
|
|
220
|
+
ch.emetteur_chauffage_collection.emetteur_chauffage[0].donnee_intermediaire
|
|
221
|
+
).toBeUndefined();
|
|
222
|
+
|
|
223
|
+
expect(resultat.logement.production_elec_enr.donnee_intermediaire).toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('reste robuste lorsque les collections ne contiennent aucun élément', () => {
|
|
227
|
+
// Aucun tableau interne ni production_elec_enr : les `?.` court-circuitent
|
|
228
|
+
expect(() => service.execute(dpeMinimal())).not.toThrow();
|
|
6
229
|
});
|
|
7
230
|
});
|