@open3cl/engine 1.8.8 → 1.8.9

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.
@@ -5,6 +5,7 @@ import { updateGenerateurChaudieres } from './13.2_generateur_combustion_chaudie
5
5
  import { updateGenerateurPacs } from './13.2_generateur_pac.js';
6
6
  import getFicheTechnique from './ficheTechnique.js';
7
7
  import { evaluateFormula as evaluateTvFormula } from './formula.js';
8
+ import { conventionPuissanceNominale } from './13.2_generateur_combustion_pn.js';
8
9
 
9
10
  function criterePn(Pn, matcher) {
10
11
  let critere_list = tvColumnLines('generateur_combustion', 'critere_pn', matcher);
@@ -41,6 +42,58 @@ function evaluateFormula(formulaOrValue, pn, E, F) {
41
42
  return evaluateTvFormula(formulaOrValue, pn, E, F);
42
43
  }
43
44
 
45
+ /**
46
+ * Caractéristiques forfaitaires (rpn, rpint, qp0) d'un générateur à combustion.
47
+ *
48
+ * Si la consommation est obtenue par virtualisation du générateur collectif pour les besoins
49
+ * individuels, les formules sont évaluées sur la puissance du générateur collectif
50
+ * Pn(collectif) = pn / ratio, et qp0 est ramené au logement (× ratio).
51
+ * 17.2.1 - Génération d’un DPE à l’appartement / Traitement des usages collectifs
52
+ *
53
+ * Seules les caractéristiques non saisies (selon la méthode de saisie) sont renvoyées.
54
+ *
55
+ * @param row {object} ligne de la table generateur_combustion
56
+ * @param pn {number} puissance nominale du générateur (W)
57
+ * @param ratio {number} ratio de virtualisation
58
+ * @param E {number}
59
+ * @param F {number}
60
+ * @param type {'ecs'|'ch'}
61
+ * @param methodeSaisie {number}
62
+ * @returns {{rpn?: number, rpint?: number, qp0?: number}}
63
+ */
64
+ export function caracteristiquesGenerateurCombustion(row, pn, ratio, E, F, type, methodeSaisie) {
65
+ const caracteristiques = {};
66
+
67
+ if (![3, 4, 5].includes(methodeSaisie)) {
68
+ if (row.rpn) {
69
+ caracteristiques.rpn = evaluateFormula(row.rpn, pn / ratio, E, F) / 100;
70
+ }
71
+ if (type === 'ch' && row.rpint) {
72
+ caracteristiques.rpint = evaluateFormula(row.rpint, pn / ratio, E, F) / 100;
73
+ }
74
+ }
75
+
76
+ if (![4, 5].includes(methodeSaisie)) {
77
+ if (row.qp0_perc) {
78
+ const qp0_calc = evaluateFormula(row.qp0_perc, pn / ratio, E, F);
79
+ // Certaines chaudières ont un qp0 en % de pn, d'autres ont des valeurs constantes
80
+ if (row.qp0_perc.includes('Pn')) {
81
+ caracteristiques.qp0 = qp0_calc * 1000 * ratio;
82
+ } else {
83
+ if (row.qp0_perc.includes('%')) {
84
+ caracteristiques.qp0 = qp0_calc * pn;
85
+ } else {
86
+ caracteristiques.qp0 = qp0_calc * 1000 * ratio;
87
+ }
88
+ }
89
+ } else {
90
+ caracteristiques.qp0 = 0;
91
+ }
92
+ }
93
+
94
+ return caracteristiques;
95
+ }
96
+
44
97
  /**
45
98
  * Si la méthode de saisie n'est pas "Valeur forfaitaire" mais "caractéristiques saisies"
46
99
  * Documentation 3CL : "Pour les installations récentes ou recommandées, les caractéristiques réelles des chaudières présentées sur les bases
@@ -63,6 +116,7 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
63
116
  const typeGenerateurKey = `enum_type_generateur_${type}_id`;
64
117
  let enumTypeGenerateurId = de[typeGenerateurKey];
65
118
  let row;
119
+ let selectionLigne;
66
120
 
67
121
  /**
68
122
  * Certains DPE configurent mal les données du générateur ECS lorsque c'est un générateur mixte Chauffage + ECS
@@ -99,11 +153,14 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
99
153
  di.pn = (1.2 * GV * (19 - tbase)) / 0.95 ** 3;
100
154
  }
101
155
 
102
- let matcher = {};
103
- matcher[typeGenerateurKey] = enumTypeGenerateurId;
104
- matcher.critere_pn = criterePn(di.pn / (de.ratio_virtualisation * 1000), matcher);
156
+ selectionLigne = (pn, ratio) => {
157
+ let matcher = {};
158
+ matcher[typeGenerateurKey] = enumTypeGenerateurId;
159
+ matcher.critere_pn = criterePn(pn / (ratio * 1000), matcher);
160
+ return tv('generateur_combustion', matcher);
161
+ };
105
162
 
106
- row = tv('generateur_combustion', matcher);
163
+ row = selectionLigne(di.pn, de.ratio_virtualisation);
107
164
 
108
165
  /**
109
166
  * Si l'identifiant du générateur à combustion utilisé n'est pas le bon, avertissement
@@ -144,41 +201,69 @@ export function tv_generateur_combustion(dpe, di, de, type, GV, tbase, methodeSa
144
201
  const E = E_tab[de.presence_ventouse];
145
202
  const F = F_tab[de.presence_ventouse];
146
203
 
147
- const ratioVirtualisation = de.ratio_virtualisation || 1;
204
+ let ratioVirtualisation = de.ratio_virtualisation || 1;
148
205
 
149
206
  /**
150
- * Si la consommation ECS est obtenue par virtualisation du générateur collectif pour les besoins individuels
151
- * la puissance nominale est obtenu à partir de la puissance nominale du générateur collectif multiplié par le
152
- * ratio de virtualisation
153
- * 17.2.1 - Génération d’un DPE à l’appartement / Traitement des usages collectifs
207
+ * Générateur collectif virtualisé : la puissance saisie dans le DPE peut être la puissance
208
+ * virtualisée Pe = a × Pn (convention 3CL) ou la puissance du générateur collectif Pn selon les
209
+ * logiciels (issue #124). La convention réellement utilisée est déduite des valeurs
210
+ * intermédiaires (rpn, qp0) présentes dans le DPE.
154
211
  */
155
- if (![3, 4, 5].includes(methodeSaisie)) {
156
- if (row.rpn) {
157
- di.rpn = evaluateFormula(row.rpn, di.pn / ratioVirtualisation, E, F) / 100;
158
- }
159
- if (type === 'ch' && row.rpint) {
160
- di.rpint = evaluateFormula(row.rpint, di.pn / ratioVirtualisation, E, F) / 100;
212
+ if (selectionLigne) {
213
+ const ligneOuDefaut = (pn, ratio) => selectionLigne(pn, ratio) || row;
214
+ const caracteristiques = (pn, ratio) =>
215
+ caracteristiquesGenerateurCombustion(
216
+ ligneOuDefaut(pn, ratio),
217
+ pn,
218
+ ratio,
219
+ E,
220
+ F,
221
+ type,
222
+ methodeSaisie
223
+ );
224
+ /**
225
+ * Formule rpn de la ligne sélectionnée pour un Pn(collectif) donné (W) : permet de retrouver
226
+ * Pn(collectif) à partir du rpn stocké (4e hypothèse).
227
+ */
228
+ const formuleRpn = (pnCollectif) => {
229
+ const ligne = selectionLigne(pnCollectif, 1);
230
+ return ligne?.rpn ? (p) => evaluateFormula(ligne.rpn, p, E, F) / 100 : undefined;
231
+ };
232
+ const puissance = conventionPuissanceNominale(
233
+ di.pn,
234
+ ratioVirtualisation,
235
+ { rpn: di.rpn, rpint: di.rpint, qp0: di.qp0 },
236
+ caracteristiques,
237
+ bug_for_bug_compat,
238
+ formuleRpn
239
+ );
240
+
241
+ if (puissance.convention === 'puissance_depuis_rpn') {
242
+ console.warn(
243
+ `La puissance nominale du générateur ${de.description} (${di.pn} W) n'est cohérente ni avec la puissance
244
+ virtualisée ni avec celle du générateur collectif. Puissance du générateur collectif recalculée à partir
245
+ du rpn du DPE (${di.rpn}) : Pn = ${puissance.pnCollectif / 1000} kW, utilisation de Pe = a × Pn = ${puissance.pn} W.`
246
+ );
247
+ } else if (puissance.convention !== 'individualisee') {
248
+ console.warn(
249
+ `La puissance nominale du générateur ${de.description} (${di.pn} W) est celle du générateur collectif :
250
+ utilisation de la puissance ${puissance.pn} W et du ratio de virtualisation ${puissance.ratio} (${puissance.convention}).`
251
+ );
161
252
  }
162
- }
163
253
 
164
- if (![4, 5].includes(methodeSaisie)) {
165
- if (row.qp0_perc) {
166
- const qp0_calc = evaluateFormula(row.qp0_perc, di.pn / ratioVirtualisation, E, F);
167
- // Certaines chaudières ont un qp0 en % de pn, d'autres ont des valeurs constantes
168
- if (row.qp0_perc.includes('Pn')) {
169
- di.qp0 = qp0_calc * 1000 * ratioVirtualisation;
170
- } else {
171
- if (row.qp0_perc.includes('%')) {
172
- di.qp0 = qp0_calc * di.pn;
173
- } else {
174
- di.qp0 = qp0_calc * 1000 * ratioVirtualisation;
175
- }
176
- }
177
- } else {
178
- di.qp0 = 0;
254
+ if (puissance.convention !== 'individualisee') {
255
+ di.pn = puissance.pn;
256
+ ratioVirtualisation = puissance.ratio;
257
+ row = ligneOuDefaut(di.pn, ratioVirtualisation);
258
+ de.tv_generateur_combustion_id = Number(row.tv_generateur_combustion_id);
179
259
  }
180
260
  }
181
261
 
262
+ Object.assign(
263
+ di,
264
+ caracteristiquesGenerateurCombustion(row, di.pn, ratioVirtualisation, E, F, type, methodeSaisie)
265
+ );
266
+
182
267
  if (methodeSaisie === 1 || !di.pveilleuse) {
183
268
  di.pveil = Number(row.pveil) || 0;
184
269
  } else {
@@ -51,10 +51,21 @@ vi.mock('./13.2_generateur_pac.js', () => ({
51
51
  vi.mock('./ficheTechnique.js', () => ({
52
52
  default: vi.fn()
53
53
  }));
54
+ /**
55
+ * Détection de la convention de puissance (issue #124) : testée dans son propre spec. Par défaut,
56
+ * le double renvoie la convention individualisée (pn et ratio inchangés).
57
+ */
58
+ vi.mock('./13.2_generateur_combustion_pn.js', () => ({
59
+ conventionPuissanceNominale: vi.fn((pn, ratio) => ({ pn, ratio, convention: 'individualisee' }))
60
+ }));
54
61
 
55
- const { tv_generateur_combustion, updateGenerateurCombustion } =
56
- await import('./13.2_generateur_combustion.js');
62
+ const {
63
+ tv_generateur_combustion,
64
+ updateGenerateurCombustion,
65
+ caracteristiquesGenerateurCombustion
66
+ } = await import('./13.2_generateur_combustion.js');
57
67
  const { tv, tvColumnLines } = await import('./utils.js');
68
+ const { conventionPuissanceNominale } = await import('./13.2_generateur_combustion_pn.js');
58
69
  const { updateGenerateurBouilleurs } = await import('./13.2_generateur_combustion_bouilleur.js');
59
70
  const { updateGenerateurChaudieres } = await import('./13.2_generateur_combustion_chaudiere.js');
60
71
  const { updateGenerateurPacs } = await import('./13.2_generateur_pac.js');
@@ -68,6 +79,12 @@ beforeEach(() => {
68
79
  vi.mocked(updateGenerateurChaudieres).mockReset();
69
80
  vi.mocked(updateGenerateurPacs).mockReset();
70
81
  vi.mocked(getFicheTechnique).mockReset();
82
+ vi.mocked(conventionPuissanceNominale).mockReset();
83
+ vi.mocked(conventionPuissanceNominale).mockImplementation((pn, ratio) => ({
84
+ pn,
85
+ ratio,
86
+ convention: 'individualisee'
87
+ }));
71
88
  state.bug = false;
72
89
  });
73
90
 
@@ -535,3 +552,270 @@ describe('updateGenerateurCombustion - orchestration des substitutions', () => {
535
552
  expect(de.presenceVentilateur).toBeUndefined();
536
553
  });
537
554
  });
555
+
556
+ /**
557
+ * Issue #124 : générateur collectif virtualisé, puissance saisie collective (Pn) ou individualisée
558
+ * (Pe = a × Pn) selon les logiciels.
559
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §13.2 et §17.2.1
560
+ */
561
+ describe('tv_generateur_combustion - convention de puissance d’un générateur virtualisé (#124)', () => {
562
+ const ROW_PN = {
563
+ tv_generateur_combustion_id: '42',
564
+ rpn: '84 + 2 logPn',
565
+ rpint: '80 + 3 logPn',
566
+ qp0_perc: '1%',
567
+ pveil: '0'
568
+ };
569
+
570
+ test('la détection reçoit pn, ratio, valeurs du DPE et bug_for_bug_compat', () => {
571
+ state.bug = true;
572
+ tv.mockReturnValue({ ...ROW_PN });
573
+ const di = { pn: 23000, rpn: 0.87, qp0: 0.276 };
574
+ const de = {
575
+ enum_type_generateur_ch_id: '89',
576
+ ratio_virtualisation: 0.1,
577
+ presence_ventouse: 0
578
+ };
579
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
580
+ expect(conventionPuissanceNominale).toHaveBeenCalledWith(
581
+ 23000,
582
+ 0.1,
583
+ { rpn: 0.87, rpint: undefined, qp0: 0.276 },
584
+ expect.any(Function),
585
+ true,
586
+ expect.any(Function)
587
+ );
588
+ });
589
+
590
+ test('formule rpn fournie (4e hypothèse) : ligne sélectionnée sur Pn collectif, rpn en fraction', () => {
591
+ tvColumnLines.mockReturnValue(['Pn ≤ 70', 'Pn > 70']);
592
+ tv.mockReturnValue({ ...ROW_PN });
593
+ let formuleRpn;
594
+ conventionPuissanceNominale.mockImplementation((pn, ratio, _, __, ___, fn) => {
595
+ formuleRpn = fn;
596
+ return { pn, ratio, convention: 'individualisee' };
597
+ });
598
+ const di = { pn: 23000, rpint: 0.85 };
599
+ const de = {
600
+ enum_type_generateur_ch_id: '89',
601
+ ratio_virtualisation: 0.1,
602
+ presence_ventouse: 0
603
+ };
604
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
605
+ expect(conventionPuissanceNominale.mock.calls[0][2]).toEqual({
606
+ rpn: undefined,
607
+ rpint: 0.85,
608
+ qp0: undefined
609
+ });
610
+
611
+ tv.mockClear();
612
+ const formule = formuleRpn(696000);
613
+ // Pn collectif = 696 kW (ratio 1) -> critère « Pn > 70 »
614
+ expect(tv).toHaveBeenCalledWith(
615
+ 'generateur_combustion',
616
+ expect.objectContaining({ critere_pn: 'Pn > 70' })
617
+ );
618
+ // Référence de régression : rpn = (84 + 2 log10(696)) / 100
619
+ expect(formule(696000)).toBeCloseTo((84 + 2 * Math.log10(696)) / 100, 9);
620
+ });
621
+
622
+ test('formule rpn : ligne absente ou sans rpn -> aucune formule', () => {
623
+ tv.mockReturnValueOnce({ ...ROW_PN });
624
+ let formuleRpn;
625
+ conventionPuissanceNominale.mockImplementation((pn, ratio, _, __, ___, fn) => {
626
+ formuleRpn = fn;
627
+ return { pn, ratio, convention: 'individualisee' };
628
+ });
629
+ const de = {
630
+ enum_type_generateur_ch_id: '89',
631
+ ratio_virtualisation: 0.1,
632
+ presence_ventouse: 0
633
+ };
634
+ tv_generateur_combustion({}, { pn: 23000 }, de, 'ch', 200, -9, 1);
635
+ tv.mockReturnValueOnce(null).mockReturnValueOnce({ ...ROW_PN, rpn: undefined });
636
+ expect(formuleRpn(696000)).toBeUndefined();
637
+ expect(formuleRpn(696000)).toBeUndefined();
638
+ });
639
+
640
+ test('pn recalculé depuis rpn : Pe retenu, ligne re-sélectionnée et avertissement explicite', () => {
641
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
642
+ tv.mockReturnValueOnce({ ...ROW_PN }).mockReturnValue({
643
+ ...ROW_PN,
644
+ tv_generateur_combustion_id: '43'
645
+ });
646
+ conventionPuissanceNominale.mockReturnValue({
647
+ pn: 2300,
648
+ ratio: 0.1,
649
+ convention: 'puissance_depuis_rpn',
650
+ pnCollectif: 23000
651
+ });
652
+ const di = { pn: 230, rpn: 0.8672, rpint: 0.8408 };
653
+ const de = {
654
+ description: 'Chaudière collective',
655
+ enum_type_generateur_ch_id: '89',
656
+ ratio_virtualisation: 0.1,
657
+ presence_ventouse: 0
658
+ };
659
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
660
+ expect(di.pn).toBe(2300);
661
+ expect(de.tv_generateur_combustion_id).toBe(43);
662
+ // Formules évaluées sur Pn collectif = 2300 / 0.1 = 23 kW
663
+ expect(di.rpn).toBeCloseTo((84 + 2 * Math.log10(23)) / 100, 9);
664
+ const message = warnSpy.mock.calls[0][0];
665
+ expect(message).toContain('(230 W)');
666
+ expect(message).toContain('Pn = 23 kW');
667
+ expect(message).toContain('Pe = a × Pn = 2300 W');
668
+ warnSpy.mockRestore();
669
+ });
670
+
671
+ test('fonction de caractéristiques fournie : ligne et formules évaluées sur pn / ratio', () => {
672
+ tvColumnLines.mockReturnValue(['Pn ≤ 70', 'Pn > 70']);
673
+ tv.mockReturnValue({ ...ROW_PN });
674
+ let caracteristiques;
675
+ conventionPuissanceNominale.mockImplementation((pn, ratio, _, fn) => {
676
+ caracteristiques = fn;
677
+ return { pn, ratio, convention: 'individualisee' };
678
+ });
679
+ const di = { pn: 23000 };
680
+ const de = {
681
+ enum_type_generateur_ch_id: '89',
682
+ ratio_virtualisation: 0.1,
683
+ presence_ventouse: 0
684
+ };
685
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
686
+
687
+ tv.mockClear();
688
+ const hypothese = caracteristiques(2300, 0.1);
689
+ // Pn collectif = 2300 / 0.1 = 23 kW -> critère « Pn ≤ 70 »
690
+ expect(tv).toHaveBeenCalledWith(
691
+ 'generateur_combustion',
692
+ expect.objectContaining({ critere_pn: 'Pn ≤ 70' })
693
+ );
694
+ // Référence de régression : rpn = (84 + 2 log10(23)) / 100
695
+ expect(hypothese.rpn).toBeCloseTo((84 + 2 * Math.log10(23)) / 100, 9);
696
+ // qp0 en % : rapporté à la puissance saisie (2300 W)
697
+ expect(hypothese.qp0).toBeCloseTo(23, 9);
698
+ });
699
+
700
+ test('aucune ligne pour l’hypothèse testée : repli sur la ligne initiale', () => {
701
+ tv.mockReturnValueOnce({ ...ROW_PN }).mockReturnValue(null);
702
+ let caracteristiques;
703
+ conventionPuissanceNominale.mockImplementation((pn, ratio, _, fn) => {
704
+ caracteristiques = fn;
705
+ return { pn, ratio, convention: 'individualisee' };
706
+ });
707
+ const di = { pn: 23000 };
708
+ const de = {
709
+ enum_type_generateur_ch_id: '89',
710
+ ratio_virtualisation: 0.1,
711
+ presence_ventouse: 0
712
+ };
713
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
714
+ expect(caracteristiques(23000, 1).rpn).toBeCloseTo((84 + 2 * Math.log10(23)) / 100, 9);
715
+ });
716
+
717
+ test('convention individualisée : pn, ratio et identifiant inchangés', () => {
718
+ tv.mockReturnValue({ ...ROW_PN });
719
+ const di = { pn: 23000 };
720
+ const de = {
721
+ enum_type_generateur_ch_id: '89',
722
+ ratio_virtualisation: 0.1,
723
+ presence_ventouse: 0
724
+ };
725
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
726
+ expect(di.pn).toBe(23000);
727
+ // rpn évalué sur Pn collectif = 230 kW
728
+ expect(di.rpn).toBeCloseTo((84 + 2 * Math.log10(230)) / 100, 9);
729
+ });
730
+
731
+ test('puissance collective virtualisée : pn ramené à Pe, ligne et identifiant re-sélectionnés', () => {
732
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
733
+ tv.mockReturnValueOnce({ ...ROW_PN }).mockReturnValue({
734
+ ...ROW_PN,
735
+ tv_generateur_combustion_id: '43'
736
+ });
737
+ conventionPuissanceNominale.mockReturnValue({
738
+ pn: 2300,
739
+ ratio: 0.1,
740
+ convention: 'collective_virtualisee'
741
+ });
742
+ const di = { pn: 23000, rpn: 0.9, qp0: 23 };
743
+ const de = {
744
+ enum_type_generateur_ch_id: '89',
745
+ ratio_virtualisation: 0.1,
746
+ presence_ventouse: 0
747
+ };
748
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
749
+ expect(di.pn).toBe(2300);
750
+ expect(de.tv_generateur_combustion_id).toBe(43);
751
+ // Formules évaluées sur Pn collectif = 2300 / 0.1 = 23 kW
752
+ expect(di.rpn).toBeCloseTo((84 + 2 * Math.log10(23)) / 100, 9);
753
+ expect(di.rpint).toBeCloseTo((80 + 3 * Math.log10(23)) / 100, 9);
754
+ expect(di.qp0).toBeCloseTo(23, 9);
755
+ expect(warnSpy).toHaveBeenCalled();
756
+ warnSpy.mockRestore();
757
+ });
758
+
759
+ test('puissance collective non virtualisée (bug_for_bug_compat) : calcul sur pn avec ratio 1', () => {
760
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
761
+ state.bug = true;
762
+ tv.mockReturnValue({ ...ROW_PN });
763
+ conventionPuissanceNominale.mockReturnValue({
764
+ pn: 23000,
765
+ ratio: 1,
766
+ convention: 'collective_non_virtualisee'
767
+ });
768
+ const di = { pn: 23000, rpn: 0.87, qp0: 0.23 };
769
+ const de = {
770
+ enum_type_generateur_ch_id: '89',
771
+ ratio_virtualisation: 0.1,
772
+ presence_ventouse: 0
773
+ };
774
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
775
+ expect(di.pn).toBe(23000);
776
+ expect(di.rpn).toBeCloseTo((84 + 2 * Math.log10(23)) / 100, 9);
777
+ expect(di.qp0).toBeCloseTo(230, 9);
778
+ warnSpy.mockRestore();
779
+ });
780
+
781
+ test('générateur collectif par défaut (119, tv_generateur_combustion_id) : pas de détection', () => {
782
+ state.bug = true;
783
+ tv.mockReturnValue({ ...ROW_PN });
784
+ const di = { pn: 23000 };
785
+ const de = {
786
+ enum_type_generateur_ch_id: '119',
787
+ tv_generateur_combustion_id: '42',
788
+ ratio_virtualisation: 0.1,
789
+ presence_ventouse: 0
790
+ };
791
+ tv_generateur_combustion({}, di, de, 'ch', 200, -9, 1);
792
+ expect(conventionPuissanceNominale).not.toHaveBeenCalled();
793
+ });
794
+ });
795
+
796
+ describe('caracteristiquesGenerateurCombustion - selon la méthode de saisie', () => {
797
+ const ROW = { rpn: '84 + 2 logPn', rpint: '80 + 3 logPn', qp0_perc: '1%' };
798
+
799
+ test('méthode forfaitaire : rpn, rpint et qp0 calculés', () => {
800
+ expect(
801
+ Object.keys(caracteristiquesGenerateurCombustion(ROW, 23000, 1, 2.5, -0.8, 'ch', 1))
802
+ ).toEqual(['rpn', 'rpint', 'qp0']);
803
+ });
804
+
805
+ test('méthode 3 : rpn et rpint saisis, seul qp0 calculé', () => {
806
+ expect(caracteristiquesGenerateurCombustion(ROW, 23000, 1, 2.5, -0.8, 'ch', 3)).toEqual({
807
+ qp0: expect.any(Number)
808
+ });
809
+ });
810
+
811
+ test('méthodes 4 et 5 : aucune caractéristique calculée', () => {
812
+ expect(caracteristiquesGenerateurCombustion(ROW, 23000, 1, 2.5, -0.8, 'ch', 4)).toEqual({});
813
+ expect(caracteristiquesGenerateurCombustion(ROW, 23000, 1, 2.5, -0.8, 'ch', 5)).toEqual({});
814
+ });
815
+
816
+ test('qp0 constant en kW (sans % ni Pn) : ramené au logement par le ratio', () => {
817
+ expect(
818
+ caracteristiquesGenerateurCombustion({ qp0_perc: '0.5' }, 2300, 0.1, 2.5, -0.8, 'ecs', 1).qp0
819
+ ).toBeCloseTo(50, 9);
820
+ });
821
+ });