@open3cl/engine 1.8.49 → 1.8.51

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/conso.spec.js CHANGED
@@ -37,13 +37,21 @@ vi.mock('./tv.js', () => ({
37
37
  }
38
38
  }));
39
39
 
40
+ /** Mode bug_for_bug_compat piloté par les tests (export « live » du module utils mocké). */
41
+ const utilsState = vi.hoisted(() => ({ bugForBugCompat: false }));
42
+
43
+ /** Lecture des tables par défaut : réseau de chaleur "R1" connu, "R2" inconnu. */
44
+ const tvParDefaut = (table, matcher) => {
45
+ if (matcher?.identifiant_reseau === 'R1') return { contenu_co2_acv: 0.15 };
46
+ return null;
47
+ };
48
+
40
49
  vi.mock('./utils.js', () => ({
41
50
  set_bug_for_bug_compat: vi.fn(),
42
- tv: vi.fn((table, matcher) => {
43
- // Réseau de chaleur "R1" connu, "R2" inconnu
44
- if (matcher?.identifiant_reseau === 'R1') return { contenu_co2_acv: 0.15 };
45
- return null;
46
- })
51
+ get bug_for_bug_compat() {
52
+ return utilsState.bugForBugCompat;
53
+ },
54
+ tv: vi.fn((table, matcher) => tvParDefaut(table, matcher))
47
55
  }));
48
56
 
49
57
  const {
@@ -58,8 +66,13 @@ const {
58
66
  getAuxGenerationEcs,
59
67
  DATE_BAREME_COUT_2024,
60
68
  appointBijonction,
61
- coutBijonction
69
+ coutBijonction,
70
+ ARRETES_RESEAU_CHALEUR,
71
+ COEF_GES_AUTRES_RESEAUX_CHALEUR,
72
+ indexArreteReseauChaleur,
73
+ getContenuCo2ReseauChaleur
62
74
  } = await import('./conso.js');
75
+ const { tv } = await import('./utils.js');
63
76
 
64
77
  /**
65
78
  * Classe énergétique (bilan DPE)
@@ -432,7 +445,7 @@ describe('calc_conso - agrégation des consommations', () => {
432
445
  expect(res.emission_ges.emission_ges_ch).toBeCloseTo(150, 9);
433
446
  });
434
447
 
435
- test('réseau de chaleur inconnu (identifiant sans ligne) : erreur signalée', () => {
448
+ test('réseau de chaleur inconnu (identifiant sans ligne) : erreur signalée, facteur « autres réseaux » 0,385', () => {
436
449
  const ch = [
437
450
  installCh({ cle_repartition_ch: 1 }, [
438
451
  genCh(
@@ -443,8 +456,10 @@ describe('calc_conso - agrégation des consommations', () => {
443
456
  ])
444
457
  ];
445
458
 
446
- calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
459
+ const res = calc_conso(100, 1, 1, [], ch, [], [], 1, 1, DATE_DPE, coef_ep);
447
460
  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('R2'));
461
+ // Plus de repli sur 1 kgCO2/kWh (GES = consommation) : 1000 * 0,385
462
+ expect(res.emission_ges.emission_ges_ch).toBeCloseTo(385, 9);
448
463
  });
449
464
 
450
465
  test('réseau de chaleur sans identifiant : coefficient GES forfaitaire 0,385', () => {
@@ -459,8 +474,8 @@ describe('calc_conso - agrégation des consommations', () => {
459
474
  expect(res.emission_ges.emission_ges_ch).toBeCloseTo(385, 9);
460
475
  });
461
476
 
462
- test("date de DPE invalide (levée d'exception) : année de repli 2022", () => {
463
- // date_arrete_reseau_chaleur = Symbol => new Date(...) lève => catch => 2022
477
+ test("date d'arrêté réseau de chaleur invalide : aucune exception", () => {
478
+ // date_arrete_reseau_chaleur = Symbol : ignorée (repli sur la date du DPE)
464
479
  const ch = [
465
480
  installCh({ cle_repartition_ch: 1 }, [
466
481
  genCh(
@@ -1169,3 +1184,166 @@ describe('appointBijonction / coutBijonction', () => {
1169
1184
  );
1170
1185
  });
1171
1186
  });
1187
+
1188
+ /**
1189
+ * Facteur d'émission des réseaux de chaleur urbains : table de l'arrêté utilisé
1190
+ * (modele_donnee/arrete_reseau_chaleur.json de l'observatoire DPE).
1191
+ */
1192
+ describe('réseaux de chaleur - sélection de l’arrêté et du contenu CO2', () => {
1193
+ let errorSpy;
1194
+
1195
+ beforeEach(() => {
1196
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
1197
+ vi.mocked(tv).mockReset();
1198
+ utilsState.bugForBugCompat = false;
1199
+ });
1200
+
1201
+ afterEach(() => {
1202
+ errorSpy.mockRestore();
1203
+ vi.mocked(tv).mockImplementation(tvParDefaut);
1204
+ utilsState.bugForBugCompat = false;
1205
+ });
1206
+
1207
+ /** Tables mockées : identifiant -> { table: ligne } */
1208
+ const mockTables = (lignes) =>
1209
+ vi
1210
+ .mocked(tv)
1211
+ .mockImplementation((table, matcher) => lignes[matcher.identifiant_reseau]?.[table] ?? null);
1212
+
1213
+ test('six arrêtés, dans l’ordre chronologique, table ≠ année de l’arrêté', () => {
1214
+ expect(ARRETES_RESEAU_CHALEUR.map((a) => a.table)).toEqual([
1215
+ 'reseau_chaleur_2020',
1216
+ 'reseau_chaleur_2021',
1217
+ 'reseau_chaleur_2022',
1218
+ 'reseau_chaleur_2023',
1219
+ 'reseau_chaleur_2024',
1220
+ 'reseau_chaleur_2025'
1221
+ ]);
1222
+ // L'arrêté du 05/07/2024 publie la table 2023 (cause du bug : 2024 - 1 = 2023 forcé à 2022)
1223
+ expect(ARRETES_RESEAU_CHALEUR[3]).toMatchObject({
1224
+ table: 'reseau_chaleur_2023',
1225
+ date_arrete: '2024-07-05'
1226
+ });
1227
+ const dates = ARRETES_RESEAU_CHALEUR.map((a) => a.date_arrete);
1228
+ expect([...dates].sort()).toEqual(dates);
1229
+ ARRETES_RESEAU_CHALEUR.forEach((a) => expect(a.date_application >= a.date_arrete).toBe(true));
1230
+ });
1231
+
1232
+ test.each([
1233
+ ['2020-10-12', 0],
1234
+ ['2021-10-21', 1],
1235
+ ['2022-01-18', 1], // date hors liste : dernier arrêté publié avant
1236
+ ['2023-03-16', 2],
1237
+ ['2024-07-05', 3],
1238
+ ['2024-07-10', 3],
1239
+ ['2025-04-11', 4],
1240
+ ['2026-04-25', 5],
1241
+ ['2030-01-01', 5],
1242
+ ['2019-01-01', 0]
1243
+ ])('date_arrete_reseau_chaleur %s : arrêté n°%i', (dateArrete, index) => {
1244
+ // La date du DPE est ignorée quand la date d'arrêté est renseignée
1245
+ expect(indexArreteReseauChaleur(dateArrete, '2030-01-01')).toBe(index);
1246
+ });
1247
+
1248
+ test.each([
1249
+ ['2022-01-17', 0],
1250
+ ['2022-01-18', 1],
1251
+ ['2023-04-15', 1], // arrêté du 16/03/2023 applicable au 16/04/2023 seulement
1252
+ ['2023-04-16', 2],
1253
+ ['2024-08-09', 3],
1254
+ [undefined, 0],
1255
+ ['date invalide', 0]
1256
+ ])('sans date d’arrêté, DPE du %s : arrêté applicable n°%i', (dateDpe, index) => {
1257
+ expect(indexArreteReseauChaleur(undefined, dateDpe)).toBe(index);
1258
+ });
1259
+
1260
+ test('date d’arrêté illisible (non chaîne) : repli sur la date du DPE', () => {
1261
+ expect(indexArreteReseauChaleur(Symbol('invalide'), '2024-08-09')).toBe(3);
1262
+ expect(indexArreteReseauChaleur(new Date('2024-07-05'), undefined)).toBe(3);
1263
+ });
1264
+
1265
+ test('arrêté du 05/07/2024 : lecture de reseau_chaleur_2023 et contenu ACV (cas Pau 6403C)', () => {
1266
+ mockTables({
1267
+ '6403C': { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
1268
+ });
1269
+ expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', 'Pleiades')).toBe(0.061);
1270
+ expect(tv).toHaveBeenCalledTimes(1);
1271
+ expect(tv).toHaveBeenCalledWith('reseau_chaleur_2023', { identifiant_reseau: '6403C' });
1272
+ });
1273
+
1274
+ test('bug_for_bug_compat + LICIEL + table 2023 : contenu CO2 hors ACV transmis par le logiciel', () => {
1275
+ utilsState.bugForBugCompat = true;
1276
+ mockTables({
1277
+ '6403C': {
1278
+ reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' },
1279
+ reseau_chaleur_2022: { contenu_co2: '0.1', contenu_co2_acv: '0.2' }
1280
+ }
1281
+ });
1282
+ const liciel = 'LICIEL Diagnostics v4 [Version XML:305]';
1283
+ expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', liciel)).toBe(0.047);
1284
+ // Autre logiciel : valeur ACV
1285
+ expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', 'DPEWIN V5.2.5')).toBe(
1286
+ 0.061
1287
+ );
1288
+ expect(getContenuCo2ReseauChaleur('6403C', '2024-07-05', '2025-01-24', undefined)).toBe(0.061);
1289
+ // LICIEL avec un autre arrêté (16/03/2023 -> table 2022) : valeur ACV
1290
+ expect(getContenuCo2ReseauChaleur('6403C', '2023-03-16', '2023-06-01', liciel)).toBe(0.2);
1291
+ });
1292
+
1293
+ test('sans bug_for_bug_compat, LICIEL lit aussi le contenu ACV', () => {
1294
+ mockTables({
1295
+ R: { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
1296
+ });
1297
+ expect(getContenuCo2ReseauChaleur('R', '2024-07-05', undefined, 'LICIEL')).toBe(0.061);
1298
+ });
1299
+
1300
+ test('réseau absent de la table de l’arrêté : table antérieure la plus récente qui le contient', () => {
1301
+ mockTables({
1302
+ R: {
1303
+ reseau_chaleur_2021: { contenu_co2: '0.3', contenu_co2_acv: '0.33' },
1304
+ reseau_chaleur_2020: { contenu_co2: '0.4', contenu_co2_acv: '0.44' }
1305
+ }
1306
+ });
1307
+ expect(getContenuCo2ReseauChaleur('R', '2024-07-05', undefined, undefined)).toBe(0.33);
1308
+ expect(vi.mocked(tv).mock.calls.map((c) => c[0])).toEqual([
1309
+ 'reseau_chaleur_2023',
1310
+ 'reseau_chaleur_2022',
1311
+ 'reseau_chaleur_2021'
1312
+ ]);
1313
+ });
1314
+
1315
+ test('réseau introuvable dans toutes les tables : facteur « autres réseaux de chaleur » 0,385 et erreur', () => {
1316
+ mockTables({});
1317
+ expect(COEF_GES_AUTRES_RESEAUX_CHALEUR).toBe(0.385);
1318
+ expect(getContenuCo2ReseauChaleur('XXXX', '2024-07-05', undefined, undefined)).toBe(0.385);
1319
+ expect(tv).toHaveBeenCalledTimes(4);
1320
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('XXXX'));
1321
+ });
1322
+
1323
+ test('calc_conso : facteur réseau appliqué au chauffage et à l’ECS, version logiciel transmise', () => {
1324
+ utilsState.bugForBugCompat = true;
1325
+ mockTables({
1326
+ '6403C': { reseau_chaleur_2023: { contenu_co2: '0.047', contenu_co2_acv: '0.061' } }
1327
+ });
1328
+ const de = { identifiant_reseau_chaleur: '6403C', date_arrete_reseau_chaleur: '2024-07-05' };
1329
+ const ch = [
1330
+ installCh({ cle_repartition_ch: 1 }, [
1331
+ genCh('3', { conso_ch: 1000, conso_ch_depensier: 1200 }, de)
1332
+ ])
1333
+ ];
1334
+ const ecs = [
1335
+ installEcs({ cle_repartition_ecs: 1 }, [
1336
+ genEcs('3', { conso_ecs: 500, conso_ecs_depensier: 600 }, de)
1337
+ ])
1338
+ ];
1339
+ const args = [100, 1, 1, [], ch, ecs, [], 1, 1, '2025-01-24', coef_ep, 1, 1, {}];
1340
+
1341
+ const licielRes = calc_conso(...args, 'LICIEL Diagnostics v4');
1342
+ // 1000 * 0,047 et 500 * 0,047 (valeur ADEME du DPE 2564E0255485Z) — avant : 1000 * 1
1343
+ expect(licielRes.emission_ges.emission_ges_ch).toBeCloseTo(47, 9);
1344
+ expect(licielRes.emission_ges.emission_ges_ecs).toBeCloseTo(23.5, 9);
1345
+
1346
+ const autreRes = calc_conso(...args);
1347
+ expect(autreRes.emission_ges.emission_ges_ch).toBeCloseTo(61, 9);
1348
+ });
1349
+ });
package/engine.js CHANGED
@@ -33,7 +33,7 @@ import getFicheTechnique from './ficheTechnique.js';
33
33
  import { ProductionENR, ratioProrataPvLogement } from './16.2_production_enr.js';
34
34
  import DpeSanitizerService from './dpe-sanitizer.service.js';
35
35
 
36
- const LIB_VERSION = '1.8.49';
36
+ const LIB_VERSION = '1.8.51';
37
37
 
38
38
  const dpeSanitizerService = new DpeSanitizerService();
39
39
 
@@ -85,6 +85,7 @@ export function calcul_3cl(inputDpe, options) {
85
85
  }
86
86
  const modele = enums.modele_dpe[dpe.administratif.enum_modele_dpe_id];
87
87
  const dateDpe = dpe.administratif.date_etablissement_dpe;
88
+ const versionLogiciel = dpe.administratif.diagnostiqueur?.version_logiciel;
88
89
  if (modele !== 'dpe 3cl 2021 méthode logement') {
89
90
  console.error('Moteur dpe non implémenté pour le modèle: ' + modele);
90
91
  return null;
@@ -606,7 +607,9 @@ export function calcul_3cl(inputDpe, options) {
606
607
  dateDpe,
607
608
  coef_ep,
608
609
  th === 'immeuble' ? Nb_lgt : 1,
609
- ratioSurfaceCoutCollectif
610
+ ratioSurfaceCoutCollectif,
611
+ {},
612
+ versionLogiciel
610
613
  );
611
614
 
612
615
  // Proratisation au logement d'une installation PV d'immeuble (appartement)
@@ -648,7 +651,8 @@ export function calcul_3cl(inputDpe, options) {
648
651
  coef_ep,
649
652
  th === 'immeuble' ? Nb_lgt : 1,
650
653
  ratioSurfaceCoutCollectif,
651
- facteursAc
654
+ facteursAc,
655
+ versionLogiciel
652
656
  );
653
657
  conso.emission_ges = consoNette.emission_ges;
654
658
  // Ligne électricité de sortie_par_energie nette de l'autoconsommation (issue #34)
@@ -669,7 +673,9 @@ export function calcul_3cl(inputDpe, options) {
669
673
  dateDpe,
670
674
  coef_ep_1_7,
671
675
  th === 'immeuble' ? Nb_lgt : 1,
672
- ratioSurfaceCoutCollectif
676
+ ratioSurfaceCoutCollectif,
677
+ {},
678
+ versionLogiciel
673
679
  );
674
680
 
675
681
  productionENR.calculateEnr(
@@ -695,7 +701,9 @@ export function calcul_3cl(inputDpe, options) {
695
701
  dateDpe,
696
702
  coef_ep_2_3,
697
703
  th === 'immeuble' ? Nb_lgt : 1,
698
- ratioSurfaceCoutCollectif
704
+ ratioSurfaceCoutCollectif,
705
+ {},
706
+ versionLogiciel
699
707
  );
700
708
 
701
709
  productionENR.calculateEnr(
package/engine.spec.js CHANGED
@@ -518,6 +518,24 @@ describe('calcul_3cl - run intégral (maison)', () => {
518
518
  expect(dpe.logement.sortie.cout).toEqual({ cout_5_usages: 2 });
519
519
  });
520
520
 
521
+ test('version du logiciel de saisie transmise à tous les calculs de conso (facteur GES réseau de chaleur)', () => {
522
+ const dpe = makeDpe();
523
+ dpe.administratif.diagnostiqueur = { version_logiciel: 'LICIEL Diagnostics v4' };
524
+ const facteurs = { ch: 0.8 };
525
+ vi.mocked(calculateEnr).mockImplementationOnce((enr, conso, Sh, th, zc, coef, ratio, f) => {
526
+ Object.assign(f, facteurs);
527
+ return { pv: 1 };
528
+ });
529
+
530
+ calcul_3cl(dpe, { sanitize: false });
531
+
532
+ // Lue avant l'écrasement de `diagnostiqueur` par la version du moteur
533
+ expect(calc_conso).toHaveBeenCalledTimes(4);
534
+ calc_conso.mock.calls.forEach((call) => expect(call[14]).toBe('LICIEL Diagnostics v4'));
535
+ expect(calc_conso.mock.calls[0][13]).toEqual({});
536
+ expect(calc_conso.mock.calls[1][13]).toEqual(facteurs);
537
+ });
538
+
521
539
  test('bâtiment matériaux anciens => ilpa = 1 ; incohérence inertie => erreur loggée', () => {
522
540
  const dpe = makeDpe();
523
541
  dpe.logement.meteo.batiment_materiaux_anciens = 1;
@@ -4,6 +4,11 @@ import { excel_to_js_exec } from '../../../../utils.js';
4
4
  import { TvStore } from '../../../dpe/infrastructure/tv.store.js';
5
5
  import { EmetteurChService } from './emetteur-ch.service.js';
6
6
  import { TypeGenerateur } from '../../../dpe/domain/models/type-generateur.model.js';
7
+ import enums from '../../../../enums.js';
8
+ import {
9
+ anneeInstallationGenerateur,
10
+ pnEstimeeDimensionnement
11
+ } from '../../../../13.2_generateur_combustion_pdim.js';
7
12
 
8
13
  /**
9
14
  * Calcul des données de calcul pour chacun des générateurs
@@ -76,7 +81,8 @@ export class GenerateurChService {
76
81
  generateurChauffage.donnee_intermediaire.pn = this.pn(
77
82
  ctx,
78
83
  logement,
79
- installationChauffage
84
+ installationChauffage,
85
+ generateurChauffage
80
86
  );
81
87
  }
82
88
 
@@ -206,9 +212,10 @@ export class GenerateurChService {
206
212
  * @param ctx {Contexte}
207
213
  * @param logement {Logement}
208
214
  * @param installationChauffage {InstallationChauffage}
215
+ * @param generateurChauffage {GenerateurChauffage}
209
216
  * @return {number}
210
217
  */
211
- pn(ctx, logement, installationChauffage) {
218
+ pn(ctx, logement, installationChauffage, generateurChauffage) {
212
219
  const Tbase = this.#tvStore.getTempBase(ctx.altitude.id, ctx.zoneClimatique.id);
213
220
  // §13.2.2.4 : générateur individuel n'alimentant qu'une partie du logement => prorata Sh_installation / Sh.
214
221
  // Un générateur collectif dessert tout le bâtiment : pas de prorata.
@@ -218,9 +225,58 @@ export class GenerateurChService {
218
225
  ? installationDE.surface_chauffee / ctx.surfaceHabitable
219
226
  : 1;
220
227
  const prorata = ratio > 0 && ratio < 1 ? ratio : 1;
221
- return (
222
- (prorata * 1.2 * logement.sortie.deperdition.deperdition_enveloppe * (19 - Tbase)) / 0.95 ** 3
223
- );
228
+ const pch =
229
+ (prorata * 1.2 * logement.sortie.deperdition.deperdition_enveloppe * (19 - Tbase)) /
230
+ 0.95 ** 3;
231
+
232
+ // §13.2.2.4 : Pdim = max(Pch ; Pecs) puis table Pdim → Pn des chaudières (générateurs individuels)
233
+ if (String(installationDE?.enum_type_installation_id) !== '1' || !generateurChauffage) {
234
+ return pch;
235
+ }
236
+
237
+ const generateurDE = generateurChauffage.donnee_entree;
238
+ const usageMixte = String(generateurDE.enum_usage_generateur_id) === '3';
239
+ const libelleTypeGenerateur = enums.type_generateur_ch[generateurDE.enum_type_generateur_ch_id];
240
+
241
+ return pnEstimeeDimensionnement({
242
+ pch,
243
+ volumeStockage: usageMixte ? this.volumeStockageEcsMixte(logement, generateurDE) : undefined,
244
+ usageEcs: usageMixte,
245
+ libelleTypeGenerateur,
246
+ // Fiches techniques non disponibles dans la nouvelle architecture : pose murale inconnue
247
+ murale: undefined,
248
+ anneeInstallation: anneeInstallationGenerateur(
249
+ generateurDE.annee_installation,
250
+ ctx.anneeConstruction,
251
+ libelleTypeGenerateur
252
+ )
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Volume de stockage (L) de la production d'ECS assurée par un générateur de chauffage mixte :
258
+ * générateur ECS mixte de même `reference_generateur_mixte`, sinon générateur ECS mixte unique.
259
+ *
260
+ * @param logement {Logement}
261
+ * @param generateurChauffageDE {GenerateurChauffageDE}
262
+ * @return {number|undefined}
263
+ */
264
+ volumeStockageEcsMixte(logement, generateurChauffageDE) {
265
+ const generateursEcsMixtes = (logement.installation_ecs_collection?.installation_ecs || [])
266
+ .flatMap((installation) => installation.generateur_ecs_collection?.generateur_ecs || [])
267
+ .filter((generateur) => String(generateur.donnee_entree?.enum_usage_generateur_id) === '3');
268
+
269
+ const reference = generateurChauffageDE.reference_generateur_mixte;
270
+ const jumeau =
271
+ (reference !== undefined &&
272
+ reference !== null &&
273
+ generateursEcsMixtes.find(
274
+ (generateur) =>
275
+ String(generateur.donnee_entree.reference_generateur_mixte) === String(reference)
276
+ )) ||
277
+ (generateursEcsMixtes.length === 1 ? generateursEcsMixtes[0] : undefined);
278
+
279
+ return jumeau?.donnee_entree.volume_stockage;
224
280
  }
225
281
 
226
282
  /**
@@ -128,3 +128,104 @@ describe('Puissance nominale estimée des générateurs de chauffage (issue #264
128
128
  expect(service.pn(ctx, logement, installation)).toBeCloseTo(PCH_LOGEMENT, 9);
129
129
  });
130
130
  });
131
+
132
+ /**
133
+ * Puissance nominale estimée par la puissance de dimensionnement : Pdim = max(Pch ; Pecs) puis table
134
+ * Pdim → Pn des chaudières (le calcul de la table est testé dans 13.2_generateur_combustion_pdim.spec.js).
135
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2.2.4
136
+ */
137
+ describe('Puissance nominale estimée : Pdim et table Pdim → Pn (issue #271)', () => {
138
+ /** @type {TvStore} */
139
+ let tvStore;
140
+ const ctx = {
141
+ altitude: { id: 1 },
142
+ zoneClimatique: { id: 1 },
143
+ surfaceHabitable: 100,
144
+ anneeConstruction: 1975
145
+ };
146
+ const installation = { donnee_entree: { surface_chauffee: 100, enum_type_installation_id: 1 } };
147
+ const logementAvecEcs = (generateursEcs) => ({
148
+ sortie: { deperdition: { deperdition_enveloppe: 200 } },
149
+ installation_ecs_collection: {
150
+ installation_ecs: [{ generateur_ecs_collection: { generateur_ecs: generateursEcs } }]
151
+ }
152
+ });
153
+ const generateur = (de) => ({ donnee_entree: de });
154
+
155
+ beforeEach(() => {
156
+ tvStore = new TvStore();
157
+ service = new GenerateurChService(new ChTvStore(), tvStore, new EmetteurChService());
158
+ vi.spyOn(tvStore, 'getTempBase').mockReturnValue(-9);
159
+ });
160
+
161
+ test('chaudière de chauffage seul installée après 2015 : Pch = 7,84 kW → Pn = 10 kW', () => {
162
+ const logement = logementAvecEcs([]);
163
+ const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 1 });
164
+ expect(service.pn(ctx, logement, installation, gen)).toBe(10000);
165
+ });
166
+
167
+ test('chaudière 1991-2000 (pose inconnue, avant 2006) : plancher 18 kW', () => {
168
+ const logement = logementAvecEcs([]);
169
+ const gen = generateur({ enum_type_generateur_ch_id: '88', enum_usage_generateur_id: 1 });
170
+ expect(service.pn(ctx, logement, installation, gen)).toBe(18000);
171
+ });
172
+
173
+ test('chaudière mixte instantanée : Pdim = max(Pch ; 21 kW) → Pn = 24 kW', () => {
174
+ const logement = logementAvecEcs([
175
+ { donnee_entree: { enum_usage_generateur_id: 2, volume_stockage: 300 } },
176
+ { donnee_entree: { enum_usage_generateur_id: 3, volume_stockage: 0 } }
177
+ ]);
178
+ const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 3 });
179
+ expect(service.pn(ctx, logement, installation, gen)).toBe(24000);
180
+ });
181
+
182
+ test('radiateur gaz : Pn = Pdim (pas de table)', () => {
183
+ const logement = logementAvecEcs([]);
184
+ const gen = generateur({ enum_type_generateur_ch_id: '54', enum_usage_generateur_id: 1 });
185
+ expect(service.pn(ctx, logement, installation, gen)).toBeCloseTo(7837.877241580406, 9);
186
+ });
187
+
188
+ test('installation collective : Pch conservée', () => {
189
+ const logement = logementAvecEcs([]);
190
+ const collective = { donnee_entree: { enum_type_installation_id: 2 } };
191
+ const gen = generateur({ enum_type_generateur_ch_id: '97', enum_usage_generateur_id: 1 });
192
+ expect(service.pn(ctx, logement, collective, gen)).toBeCloseTo(7837.877241580406, 9);
193
+ });
194
+
195
+ test('volume de stockage du générateur ECS mixte jumeau (reference_generateur_mixte)', () => {
196
+ const ecs = (reference, volume) => ({
197
+ donnee_entree: {
198
+ enum_usage_generateur_id: '3',
199
+ reference_generateur_mixte: reference,
200
+ volume_stockage: volume
201
+ }
202
+ });
203
+ const logement = logementAvecEcs([ecs('A', 0), ecs('B', 200)]);
204
+ expect(service.volumeStockageEcsMixte(logement, { reference_generateur_mixte: 'B' })).toBe(200);
205
+ expect(service.volumeStockageEcsMixte(logement, {})).toBeUndefined();
206
+ expect(service.volumeStockageEcsMixte({}, {})).toBeUndefined();
207
+ expect(
208
+ service.volumeStockageEcsMixte(
209
+ { installation_ecs_collection: { installation_ecs: [{}] } },
210
+ { reference_generateur_mixte: 'B' }
211
+ )
212
+ ).toBeUndefined();
213
+ });
214
+
215
+ test('execute : le générateur est transmis à l’estimation de pn', () => {
216
+ vi.spyOn(service, 'pn').mockReturnValue(24000);
217
+ vi.spyOn(service, 'caracteristiques').mockReturnValue({});
218
+ const gen = {
219
+ donnee_entree: { enum_type_generateur_ch_id: '97', enum_lien_generateur_emetteur_id: 1 },
220
+ donnee_intermediaire: {}
221
+ };
222
+ const inst = {
223
+ donnee_entree: { enum_type_installation_id: 1 },
224
+ generateur_chauffage_collection: { generateur_chauffage: [gen] }
225
+ };
226
+ const logement = logementAvecEcs([]);
227
+ service.execute(ctx, logement, inst);
228
+ expect(service.pn).toHaveBeenCalledWith(ctx, logement, inst, gen);
229
+ expect(gen.donnee_intermediaire.pn).toBe(24000);
230
+ });
231
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.8.49",
3
+ "version": "1.8.51",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {