@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.
Files changed (107) hide show
  1. package/10_besoin_fr.spec.js +122 -0
  2. package/10_clim.spec.js +116 -0
  3. package/11_besoin_ecs.spec.js +69 -0
  4. package/11_ecs.spec.js +282 -0
  5. package/11_nadeq.spec.js +37 -0
  6. package/12.4_pac.spec.js +131 -0
  7. package/13.2_generateur_combustion.js +2 -0
  8. package/13.2_generateur_combustion.spec.js +537 -0
  9. package/13.2_generateur_combustion_bouilleur.js +4 -1
  10. package/13.2_generateur_combustion_bouilleur.spec.js +74 -0
  11. package/13.2_generateur_combustion_ch.spec.js +323 -0
  12. package/13.2_generateur_combustion_chaudiere.spec.js +85 -0
  13. package/13.2_generateur_pac.spec.js +64 -0
  14. package/13_rendement_distribution_ecs.spec.js +38 -0
  15. package/14_generateur_ecs.js +4 -4
  16. package/14_generateur_ecs.spec.js +745 -0
  17. package/15_conso_aux.spec.js +225 -0
  18. package/16.2_production_enr.spec.js +188 -0
  19. package/16_conso_eclairage.spec.js +62 -0
  20. package/2021_04_13_confort_ete.spec.js +159 -0
  21. package/2021_04_13_qualite_isolation.js +1 -1
  22. package/2021_04_13_qualite_isolation.spec.js +286 -0
  23. package/3.1_b.js +1 -1
  24. package/3.1_b.spec.js +367 -0
  25. package/3.2.1_mur.spec.js +812 -39
  26. package/3.2.2_plancher_bas.spec.js +643 -78
  27. package/3.2.3_plancher_haut.spec.js +452 -0
  28. package/3.3.1.4_porte.spec.js +114 -0
  29. package/3.3_baie_vitree.spec.js +449 -295
  30. package/3.4_pont_thermique.spec.js +890 -0
  31. package/3_deperdition.spec.js +397 -0
  32. package/4_ventilation.js +1 -1
  33. package/4_ventilation.spec.js +406 -0
  34. package/5_conso_ventilation.spec.js +186 -0
  35. package/6.2_surface_sud_equivalente.spec.js +138 -0
  36. package/8_intermittence.spec.js +33 -0
  37. package/9_besoin_ch.js +2 -2
  38. package/9_besoin_ch.spec.js +381 -0
  39. package/9_chauffage.js +4 -0
  40. package/9_chauffage.spec.js +478 -92
  41. package/9_conso_ch.spec.js +502 -248
  42. package/9_emetteur_ch.spec.js +339 -0
  43. package/9_generateur_ch.spec.js +467 -81
  44. package/apport_et_besoin.spec.js +126 -0
  45. package/conso.js +1 -1
  46. package/conso.spec.js +471 -82
  47. package/core/assets/domain/add-additionnal-ue-values-tables.spec.js +53 -0
  48. package/core/assets/domain/synchronize-solicitations-tables.spec.js +25 -0
  49. package/core/assets/domain/synchronize-valeur-tables.spec.js +48 -0
  50. package/core/conf/infrastructure/application.config.spec.js +35 -0
  51. package/core/file/infrastructure/adapter/csv-parser.store.spec.js +31 -13
  52. package/core/file/infrastructure/adapter/file.store.spec.js +148 -12
  53. package/core/tv/infrastructure/assets/additional-ue-values.spec.js +36 -0
  54. package/core/util/infrastructure/object-util.spec.js +20 -0
  55. package/core/util/logger/log-service.js +16 -7
  56. package/core/util/logger/log-service.spec.js +101 -0
  57. package/dpe-sanitizer.service.spec.js +138 -0
  58. package/engine.js +4 -1
  59. package/engine.spec.js +854 -0
  60. package/enums.spec.js +46 -0
  61. package/features/dpe/domain/models/type-generateur.model.js +1 -2
  62. package/features/dpe/domain/models/type-generateur.model.spec.js +25 -0
  63. package/features/dpe/domain/models/type-habitation.model.spec.js +41 -0
  64. package/features/dpe/domain/models/type-stockage.model.spec.js +18 -0
  65. package/features/dpe/domain/models/type-ventilation.model.spec.js +19 -0
  66. package/features/dpe/infrastructure/ch/chTv.store.spec.js +4 -4
  67. package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.js +14 -2
  68. package/features/dpe/infrastructure/enveloppe/baieVitreeTv.store.spec.js +16 -0
  69. package/features/dpe/infrastructure/tv.store.js +28 -9
  70. package/features/dpe/infrastructure/tv.store.spec.js +44 -0
  71. package/features/engine/domain/apport_et_besoin/apport-et-besoin.service.spec.js +2 -1
  72. package/features/engine/domain/apport_et_besoin/ch/besoin-ch.service.spec.js +18 -0
  73. package/features/engine/domain/apport_et_besoin/ch/perte-ch-recup.service.spec.js +33 -1
  74. package/features/engine/domain/apport_et_besoin/ecs/besoin-ecs.service.spec.js +21 -1
  75. package/features/engine/domain/apport_et_besoin/ecs/perte-ecs-recup.service.spec.js +41 -1
  76. package/features/engine/domain/apport_et_besoin/froid/besoin-froid.service.spec.js +44 -1
  77. package/features/engine/domain/apport_et_besoin/surface-sud-equivalente.service.spec.js +2 -1
  78. package/features/engine/domain/ch/generateur-ch.service.js +2 -0
  79. package/features/engine/domain/ch/generateur-ch.service.spec.js +117 -2
  80. package/features/engine/domain/ch/installation-ch.service.spec.js +14 -0
  81. package/features/engine/domain/conso/froid/conso-froid.service.spec.js +82 -1
  82. package/features/engine/domain/constants.spec.js +14 -0
  83. package/features/engine/domain/contexte.builder.spec.js +21 -0
  84. package/features/engine/domain/ecs/generateur-ecs.service.spec.js +11 -0
  85. package/features/engine/domain/ecs/installation-ecs.service.spec.js +55 -2
  86. package/features/engine/domain/engine.service.spec.js +227 -4
  87. package/features/engine/domain/enveloppe/baie_vitree/deperdition-baie-vitree.service.spec.js +124 -1
  88. package/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +270 -2
  89. package/features/engine/domain/enveloppe/deperdition.service.spec.js +141 -0
  90. package/features/engine/domain/enveloppe/espace_tampon/espace-tampon.service.spec.js +2 -1
  91. package/features/engine/domain/enveloppe/mur/deperdition-mur.service.spec.js +117 -1
  92. package/features/engine/domain/enveloppe/plancher_bas/deperdition-plancher-bas.service.spec.js +134 -1
  93. package/features/engine/domain/enveloppe/plancher_haut/deperdition-plancher-haut.service.spec.js +126 -1
  94. package/features/engine/domain/enveloppe/pont_thermique/deperdition-pont-thermique.service.spec.js +18 -1
  95. package/features/engine/domain/enveloppe/porte/deperdition-porte.service.spec.js +2 -1
  96. package/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +67 -1
  97. package/features/normalizer/domain/dpe-normalizer.service.js +1 -1
  98. package/features/normalizer/domain/dpe-normalizer.service.spec.js +53 -2
  99. package/ficheTechnique.spec.js +26 -0
  100. package/index.spec.js +54 -0
  101. package/package.json +5 -2
  102. package/preprocessor/index.js +20 -0
  103. package/preprocessor/index.spec.js +15 -0
  104. package/tv.spec.js +38 -0
  105. package/utils.js +43 -1
  106. package/utils.spec.js +518 -2
  107. package/output.js +0 -1
@@ -1,334 +1,488 @@
1
- import calc_bv from './3.3_baie_vitree.js';
2
- import { PRECISION } from '../test/constant.js';
3
- import { describe, expect, test } from 'vitest';
4
-
5
- describe('Recherche de bugs dans le calcul de déperdition des baies vitrées', () => {
6
- test('calcul de déperdition pour les murs de 2187E0982013C baie 1', () => {
7
- const zc = 3;
8
- const bv = {
9
- donnee_entree: {
10
- description:
11
- 'Fenêtre 1 Nord - Fenêtres battantes bois, orientées Nord, simple vitrage avec volets battants bois',
12
- enum_type_adjacence_id: '1',
13
- surface_totale_baie: 3.2,
14
- nb_baie: 1,
15
- tv_ug_id: 1,
16
- enum_type_vitrage_id: '1',
17
- enum_inclinaison_vitrage_id: '3',
18
- enum_methode_saisie_perf_vitrage_id: '1',
19
- tv_uw_id: 561,
20
- enum_type_materiaux_menuiserie_id: '3',
21
- enum_type_baie_id: '4',
22
- double_fenetre: 0,
23
- uw_1: 5.4,
24
- sw_1: 0.52,
25
- tv_deltar_id: 6,
26
- tv_ujn_id: 224,
27
- enum_type_fermeture_id: '7',
28
- presence_retour_isolation: 0,
29
- largeur_dormant: 5,
30
- tv_sw_id: 89,
31
- enum_type_pose_id: '3',
32
- enum_orientation_id: '2',
33
- tv_coef_masque_proche_id: 19,
34
- masque_lointain_non_homogene_collection: '',
35
- reference: 'baie_vitree_0',
36
- tv_coef_reduction_deperdition_id: 1
37
- },
38
- donnee_intermediaire: {
39
- b: 1,
40
- sw: 0.52,
41
- ug: 5.8,
42
- uw: 5.4,
43
- ujn: 3.8,
44
- u_menuiserie: 3.8,
45
- fe2: 1,
46
- fe1: 1
47
- }
48
- };
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
+
3
+ /**
4
+ * Dépendances mockées pour isoler `calc_bv` (calcul des déperditions des baies vitrées) :
5
+ * - `b` (3.1_b.js) : renseigne simplement `di.b` (coefficient de réduction) ;
6
+ * - `utils` : `tv` (table de valeurs, dispatchée par nom de table), `requestInput` /
7
+ * `requestInputID` (passe-plats vers les données d'entrée), `getRange` (encadrement contrôlé),
8
+ * `bug_for_bug_compat` (accesseur adossé à un état hoisté, activable par test) ;
9
+ * - `tv.js` : table `uw` réduite aux lignes utiles à l'interpolation.
10
+ */
11
+ const state = vi.hoisted(() => ({ bug: false }));
12
+
13
+ vi.mock('./3.1_b.js', () => ({
14
+ default: vi.fn((di) => {
15
+ di.b = 1;
16
+ })
17
+ }));
18
+
19
+ vi.mock('./utils.js', () => ({
20
+ tv: vi.fn(),
21
+ requestInput: (de, du, field) => de[field],
22
+ requestInputID: (de, du, field) => de[`enum_${field}_id`],
23
+ getRange: vi.fn(),
24
+ get bug_for_bug_compat() {
25
+ return state.bug;
26
+ }
27
+ }));
28
+
29
+ vi.mock('./tv.js', () => ({
30
+ default: {
31
+ uw: [
32
+ { enum_type_baie_id: '4', enum_type_materiaux_menuiserie_id: '3', ug: '2.8' },
33
+ { enum_type_baie_id: '4', enum_type_materiaux_menuiserie_id: '3', ug: '4' }
34
+ ]
35
+ }
36
+ }));
37
+
38
+ const { default: calc_bv } = await import('./3.3_baie_vitree.js');
39
+ const { tv, getRange } = await import('./utils.js');
40
+
41
+ /**
42
+ * Lignes retournées par défaut pour chaque table de valeurs. La table `uw` est dispatchée
43
+ * selon la borne d'encadrement (`^2.8$` ou `^4$`) afin de piloter l'interpolation.
44
+ */
45
+ function defaultRows() {
46
+ return {
47
+ ug: { ug: '2.8', tv_ug_id: '5' },
48
+ sw: { sw: '0.5', tv_sw_id: '1' },
49
+ uw: (matcher) => (matcher.ug === '^4$' ? { uw: '1.5' } : { uw: '3', tv_uw_id: '10' }),
50
+ deltar: { deltar: '0.1', tv_deltar_id: '6' },
51
+ ujn: { ujn: '2.4', tv_ujn_id: '201' },
52
+ coef_masque_proche: { fe1: '0.8', tv_coef_masque_proche_id: '19' },
53
+ coef_masque_lointain_homogene: { fe2: '0.9', tv_coef_masque_lointain_homogene_id: '3' },
54
+ coef_masque_lointain_non_homoge: { omb: '20' }
55
+ };
56
+ }
57
+
58
+ let rows;
59
+ let errorSpy;
60
+ let logSpy;
61
+
62
+ beforeEach(() => {
63
+ vi.mocked(tv).mockReset();
64
+ vi.mocked(getRange).mockReset();
65
+ state.bug = false;
66
+ rows = defaultRows();
67
+
68
+ vi.mocked(tv).mockImplementation((table, matcher) => {
69
+ const entry = rows[table];
70
+ return typeof entry === 'function' ? entry(matcher) : entry;
71
+ });
72
+ // Encadrement par défaut : bornes identiques => interpolation dégénérée (delta_ug = 0)
73
+ vi.mocked(getRange).mockReturnValue([2.8, 2.8]);
74
+
75
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
76
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
77
+ });
78
+
79
+ afterEach(() => {
80
+ errorSpy.mockRestore();
81
+ logSpy.mockRestore();
82
+ });
83
+
84
+ /** Baie vitrée double vitrage, avec fermeture, sans masque ni double-fenêtre. */
85
+ function baseBv(overrides = {}) {
86
+ return {
87
+ donnee_entree: {
88
+ description: 'Fenêtre test',
89
+ enum_type_vitrage_id: '2',
90
+ enum_type_gaz_lame_id: '1',
91
+ enum_inclinaison_vitrage_id: '3',
92
+ vitrage_vir: 0,
93
+ epaisseur_lame: 12,
94
+ enum_type_baie_id: '4',
95
+ enum_type_materiaux_menuiserie_id: '3',
96
+ enum_type_pose_id: '3',
97
+ enum_type_fermeture_id: '7',
98
+ type_fermeture: 'volets battants bois',
99
+ surface_totale_baie: 2.6,
100
+ ...overrides
101
+ }
102
+ };
103
+ }
104
+
105
+ /**
106
+ * 3.3 Coefficients de transmission thermique et facteur solaire des baies vitrées.
107
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.3
108
+ */
109
+ describe('calc_bv - parcours nominal (double vitrage avec fermeture)', () => {
110
+ test('renseigne b, sw, ug, uw, ujn et u_menuiserie', () => {
111
+ const bv = baseBv();
112
+ calc_bv(bv, 1);
49
113
 
50
- calc_bv(bv, zc);
114
+ const di = bv.donnee_intermediaire;
115
+ expect(di.b).toBe(1);
116
+ expect(di.sw).toBe(0.5);
117
+ expect(di.ug).toBe(2.8);
118
+ expect(di.uw).toBe(3); // getRange [2.8, 2.8] => delta_ug = 0 => uw = uw(^2.8$)
119
+ expect(di.ujn).toBe(2.4);
120
+ expect(di.u_menuiserie).toBe(2.4); // fermeture présente => u_menuiserie = ujn
121
+ expect(di.fe1).toBe(1); // pas de masque proche
122
+ expect(di.fe2).toBe(1); // pas de masque lointain
123
+ expect(di.deltar).toBeUndefined(); // supprimé en fin de calcul
124
+ expect(bv.donnee_entree.tv_ug_id).toBe(5);
125
+ });
126
+
127
+ test('matcher ug enrichi pour un double vitrage (gaz, inclinaison, vir, épaisseur)', () => {
128
+ const bv = baseBv();
129
+ calc_bv(bv, 1);
51
130
 
52
- expect(bv.donnee_intermediaire.b).toBe(1);
131
+ expect(tv).toHaveBeenCalledWith('ug', {
132
+ enum_type_vitrage_id: '2',
133
+ enum_type_gaz_lame_id: '1',
134
+ enum_inclinaison_vitrage_id: '3',
135
+ vitrage_vir: 0,
136
+ epaisseur_lame: 12
137
+ });
138
+ });
139
+ });
140
+
141
+ describe('tv_ug - coefficient de transmission du vitrage', () => {
142
+ test('simple vitrage (type 1) : matcher réduit au seul type de vitrage', () => {
143
+ rows.ug = { ug: '5.8', tv_ug_id: '1' };
144
+ const bv = baseBv({ enum_type_vitrage_id: '1' });
145
+ calc_bv(bv, 1);
146
+
147
+ expect(tv).toHaveBeenCalledWith('ug', { enum_type_vitrage_id: '1' });
53
148
  expect(bv.donnee_intermediaire.ug).toBe(5.8);
54
- expect(bv.donnee_intermediaire.uw).toBe(5.4);
55
- expect(bv.donnee_intermediaire.ujn).toBe(3.8);
56
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(3.8);
57
- expect(bv.donnee_intermediaire.sw).toBe(0.52);
58
- expect(bv.donnee_intermediaire.fe1).toBe(1);
59
- expect(bv.donnee_intermediaire.fe2).toBe(1);
60
149
  });
61
150
 
62
- test('calcul de déperdition pour les murs de 2187E0982013C baie 2', () => {
63
- const zc = 3;
64
- const bv = {
65
- donnee_entree: {
66
- description:
67
- "Fenêtre 2 Est - Fenêtres battantes bois, orientées Est, double vitrage avec lame d'air 12 mm et volets battants bois",
68
- enum_type_adjacence_id: '1',
69
- surface_totale_baie: 2.6,
70
- nb_baie: 2,
71
- tv_ug_id: 5,
72
- enum_type_vitrage_id: '2',
73
- enum_inclinaison_vitrage_id: '3',
74
- enum_type_gaz_lame_id: '1',
75
- epaisseur_lame: 12,
76
- vitrage_vir: 0,
77
- enum_methode_saisie_perf_vitrage_id: '1',
78
- tv_uw_id: 548,
79
- enum_type_materiaux_menuiserie_id: '3',
80
- enum_type_baie_id: '4',
81
- double_fenetre: 0,
82
- uw_1: 3,
83
- sw_1: 0.47000000000000003,
84
- tv_deltar_id: 6,
85
- tv_ujn_id: 201,
86
- enum_type_fermeture_id: '7',
87
- presence_retour_isolation: 0,
88
- largeur_dormant: 5,
89
- tv_sw_id: 90,
90
- enum_type_pose_id: '3',
91
- enum_orientation_id: '3',
92
- tv_coef_masque_proche_id: 19,
93
- masque_lointain_non_homogene_collection: ''
94
- },
95
- donnee_intermediaire: {
96
- b: 1,
97
- ug: 2.8,
98
- uw: 3,
99
- ujn: 2.4,
100
- u_menuiserie: 2.4,
101
- sw: 0.47000000000000003,
102
- fe1: 1,
103
- fe2: 1
104
- }
105
- };
151
+ test('type de vitrage absent : matcher réduit (aucun enrichissement)', () => {
152
+ rows.ug = { ug: '5.8', tv_ug_id: '1' };
153
+ const bv = baseBv({ enum_type_vitrage_id: undefined });
154
+ calc_bv(bv, 1);
106
155
 
107
- calc_bv(bv, zc);
156
+ expect(tv).toHaveBeenCalledWith('ug', { enum_type_vitrage_id: undefined });
157
+ });
108
158
 
109
- expect(bv.donnee_intermediaire.b).toBe(1);
110
- expect(bv.donnee_intermediaire.ug).toBe(2.8);
111
- expect(bv.donnee_intermediaire.uw).toBe(3);
112
- expect(bv.donnee_intermediaire.ujn).toBe(2.4);
113
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(2.4);
114
- expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.47000000000000003, PRECISION);
115
- expect(bv.donnee_intermediaire.fe1).toBe(1);
116
- expect(bv.donnee_intermediaire.fe2).toBe(1);
159
+ test('aucune ligne ug trouvée : erreur émise', () => {
160
+ rows.ug = null;
161
+ const bv = baseBv();
162
+ calc_bv(bv, 1);
163
+
164
+ expect(errorSpy).toHaveBeenCalledWith('!! pas de valeur forfaitaire trouvée pour ug !!');
165
+ expect(bv.donnee_intermediaire.ug).toBeUndefined();
117
166
  });
167
+ });
118
168
 
119
- test('calcul de déperdition pour les murs de 2187E0982013C baie 3', () => {
120
- const zc = 3;
121
- const bv = {
122
- donnee_entree: {
123
- description:
124
- "Fenêtre 3 Est - Fenêtres battantes bois, orientées Est, double vitrage avec lame d'air 12 mm et volets battants bois",
125
- enum_type_adjacence_id: '1',
126
- surface_totale_baie: 4.5,
127
- nb_baie: 3,
128
- tv_ug_id: 5,
129
- enum_type_vitrage_id: '2',
130
- enum_inclinaison_vitrage_id: '3',
131
- enum_type_gaz_lame_id: '1',
132
- epaisseur_lame: 12,
133
- vitrage_vir: 0,
134
- enum_methode_saisie_perf_vitrage_id: '1',
135
- tv_uw_id: 548,
136
- enum_type_materiaux_menuiserie_id: '3',
137
- enum_type_baie_id: '4',
138
- double_fenetre: 0,
139
- uw_1: 3,
140
- sw_1: 0.47000000000000003,
141
- tv_deltar_id: 6,
142
- tv_ujn_id: 201,
143
- enum_type_fermeture_id: '7',
144
- presence_retour_isolation: 0,
145
- largeur_dormant: 5,
146
- tv_sw_id: 90,
147
- enum_type_pose_id: '3',
148
- enum_orientation_id: '3',
149
- tv_coef_masque_proche_id: 19,
150
- masque_lointain_non_homogene_collection: ''
151
- },
152
- donnee_intermediaire: {
153
- b: 1,
154
- ug: 2.8,
155
- uw: 3,
156
- ujn: 2.4,
157
- u_menuiserie: 2.4,
158
- sw: 0.47000000000000003,
159
- fe1: 1,
160
- fe2: 1
161
- }
162
- };
169
+ describe('tv_sw - facteur solaire', () => {
170
+ test('sw saisi : valeur reprise directement sans accès table', () => {
171
+ const bv = baseBv({ sw_saisi: 0.42 });
172
+ calc_bv(bv, 1);
163
173
 
164
- calc_bv(bv, zc);
174
+ expect(bv.donnee_intermediaire.sw).toBe(0.42);
175
+ expect(tv).not.toHaveBeenCalledWith('sw', expect.anything());
176
+ });
165
177
 
166
- expect(bv.donnee_intermediaire.b).toBe(1);
167
- expect(bv.donnee_intermediaire.ug).toBe(2.8);
168
- expect(bv.donnee_intermediaire.uw).toBe(3);
169
- expect(bv.donnee_intermediaire.ujn).toBe(2.4);
170
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(2.4);
171
- expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.47000000000000003, PRECISION);
172
- expect(bv.donnee_intermediaire.fe1).toBe(1);
173
- expect(bv.donnee_intermediaire.fe2).toBe(1);
178
+ test('menuiserie brique de verre (matériau 1) : ni vir ni pose dans le matcher', () => {
179
+ const bv = baseBv({ enum_type_materiaux_menuiserie_id: '1' });
180
+ calc_bv(bv, 1);
181
+
182
+ expect(tv).toHaveBeenCalledWith('sw', {
183
+ enum_type_vitrage_id: '2',
184
+ enum_type_baie_id: '4',
185
+ enum_type_materiaux_menuiserie_id: '1'
186
+ });
187
+ });
188
+
189
+ test('menuiserie classique : matcher enrichi (vir + pose)', () => {
190
+ const bv = baseBv();
191
+ calc_bv(bv, 1);
192
+
193
+ expect(tv).toHaveBeenCalledWith('sw', {
194
+ enum_type_vitrage_id: '2',
195
+ enum_type_baie_id: '4',
196
+ enum_type_materiaux_menuiserie_id: '3',
197
+ vitrage_vir: 0,
198
+ enum_type_pose_id: '3'
199
+ });
200
+ });
201
+
202
+ test('aucune ligne sw trouvée : erreur émise', () => {
203
+ rows.sw = null;
204
+ const bv = baseBv();
205
+ calc_bv(bv, 1);
206
+
207
+ expect(errorSpy).toHaveBeenCalledWith('!! pas de valeur forfaitaire trouvée pour sw !!');
174
208
  });
209
+ });
210
+
211
+ describe('tv_uw - coefficient de transmission de la fenêtre', () => {
212
+ test('uw saisi : valeur reprise directement', () => {
213
+ const bv = baseBv({ uw_saisi: 2.1 });
214
+ calc_bv(bv, 1);
215
+ expect(bv.donnee_intermediaire.uw).toBe(2.1);
216
+ });
217
+
218
+ test('paroi en brique de verre (type baie 1) : uw lu directement dans la table', () => {
219
+ rows.uw = { uw: '2', tv_uw_id: '10' };
220
+ const bv = baseBv({ enum_type_baie_id: '1' });
221
+ calc_bv(bv, 1);
222
+
223
+ expect(tv).toHaveBeenCalledWith('uw', { enum_type_baie_id: '1' });
224
+ expect(bv.donnee_intermediaire.uw).toBe(2);
225
+ });
226
+
227
+ test('brique de verre sans ligne uw : uw indéfini et erreur émise', () => {
228
+ rows.uw = null;
229
+ const bv = baseBv({ enum_type_baie_id: '1' });
230
+ calc_bv(bv, 1);
231
+
232
+ expect(bv.donnee_intermediaire.uw).toBeUndefined();
233
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Pas de valeur forfaitaire uw'));
234
+ });
235
+
236
+ test('interpolation entre deux Ug tabulés (delta_ug ≠ 0)', () => {
237
+ rows.ug = { ug: '3', tv_ug_id: '5' };
238
+ getRange.mockReturnValue([2.8, 4]);
239
+ const bv = baseBv();
240
+ calc_bv(bv, 1);
175
241
 
176
- test('calcul de déperdition pour les murs de 2187E0982013C baie 4', () => {
177
- const zc = 3;
178
- const bv = {
242
+ // uw = 3 + (1,5 - 3) * (3 - 2,8) / (4 - 2,8) = 2,75 (référence de régression)
243
+ expect(bv.donnee_intermediaire.uw).toBeCloseTo(2.75, 9);
244
+ });
245
+
246
+ test('interpolation sans ligne uw exploitable : uw indéfini et erreur émise', () => {
247
+ getRange.mockReturnValue([2.8, 4]);
248
+ rows.uw = null;
249
+ const bv = baseBv();
250
+ calc_bv(bv, 1);
251
+
252
+ expect(bv.donnee_intermediaire.uw).toBeUndefined();
253
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Pas de valeur forfaitaire uw'));
254
+ });
255
+ });
256
+
257
+ describe('calc_bv - traitement de la fermeture', () => {
258
+ test('ujn saisi : valeur reprise, u_menuiserie = ujn', () => {
259
+ const bv = baseBv({ ujn_saisi: 1.9 });
260
+ calc_bv(bv, 1);
261
+
262
+ expect(bv.donnee_intermediaire.ujn).toBe(1.9);
263
+ expect(bv.donnee_intermediaire.u_menuiserie).toBe(1.9);
264
+ });
265
+
266
+ test('absence de fermeture : u_menuiserie = uw (pas de calcul de ujn)', () => {
267
+ const bv = baseBv({ type_fermeture: 'abscence de fermeture pour la baie vitrée' });
268
+ calc_bv(bv, 1);
269
+
270
+ expect(bv.donnee_intermediaire.u_menuiserie).toBe(3); // = uw
271
+ expect(bv.donnee_intermediaire.ujn).toBeUndefined();
272
+ });
273
+
274
+ test('aucune ligne deltar trouvée : erreur émise', () => {
275
+ rows.deltar = null;
276
+ const bv = baseBv();
277
+ calc_bv(bv, 1);
278
+
279
+ expect(errorSpy).toHaveBeenCalledWith('!! pas de valeur forfaitaire trouvée pour deltar !!');
280
+ });
281
+
282
+ test('aucune ligne ujn trouvée : erreur émise', () => {
283
+ rows.ujn = null;
284
+ const bv = baseBv();
285
+ calc_bv(bv, 1);
286
+
287
+ expect(errorSpy).toHaveBeenCalledWith('!! pas de valeur forfaitaire trouvée pour ujn !!');
288
+ });
289
+ });
290
+
291
+ /**
292
+ * 3.3.2 / 6.2.1 Traitement des doubles fenêtres : sw et uw équivalents.
293
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.3.2
294
+ */
295
+ describe('calc_bv - double fenêtre', () => {
296
+ /** Baie secondaire (deuxième fenêtre) mesurable pour sw / uw équivalents. */
297
+ function secondFenetre(overrides = {}) {
298
+ return {
179
299
  donnee_entree: {
180
- description:
181
- "Fenêtre 4 Ouest - Fenêtres battantes bois, orientées Ouest, double vitrage avec lame d'air 12 mm et volets battants bois",
182
- enum_type_adjacence_id: '1',
183
- surface_totale_baie: 3.84,
184
- nb_baie: 4,
185
- tv_ug_id: 5,
300
+ description: 'Seconde fenêtre',
186
301
  enum_type_vitrage_id: '2',
187
- enum_inclinaison_vitrage_id: '3',
188
302
  enum_type_gaz_lame_id: '1',
189
- epaisseur_lame: 12,
303
+ enum_inclinaison_vitrage_id: '3',
190
304
  vitrage_vir: 0,
191
- enum_methode_saisie_perf_vitrage_id: '1',
192
- tv_uw_id: 548,
193
- enum_type_materiaux_menuiserie_id: '3',
305
+ epaisseur_lame: 12,
194
306
  enum_type_baie_id: '4',
195
- double_fenetre: 0,
196
- uw_1: 3,
197
- sw_1: 0.47000000000000003,
198
- tv_deltar_id: 6,
199
- tv_ujn_id: 201,
200
- enum_type_fermeture_id: '7',
201
- presence_retour_isolation: 0,
202
- largeur_dormant: 5,
203
- tv_sw_id: 90,
307
+ enum_type_materiaux_menuiserie_id: '3',
204
308
  enum_type_pose_id: '3',
205
- enum_orientation_id: '4',
206
- tv_coef_masque_proche_id: 19,
207
- masque_lointain_non_homogene_collection: ''
309
+ ...overrides
208
310
  },
209
- donnee_intermediaire: {
210
- b: 1,
211
- ug: 2.8,
212
- uw: 3,
213
- ujn: 2.4,
214
- u_menuiserie: 2.4,
215
- sw: 0.47000000000000003,
216
- fe1: 1,
217
- fe2: 1
218
- }
311
+ donnee_intermediaire: {}
219
312
  };
313
+ }
314
+
315
+ test('sw et uw équivalents calculés à partir des deux fenêtres', () => {
316
+ const bv = baseBv({ double_fenetre: 1 });
317
+ bv.baie_vitree_double_fenetre = secondFenetre();
318
+ calc_bv(bv, 1);
319
+
320
+ // sw équivalent = sw1 * sw2 = 0,5 * 0,5 = 0,25
321
+ expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.25, 9);
322
+ // uw équivalent = 1 / (1/3 + 1/3 + 0,07) (référence de régression)
323
+ expect(bv.donnee_intermediaire.uw).toBeCloseTo(1.3574660633484164, 9);
324
+ });
325
+
326
+ test('sw et uw saisis sur la seconde fenêtre : valeurs reprises directement', () => {
327
+ const bv = baseBv({ double_fenetre: 1 });
328
+ bv.baie_vitree_double_fenetre = secondFenetre({ sw_saisi: 0.8, uw_saisi: 2 });
329
+ calc_bv(bv, 1);
330
+
331
+ // sw équivalent = 0,5 * 0,8 = 0,4
332
+ expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.4, 9);
333
+ // uw équivalent = 1 / (1/3 + 1/2 + 0,07)
334
+ expect(bv.donnee_intermediaire.uw).toBeCloseTo(1 / (1 / 3 + 1 / 2 + 0.07), 9);
335
+ });
336
+
337
+ test('sw nul sur la seconde fenêtre : facteur multiplicatif ramené à 1', () => {
338
+ // La seconde fenêtre (matériau brique de verre) renvoie un sw nul => on retombe sur 1
339
+ rows.sw = (matcher) =>
340
+ matcher.enum_type_materiaux_menuiserie_id === '1'
341
+ ? { sw: '0' }
342
+ : { sw: '0.5', tv_sw_id: '1' };
343
+ const bv = baseBv({ double_fenetre: 1 });
344
+ bv.baie_vitree_double_fenetre = secondFenetre({ enum_type_materiaux_menuiserie_id: '1' });
345
+ calc_bv(bv, 1);
220
346
 
221
- calc_bv(bv, zc);
347
+ // sw équivalent = sw1 * 1 = 0,5
348
+ expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.5, 9);
349
+ });
222
350
 
223
- expect(bv.donnee_intermediaire.b).toBe(1);
224
- expect(bv.donnee_intermediaire.ug).toBe(2.8);
351
+ test('double_fenetre = 1 mais aucune baie secondaire : traitement ignoré', () => {
352
+ const bv = baseBv({ double_fenetre: 1 });
353
+ calc_bv(bv, 1);
354
+
355
+ // sw et uw restent ceux de la fenêtre principale
356
+ expect(bv.donnee_intermediaire.sw).toBe(0.5);
225
357
  expect(bv.donnee_intermediaire.uw).toBe(3);
226
- expect(bv.donnee_intermediaire.ujn).toBe(2.4);
227
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(2.4);
228
- expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.47000000000000003, PRECISION);
229
- expect(bv.donnee_intermediaire.fe1).toBe(1);
230
- expect(bv.donnee_intermediaire.fe2).toBe(1);
231
358
  });
232
359
 
233
- test('calcul de déperdition pour les murs de 2187E0982013C baie 5', () => {
234
- const zc = 3;
235
- const bv = {
236
- donnee_entree: {
237
- description:
238
- "Fenêtre 5 Ouest - Fenêtres battantes bois, orientées Ouest, double vitrage avec lame d'air 12 mm",
239
- enum_type_adjacence_id: '1',
240
- surface_totale_baie: 0.4,
241
- nb_baie: 1,
242
- tv_ug_id: 5,
243
- enum_type_vitrage_id: '2',
244
- enum_inclinaison_vitrage_id: '3',
245
- enum_type_gaz_lame_id: '1',
246
- epaisseur_lame: 12,
247
- vitrage_vir: 0,
248
- enum_methode_saisie_perf_vitrage_id: '1',
249
- tv_uw_id: 548,
250
- enum_type_materiaux_menuiserie_id: '3',
251
- enum_type_baie_id: '4',
252
- double_fenetre: 0,
253
- uw_1: 3,
254
- sw_1: 0.47000000000000003,
255
- enum_type_fermeture_id: '1',
256
- presence_retour_isolation: 0,
257
- largeur_dormant: 5,
258
- tv_sw_id: 90,
259
- enum_type_pose_id: '3',
260
- enum_orientation_id: '4',
261
- tv_coef_masque_proche_id: 19,
262
- masque_lointain_non_homogene_collection: ''
263
- },
264
- donnee_intermediaire: {
265
- b: 1,
266
- ug: 2.8,
267
- uw: 3,
268
- u_menuiserie: 3,
269
- sw: 0.47000000000000003,
270
- fe1: 1,
271
- fe2: 1
360
+ /**
361
+ * Compat bug : sw et uw sont saisis sur la fenêtre principale mais égaux aux valeurs
362
+ * équivalentes calculées ; ce sont ces dernières qui sont conservées (avec erreur).
363
+ */
364
+ test('compat bug : sw/uw saisis mais égaux aux équivalents => équivalents conservés', () => {
365
+ state.bug = true;
366
+ // sw saisi = 0,5 (fenêtre principale), sw seconde fenêtre = 0,5 => produit = 0,25
367
+ const swEquiv = 0.5 * 0.5;
368
+ // uw saisi = 3 (fenêtre principale), uw seconde fenêtre = 3 => équivalent
369
+ const uwEquiv = 1 / (1 / 3 + 1 / 3 + 0.07);
370
+
371
+ const bv = baseBv({ double_fenetre: 1, sw_saisi: 0.5, uw_saisi: 3 });
372
+ // valeurs intermédiaires préexistantes égales aux équivalents des deux fenêtres
373
+ bv.donnee_intermediaire = { sw: swEquiv, uw: uwEquiv };
374
+ bv.baie_vitree_double_fenetre = secondFenetre();
375
+
376
+ calc_bv(bv, 1);
377
+
378
+ expect(bv.donnee_intermediaire.sw).toBeCloseTo(swEquiv, 9);
379
+ expect(bv.donnee_intermediaire.uw).toBeCloseTo(uwEquiv, 9);
380
+ expect(errorSpy).toHaveBeenCalled();
381
+ });
382
+ });
383
+
384
+ /**
385
+ * 6.3 Masques proches et lointains.
386
+ * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §6.3
387
+ */
388
+ describe('calc_bv - masques lointains non homogènes (fe2)', () => {
389
+ test('collection de masques : fe2 = 1 - somme des occultations', () => {
390
+ const bv = baseBv({
391
+ masque_lointain_non_homogene_collection: {
392
+ masque_lointain_non_homogene: [
393
+ { tv_coef_masque_lointain_non_homogene_id: 1 },
394
+ { tv_coef_masque_lointain_non_homogene_id: 2 }
395
+ ]
272
396
  }
273
- };
397
+ });
398
+ // omb = 20 par masque => fe2 = 1 - 0,2 - 0,2 = 0,6
399
+ calc_bv(bv, 1);
400
+ expect(bv.donnee_intermediaire.fe2).toBeCloseTo(0.6, 9);
401
+ });
274
402
 
275
- calc_bv(bv, zc);
403
+ test('masque unique (objet, non tableau) : normalisé en tableau', () => {
404
+ const bv = baseBv({
405
+ masque_lointain_non_homogene_collection: {
406
+ masque_lointain_non_homogene: { tv_coef_masque_lointain_non_homogene_id: 1 }
407
+ }
408
+ });
409
+ calc_bv(bv, 1);
410
+ expect(bv.donnee_intermediaire.fe2).toBeCloseTo(0.8, 9); // 1 - 0,2
411
+ });
276
412
 
277
- expect(bv.donnee_intermediaire.b).toBe(1);
278
- expect(bv.donnee_intermediaire.ug).toBe(2.8);
279
- expect(bv.donnee_intermediaire.uw).toBe(3);
280
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(3);
281
- expect(bv.donnee_intermediaire.sw).toBeCloseTo(0.47000000000000003, PRECISION);
282
- expect(bv.donnee_intermediaire.fe1).toBe(1);
413
+ test('collection sans masque : fe2 reste à 1', () => {
414
+ const bv = baseBv({ masque_lointain_non_homogene_collection: {} });
415
+ calc_bv(bv, 1);
283
416
  expect(bv.donnee_intermediaire.fe2).toBe(1);
284
417
  });
285
418
 
286
- test('calcul de déperdition pour les murs de 2187E0982013C baie 6', () => {
287
- const zc = 3;
288
- const bv = {
289
- donnee_entree: {
290
- description: 'Fenêtre 6 Ouest - Fenêtres battantes bois, orientées Ouest, simple vitrage',
291
- enum_type_adjacence_id: '1',
292
- surface_totale_baie: 0.16,
293
- nb_baie: 1,
294
- tv_ug_id: 1,
295
- enum_type_vitrage_id: '1',
296
- enum_inclinaison_vitrage_id: '3',
297
- enum_methode_saisie_perf_vitrage_id: '1',
298
- tv_uw_id: 562,
299
- enum_type_materiaux_menuiserie_id: '3',
300
- enum_type_baie_id: '4',
301
- double_fenetre: 0,
302
- uw_1: 5.4,
303
- sw_1: 0.52,
304
- enum_type_fermeture_id: '1',
305
- presence_retour_isolation: 0,
306
- largeur_dormant: 5,
307
- tv_sw_id: 89,
308
- enum_type_pose_id: '3',
309
- enum_orientation_id: '4',
310
- tv_coef_masque_proche_id: 19,
311
- masque_lointain_non_homogene_collection: ''
312
- },
313
- donnee_intermediaire: {
314
- b: 1,
315
- ug: 5.8,
316
- uw: 5.4,
317
- u_menuiserie: 5.4,
318
- sw: 0.52,
319
- fe1: 1,
320
- fe2: 1
419
+ test('occultation supérieure à 100% : fe2 borné à 0', () => {
420
+ rows.coef_masque_lointain_non_homoge = { omb: '150' };
421
+ const bv = baseBv({
422
+ masque_lointain_non_homogene_collection: {
423
+ masque_lointain_non_homogene: [{ tv_coef_masque_lointain_non_homogene_id: 1 }]
321
424
  }
322
- };
425
+ });
426
+ calc_bv(bv, 1);
427
+ expect(bv.donnee_intermediaire.fe2).toBe(0);
428
+ });
323
429
 
324
- calc_bv(bv, zc);
430
+ test('aucune ligne omb trouvée : information consignée (console.log)', () => {
431
+ rows.coef_masque_lointain_non_homoge = null;
432
+ const bv = baseBv({
433
+ masque_lointain_non_homogene_collection: {
434
+ masque_lointain_non_homogene: [{ tv_coef_masque_lointain_non_homogene_id: 1 }]
435
+ }
436
+ });
437
+ calc_bv(bv, 1);
438
+ expect(logSpy).toHaveBeenCalledWith(
439
+ '!! pas de valeur forfaitaire trouvée pour coef_masque_lointain_non_homog !!'
440
+ );
441
+ });
442
+ });
325
443
 
326
- expect(bv.donnee_intermediaire.b).toBe(1);
327
- expect(bv.donnee_intermediaire.ug).toBe(5.8);
328
- expect(bv.donnee_intermediaire.uw).toBe(5.4);
329
- expect(bv.donnee_intermediaire.u_menuiserie).toBe(5.4);
330
- expect(bv.donnee_intermediaire.sw).toBe(0.52);
444
+ describe('calc_bv - masque proche (fe1)', () => {
445
+ test('sans identifiant de masque proche : fe1 = 1', () => {
446
+ const bv = baseBv();
447
+ calc_bv(bv, 1);
331
448
  expect(bv.donnee_intermediaire.fe1).toBe(1);
449
+ });
450
+
451
+ test('avec identifiant : fe1 lu dans la table', () => {
452
+ const bv = baseBv({ tv_coef_masque_proche_id: 19 });
453
+ calc_bv(bv, 1);
454
+ expect(bv.donnee_intermediaire.fe1).toBe(0.8);
455
+ });
456
+
457
+ test('identifiant présent mais aucune ligne : erreur émise', () => {
458
+ rows.coef_masque_proche = null;
459
+ const bv = baseBv({ tv_coef_masque_proche_id: 19 });
460
+ calc_bv(bv, 1);
461
+ expect(errorSpy).toHaveBeenCalledWith(
462
+ '!! pas de valeur forfaitaire trouvée pour coef_masque_proche !!'
463
+ );
464
+ });
465
+ });
466
+
467
+ describe('calc_bv - masque lointain homogène (fe2)', () => {
468
+ test('sans identifiant : fe2 inchangé', () => {
469
+ const bv = baseBv();
470
+ calc_bv(bv, 1);
332
471
  expect(bv.donnee_intermediaire.fe2).toBe(1);
333
472
  });
473
+
474
+ test('avec identifiant : fe2 lu dans la table (écrase la valeur par défaut)', () => {
475
+ const bv = baseBv({ tv_coef_masque_lointain_homogene_id: 3 });
476
+ calc_bv(bv, 1);
477
+ expect(bv.donnee_intermediaire.fe2).toBe(0.9);
478
+ });
479
+
480
+ test('identifiant présent mais aucune ligne : information consignée (console.log)', () => {
481
+ rows.coef_masque_lointain_homogene = null;
482
+ const bv = baseBv({ tv_coef_masque_lointain_homogene_id: 3 });
483
+ calc_bv(bv, 1);
484
+ expect(logSpy).toHaveBeenCalledWith(
485
+ '!! pas de valeur forfaitaire trouvée pour coef_masque_lointain_homogene !!'
486
+ );
487
+ });
334
488
  });