diff --git a/src/4_ventilation.js b/src/4_ventilation.js index 74aa28c8a..67bc0003d 100644 --- a/src/4_ventilation.js +++ b/src/4_ventilation.js @@ -17,37 +17,6 @@ function tv_debits_ventilation(di, de, du) { } } -/** - * Vrai si le DPE comporte au moins une menuiserie (baie ou porte) et qu'aucune n'exporte - * le champ `presence_joint` (issue #294). - */ -export function presenceJointNonRenseignee(porte_list, bv_list) { - const menuiseries = (porte_list || []).concat(bv_list || []); - return ( - menuiseries.length > 0 && - menuiseries.every((m) => [undefined, null, ''].includes(m.donnee_entree?.presence_joint)) - ); -} - -/** - * Vrai si la ligne de la table `q4pa_conv` couvre la période de construction du DPE. - */ -export function isLigneQ4paMemePeriode(rowQ4paConv, cg) { - return String(rowQ4paConv?.enum_periode_construction_id ?? '') - .split('|') - .includes(String(cg.enum_periode_construction_id)); -} - -/** - * Vrai si la ligne de la table `q4pa_conv` considère les surfaces isolées (`isolation_surfaces` = 1) - * alors que le recalcul à partir des parois les donne non isolées (`isolation_surfaces` = 0). - */ -export function isLigneQ4paSurfacesIsolees(rowQ4paConv, isolationSurfacesRecalculee) { - return ( - String(rowQ4paConv.isolation_surfaces) === '1' && String(isolationSurfacesRecalculee) === '0' - ); -} - function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) { const surfaces = mur_list.concat(ph_list); const surface_isolee = surfaces.reduce((acc, s) => { @@ -79,7 +48,7 @@ function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) { return acc + s.donnee_entree.surface_paroi_opaque; } else return acc; }, 0); - let isolation_surfaces = surface_isolee > surface_non_isolee ? '1' : '0'; + const isolation_surfaces = surface_isolee > surface_non_isolee ? '1' : '0'; // presence joints menuiserie let surface_bv_avec_joint = bv_list.reduce((acc, bv) => { @@ -110,49 +79,20 @@ function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) { }); if (rowQ4paConv) { - let ligneDpeReprise = false; /** - * Le moteur de référence reçoit Q4Pa_conv en entrée : c'est le logiciel de saisie qui choisit - * la ligne de la table. Certains logiciels (LICIEL notamment) retiennent une ligne dont le - * critère `isolation_surfaces` contredit l'isolation déclarée des parois (ex. ligne 10 « avant - * 1948, surfaces isolées », 2, alors que toutes les parois sont non isolées → ligne 1, 4,6). - * Si la ligne saisie correspond à la période de construction du DPE, on la reprend en entier - * (isolation et joints) pour reproduire le calcul du logiciel. - * - * Sens unique : la reprise ne s'applique que si la ligne indique des surfaces isolées alors que - * le recalcul les donne non isolées. Dans le sens inverse (ligne « non isolées », recalcul - * « isolées »), le DPE a pu être modifié par l'appelant (scénario d'isolation des parois) : - * la valeur recalculée est utilisée. + * La ligne q4pa_conv retenue par le logiciel de saisie n'est jamais reprise : q4pa_conv est + * toujours recalculé à partir des données d'entrée (isolation des parois, joints des + * menuiseries). Une ligne contradictoire est seulement signalée. * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4 */ if (rowQ4paConv.isolation_surfaces !== isolation_surfaces) { - if ( - isLigneQ4paSurfacesIsolees(rowQ4paConv, isolation_surfaces) && - isLigneQ4paMemePeriode(rowQ4paConv, cg) - ) { - console.error( - `Calcul de hperm pour la ventilation ${de.description}. Le DPE considère isolation_surfaces = ${rowQ4paConv.isolation_surfaces} \n alors que ce devrait être isolation_surfaces = ${isolation_surfaces}. La ligne q4pa_conv ${de.tv_q4pa_conv_id} du DPE est conservée.` - ); - isolation_surfaces = rowQ4paConv.isolation_surfaces; - pjt = rowQ4paConv.presence_joints_menuiserie === '1' ? '1' : '0'; - ligneDpeReprise = true; - } else { - console.error( - `Calcul de hperm pour la ventilation ${de.description}. Le DPE considère isolation_surfaces = ${rowQ4paConv.isolation_surfaces} \n alors que ce devrait être isolation_surfaces = ${isolation_surfaces}. La valeur recalculée isolation_surfaces = ${isolation_surfaces} est utilisée.` - ); - } + console.error( + `Calcul de hperm pour la ventilation ${de.description}. Le DPE considère isolation_surfaces = ${rowQ4paConv.isolation_surfaces} + alors que ce devrait être isolation_surfaces = ${isolation_surfaces}. La valeur recalculée isolation_surfaces = ${isolation_surfaces} est utilisée.` + ); } - /** - * Issue #294 : aucune menuiserie n'exporte `presence_joint`. Aucune donnée ne contredit alors - * la ligne q4pa_conv retenue par le logiciel (ex. ligne 12 « avant 1948 avec joints », 2,5) : - * on reprend sa valeur `presence_joints_menuiserie` au lieu de considérer les menuiseries - * sans joint. - * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4 - */ - if (!ligneDpeReprise && presenceJointNonRenseignee(porte_list, bv_list)) { - pjt = rowQ4paConv.presence_joints_menuiserie === '1' ? '1' : '0'; - } else if (!ligneDpeReprise && rowQ4paConv.presence_joints_menuiserie !== pjt) { + if (rowQ4paConv.presence_joints_menuiserie !== pjt) { console.error( `Calcul de hperm pour la ventilation ${de.description}. Le DPE considère presence_joints_menuiserie = ${rowQ4paConv.presence_joints_menuiserie} alors que ce devrait être presence_joints_menuiserie = ${pjt}. La valeur recalculée presence_joints_menuiserie = ${pjt} est utilisée.` diff --git a/src/4_ventilation.spec.js b/src/4_ventilation.spec.js index 3246e1fcb..ba467bea8 100644 --- a/src/4_ventilation.spec.js +++ b/src/4_ventilation.spec.js @@ -19,13 +19,7 @@ vi.mock('./5_conso_ventilation.js', () => ({ default: vi.fn() })); -const { - default: calc_ventilation, - calc_hperm, - presenceJointNonRenseignee, - isLigneQ4paMemePeriode, - isLigneQ4paSurfacesIsolees -} = await import('./4_ventilation.js'); +const { default: calc_ventilation, calc_hperm } = await import('./4_ventilation.js'); const { tv } = await import('./utils.js'); const { default: calc_pvent } = await import('./5_conso_ventilation.js'); @@ -412,82 +406,12 @@ describe('calc_ventilation - compatibilité bug_for_bug_compat activée', () => }); /** - * Issue #294 : presence_joint absent sur toutes les menuiseries => la valeur - * presence_joints_menuiserie de la ligne q4pa_conv du DPE est reprise. + * La ligne q4pa_conv saisie dans le DPE n'est jamais reprise (retrait des reprises #312 et + * #294) : une ligne contradictoire avec les parois ou les menuiseries est seulement signalée et + * q4pa_conv est recalculé à partir des données d'entrée. + * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4 */ - describe('presence_joint non exporté (issue #294)', () => { - /** Avant 1948, surfaces non isolées : seul le critère des joints départage les lignes 1 et 12. */ - const murNonIsole = [ - { - donnee_intermediaire: { b: 1 }, - donnee_entree: { enum_type_isolation_id: '2', surface_paroi_opaque: 100 } - } - ]; - const vtLigne12 = () => ({ - donnee_entree: { enum_type_ventilation_id: '1', tv_q4pa_conv_id: 12, description: 'v' } - }); - const ROW_12 = { - q4pa_conv: '2.5', - tv_q4pa_conv_id: '12', - isolation_surfaces: '0', - presence_joints_menuiserie: '1' - }; - - test('aucune menuiserie ne renseigne le champ : joints du DPE repris, sans message', async () => { - const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12); - const baies = [{ donnee_entree: { surface_totale_baie: 10 } }]; - const portes = [{ donnee_entree: { surface_porte: 2, presence_joint: null } }]; - - calc_ventilation_bug(vtLigne12(), CG, 'th', 80, 100, murNonIsole, [], portes, baies); - - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '0', presence_joints_menuiserie: '1' }) - ); - expect(errorSpy).not.toHaveBeenCalled(); - errorSpy.mockRestore(); - }); - - test('ligne du DPE sans joints : la valeur "0" est conservée', async () => { - const { calc_ventilation_bug, tvBug } = await chargerAvecBug({ - ...ROW_12, - tv_q4pa_conv_id: '1', - presence_joints_menuiserie: undefined - }); - const baies = [{ donnee_entree: { surface_totale_baie: 10 } }]; - - calc_ventilation_bug(vtLigne12(), CG, 'th', 80, 100, murNonIsole, [], [], baies); - - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ presence_joints_menuiserie: '0' }) - ); - }); - - test('presence_joint renseigné à 0 : la valeur recalculée prime et l’écart est signalé', async () => { - const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12); - const baies = [{ donnee_entree: { surface_totale_baie: 10, presence_joint: 0 } }]; - - calc_ventilation_bug(vtLigne12(), CG, 'th', 80, 100, murNonIsole, [], [], baies); - - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ presence_joints_menuiserie: '0' }) - ); - expect(errorSpy).toHaveBeenCalledWith( - expect.stringContaining('La valeur recalculée presence_joints_menuiserie = 0 est utilisée') - ); - errorSpy.mockRestore(); - }); - }); - - /** - * Ligne q4pa_conv du DPE dont le critère d'isolation contredit les parois (2287E1201982M : - * ligne 10 « avant 1948, surfaces isolées » alors que toutes les parois sont non isolées). - */ - describe('isolation_surfaces incohérent avec la ligne du DPE', () => { + describe('ligne q4pa_conv du DPE contradictoire : valeur recalculée', () => { const murNonIsole = [ { donnee_intermediaire: { b: 1 }, @@ -498,197 +422,98 @@ describe('calc_ventilation - compatibilité bug_for_bug_compat activée', () => const vtLigne = (id) => ({ donnee_entree: { enum_type_ventilation_id: '1', tv_q4pa_conv_id: id, description: 'v' } }); - const ROW_10 = { - q4pa_conv: '2', - tv_q4pa_conv_id: '10', - enum_periode_construction_id: '1', - isolation_surfaces: '1' - }; - test('ligne de la même période : isolation et joints de la ligne du DPE repris', async () => { + test('ligne « surfaces isolées » (2287E1201982M) alors que les parois sont non isolées : isolation recalculée', async () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_10); + const { calc_ventilation_bug, tvBug } = await chargerAvecBug({ + q4pa_conv: '2', + tv_q4pa_conv_id: '10', + enum_periode_construction_id: '1', + isolation_surfaces: '1', + presence_joints_menuiserie: '0' + }); + const vt = vtLigne(10); - calc_ventilation_bug(vtLigne(10), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint); + calc_ventilation_bug(vt, CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint); expect(tvBug).toHaveBeenLastCalledWith( 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' }) + expect.objectContaining({ isolation_surfaces: '0', presence_joints_menuiserie: '0' }) ); expect(errorSpy).toHaveBeenCalledWith( - expect.stringContaining('La ligne q4pa_conv 10 du DPE est conservée') + expect.stringContaining('La valeur recalculée isolation_surfaces = 0 est utilisée') ); - // les joints de la ligne reprise concordent : pas de second message expect(errorSpy).toHaveBeenCalledTimes(1); + // la ligne recalculée remplace celle du DPE + expect(vt.donnee_entree.tv_q4pa_conv_id).toBe(9); + expect(vt.donnee_intermediaire.q4pa_conv).toBe(1.3); errorSpy.mockRestore(); }); - test('ligne isolée de la même période, joints absents de la ligne : joints « 0 » repris', async () => { - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_10); - vi.spyOn(console, 'error').mockImplementation(() => {}); - const baieAvecJoint = [{ donnee_entree: { surface_totale_baie: 10, presence_joint: 1 } }]; - - calc_ventilation_bug(vtLigne(10), CG, 'th', 80, 100, murNonIsole, [], [], baieAvecJoint); - - // la ligne reprise prime sur les joints recalculés ('1') - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' }) - ); - }); - - test('ligne isolée de la même période avec joints : joints « 1 » repris malgré des baies sans joint', async () => { + test('ligne « avec joints » alors que les menuiseries sont sans joint : joints recalculés', async () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const { calc_ventilation_bug, tvBug } = await chargerAvecBug({ - ...ROW_10, - tv_q4pa_conv_id: '11', + q4pa_conv: '2.5', + tv_q4pa_conv_id: '12', + isolation_surfaces: '0', presence_joints_menuiserie: '1' }); - vi.spyOn(console, 'error').mockImplementation(() => {}); - calc_ventilation_bug(vtLigne(11), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint); + calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint); expect(tvBug).toHaveBeenLastCalledWith( 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '1' }) + expect.objectContaining({ isolation_surfaces: '0', presence_joints_menuiserie: '0' }) + ); + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('La valeur recalculée presence_joints_menuiserie = 0 est utilisée') ); + errorSpy.mockRestore(); }); - /** - * Sens unique : ligne « non isolées » alors que les parois sont isolées (cas d'un scénario de - * rénovation où l'appelant isole les murs du DPE). La ligne n'est pas reprise : l'isolation et - * les joints sont recalculés comme avant #312. - */ - describe('ligne non isolée alors que les parois sont isolées (sens inverse)', () => { - const ROW_12 = { + test('presence_joint exporté sur aucune menuiserie (issue #294) : menuiseries sans joint, écart signalé', async () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const { calc_ventilation_bug, tvBug } = await chargerAvecBug({ q4pa_conv: '2.5', tv_q4pa_conv_id: '12', - enum_periode_construction_id: '1', isolation_surfaces: '0', presence_joints_menuiserie: '1' - }; - - test('isolation recalculée utilisée et écart des joints signalé', async () => { - const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12); - const { murs } = surfacesIsolantes(); - - calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baieSansJoint); - - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' }) - ); - expect(errorSpy).toHaveBeenCalledWith( - expect.stringContaining('La valeur recalculée isolation_surfaces = 1 est utilisée') - ); - expect(errorSpy).toHaveBeenCalledWith( - expect.stringContaining( - 'La valeur recalculée presence_joints_menuiserie = 0 est utilisée' - ) - ); - expect(errorSpy).not.toHaveBeenCalledWith(expect.stringContaining('est conservée')); - errorSpy.mockRestore(); }); + const baies = [{ donnee_entree: { surface_totale_baie: 10 } }]; + const portes = [{ donnee_entree: { surface_porte: 2, presence_joint: null } }]; - test('presence_joint non exporté : seuls les joints de la ligne du DPE sont repris', async () => { - vi.spyOn(console, 'error').mockImplementation(() => {}); - const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_12); - const { murs } = surfacesIsolantes(); - const baies = [{ donnee_entree: { surface_totale_baie: 10 } }]; - - calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baies); + calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murNonIsole, [], portes, baies); - expect(tvBug).toHaveBeenLastCalledWith( - 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '1' }) - ); - }); + expect(tvBug).toHaveBeenLastCalledWith( + 'q4pa_conv', + expect.objectContaining({ presence_joints_menuiserie: '0' }) + ); + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('La valeur recalculée presence_joints_menuiserie = 0 est utilisée') + ); + errorSpy.mockRestore(); }); - test('ligne d’une autre période : la valeur recalculée est conservée', async () => { + test('ligne « non isolées » alors que les parois sont isolées (scénario de rénovation) : isolation recalculée', async () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const { calc_ventilation_bug, tvBug } = await chargerAvecBug({ - ...ROW_10, - tv_q4pa_conv_id: '11', - enum_periode_construction_id: '2' + q4pa_conv: '4.6', + tv_q4pa_conv_id: '1', + enum_periode_construction_id: '1', + isolation_surfaces: '0' }); + const { murs } = surfacesIsolantes(); - calc_ventilation_bug(vtLigne(11), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint); + calc_ventilation_bug(vtLigne(1), CG, 'th', 80, 100, murs, [], [], baieSansJoint); expect(tvBug).toHaveBeenLastCalledWith( 'q4pa_conv', - expect.objectContaining({ isolation_surfaces: '0' }) + expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' }) ); expect(errorSpy).toHaveBeenCalledWith( - expect.stringContaining('La valeur recalculée isolation_surfaces = 0 est utilisée') + expect.stringContaining('La valeur recalculée isolation_surfaces = 1 est utilisée') ); errorSpy.mockRestore(); }); }); }); - -describe('presenceJointNonRenseignee', () => { - test('aucune menuiserie : faux', () => { - expect(presenceJointNonRenseignee([], [])).toBe(false); - expect(presenceJointNonRenseignee(undefined, undefined)).toBe(false); - }); - - test('champ absent, null ou vide sur toutes les menuiseries : vrai', () => { - expect( - presenceJointNonRenseignee( - [{ donnee_entree: { presence_joint: '' } }], - [{ donnee_entree: {} }, { donnee_entree: { presence_joint: null } }] - ) - ).toBe(true); - }); - - test('au moins une menuiserie renseignée (même à 0) : faux', () => { - expect( - presenceJointNonRenseignee( - [{ donnee_entree: { presence_joint: 0 } }], - [{ donnee_entree: {} }] - ) - ).toBe(false); - }); -}); - -describe('isLigneQ4paMemePeriode', () => { - test('période contenue dans une liste « a|b|c » : vrai', () => { - expect( - isLigneQ4paMemePeriode( - { enum_periode_construction_id: '3|4|5' }, - { enum_periode_construction_id: 4 } - ) - ).toBe(true); - }); - - test('période différente : faux', () => { - expect( - isLigneQ4paMemePeriode( - { enum_periode_construction_id: '2' }, - { enum_periode_construction_id: '1' } - ) - ).toBe(false); - }); - - test('ligne sans période (ou absente) : faux', () => { - expect(isLigneQ4paMemePeriode({}, { enum_periode_construction_id: '1' })).toBe(false); - expect(isLigneQ4paMemePeriode(null, { enum_periode_construction_id: '1' })).toBe(false); - }); -}); - -describe('isLigneQ4paSurfacesIsolees', () => { - test('ligne « surfaces isolées » et recalcul « non isolées » : vrai', () => { - expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '1' }, '0')).toBe(true); - expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: 1 }, 0)).toBe(true); - }); - - test('ligne « non isolées » et recalcul « isolées » : faux (sens inverse)', () => { - expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '0' }, '1')).toBe(false); - }); - - test('valeurs concordantes ou ligne sans critère : faux', () => { - expect(isLigneQ4paSurfacesIsolees({ isolation_surfaces: '1' }, '1')).toBe(false); - expect(isLigneQ4paSurfacesIsolees({}, '0')).toBe(false); - }); -}); diff --git a/src/features/dpe/infrastructure/tv.store.js b/src/features/dpe/infrastructure/tv.store.js index 4bd3d503e..be133b309 100755 --- a/src/features/dpe/infrastructure/tv.store.js +++ b/src/features/dpe/infrastructure/tv.store.js @@ -340,16 +340,6 @@ export class TvStore { return debitsVentilation; } - /** - * Ligne de la table q4pa_conv par son identifiant - * @param tvQ4paConvId {number|string|undefined} - * @return {object|undefined} - */ - getQ4paConvById(tvQ4paConvId) { - if (tvQ4paConvId === undefined || tvQ4paConvId === null) return; - return tv['q4pa_conv'].find((v) => v.tv_q4pa_conv_id === String(tvQ4paConvId)); - } - /** * Valeur conventionnelle de la perméabilité sous 4Pa * @see Chapitre 4 - Calcul des déperditions par renouvellement d’air diff --git a/src/features/dpe/infrastructure/tv.store.spec.js b/src/features/dpe/infrastructure/tv.store.spec.js index 414877a02..eef4c5cad 100755 --- a/src/features/dpe/infrastructure/tv.store.spec.js +++ b/src/features/dpe/infrastructure/tv.store.spec.js @@ -607,17 +607,6 @@ describe('Lecture des tables de valeurs', () => { const ug = tvStore.getQ4paConv('2', TypeHabitation.MAISON, '0', '1'); expect(ug).toBeUndefined(); }); - - test('lecture par identifiant (issue #294)', () => { - expect(tvStore.getQ4paConvById(12)).toMatchObject({ - q4pa_conv: '2.5', - presence_joints_menuiserie: '1' - }); - expect(tvStore.getQ4paConvById('1')).toMatchObject({ q4pa_conv: '4.6' }); - expect(tvStore.getQ4paConvById(undefined)).toBeUndefined(); - expect(tvStore.getQ4paConvById(null)).toBeUndefined(); - expect(tvStore.getQ4paConvById(999)).toBeUndefined(); - }); }); test('lecture des valeurs de tempBase', () => { diff --git a/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.js b/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.js index 6ece05714..24066f396 100755 --- a/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.js +++ b/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.js @@ -305,8 +305,7 @@ export class DeperditionEnveloppeService { this.#surfaceIsolee, this.#surfaceNonIsolee, this.#surfaceMenuiserieAvecJoint, - this.#surfaceMenuiserieSansJoint, - this.#presenceJointNonRenseignee(logement.enveloppe) + this.#surfaceMenuiserieSansJoint ); deperditions.hvent += ventilation.donnee_intermediaire.hvent; deperditions.hperm += ventilation.donnee_intermediaire.hperm; @@ -314,22 +313,4 @@ export class DeperditionEnveloppeService { return deperditions; } - - /** - * Vrai si le logement comporte au moins une menuiserie (baie ou porte) et qu'aucune n'exporte - * le champ presence_joint (issue #294). - * - * @param enveloppe {Enveloppe} - * @return {boolean} - */ - #presenceJointNonRenseignee(enveloppe) { - const menuiseries = [ - ...(enveloppe?.porte_collection?.porte || []), - ...(enveloppe?.baie_vitree_collection?.baie_vitree || []) - ]; - return ( - menuiseries.length > 0 && - menuiseries.every((m) => [undefined, null, ''].includes(m.donnee_entree?.presence_joint)) - ); - } } diff --git a/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js b/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js index 05f6e3790..e560e04ae 100644 --- a/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js +++ b/src/features/engine/domain/enveloppe/deperdition-enveloppe.service.spec.js @@ -173,17 +173,7 @@ describe('DeperditionEnveloppeService (§3 déperditions de l’enveloppe)', () // déperditive = mur 10 + porte 2 + plancher haut 30 + baie 4 = 46 // isolée = mur 5 + plancher haut 7 = 12 ; non isolée = mur 10 + plancher haut 30 = 40 // menuiseries avec joint = porte 2 + baie 4 = 6 ; sans joint = porte 3 + baie 6 = 9 - // presence_joint renseigné sur au moins une menuiserie => indicateur #294 à false - expect(ventilationService.execute).toHaveBeenCalledWith( - ctx, - { id: 'v' }, - 46, - 12, - 40, - 6, - 9, - false - ); + expect(ventilationService.execute).toHaveBeenCalledWith(ctx, { id: 'v' }, 46, 12, 40, 6, 9); }); test('les surfaces sont réinitialisées à chaque calcul', () => { @@ -196,18 +186,7 @@ describe('DeperditionEnveloppeService (§3 déperditions de l’enveloppe)', () service.deperditions(ctx, logement); service.deperditions(ctx, logement); - // aucune menuiserie ne renseigne presence_joint => indicateur #294 à true - expect(ventilationService.execute).toHaveBeenLastCalledWith(ctx, {}, 2, 0, 0, 0, 2, true); - }); - - test('logement sans menuiserie : indicateur presence_joint non renseigné à false (issue #294)', () => { - const logement = logementVide(); - logement.ventilation_collection.ventilation = [{ donnee_entree: {} }]; - ventilationService.execute.mockReturnValue({ hvent: 0, hperm: 0 }); - - service.deperditions(ctx, logement); - - expect(ventilationService.execute).toHaveBeenLastCalledWith(ctx, {}, 0, 0, 0, 0, 0, false); + expect(ventilationService.execute).toHaveBeenLastCalledWith(ctx, {}, 2, 0, 0, 0, 2); }); describe('espaces tampons solarisés', () => { diff --git a/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js b/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js index 668326769..f46adf144 100644 --- a/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js +++ b/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.js @@ -1,7 +1,6 @@ import { DeperditionService } from '../deperdition.service.js'; import { logger } from '../../../../../core/util/logger/log-service.js'; -import { bug_for_bug_compat } from '../../../../../utils.js'; import { TypeHabitation } from '../../../../dpe/domain/models/type-habitation.model.js'; import { TypeVentilation } from '../../../../dpe/domain/models/type-ventilation.model.js'; @@ -23,7 +22,6 @@ export class DeperditionVentilationService extends DeperditionService { * @param surfaceNonIsolee {number} * @param surfaceMenuiserieAvecJoint {number} * @param surfaceMenuiserieSansJoint {number} - * @param presenceJointNonRenseignee {boolean} aucune menuiserie n'exporte presence_joint (issue #294) * @return {VentilationDI} */ execute( @@ -33,8 +31,7 @@ export class DeperditionVentilationService extends DeperditionService { surfaceIsolee, surfaceNonIsolee, surfaceMenuiserieAvecJoint, - surfaceMenuiserieSansJoint, - presenceJointNonRenseignee = false + surfaceMenuiserieSansJoint ) { const q4paConv = this.q4paConv( ventilationDE, @@ -42,8 +39,7 @@ export class DeperditionVentilationService extends DeperditionService { surfaceIsolee, surfaceNonIsolee, surfaceMenuiserieAvecJoint, - surfaceMenuiserieSansJoint, - presenceJointNonRenseignee + surfaceMenuiserieSansJoint ); const debitsVentilation = this.#debitsVentilation(ventilationDE.enum_type_ventilation_id); @@ -66,7 +62,6 @@ export class DeperditionVentilationService extends DeperditionService { * @param surfaceNonIsolee {number} * @param surfaceMenuiserieAvecJoint {number} * @param surfaceMenuiserieSansJoint {number} - * @param presenceJointNonRenseignee {boolean} * @return {number} */ q4paConv( @@ -75,8 +70,7 @@ export class DeperditionVentilationService extends DeperditionService { surfaceIsolee, surfaceNonIsolee, surfaceMenuiserieAvecJoint, - surfaceMenuiserieSansJoint, - presenceJointNonRenseignee = false + surfaceMenuiserieSansJoint ) { // Pour les bâtiments qui ont fait l’objet d’une mesure d’étanchéité à l’air moins de deux ans avant le diagnostic, la valeur // mesurée de Q4Paconv/m² peut être saisie. @@ -98,15 +92,6 @@ export class DeperditionVentilationService extends DeperditionService { // (si les menuiseries représentant plus de 50% de la surface totale possèdent des joints) => valeur forfaitaire. if (surfaceMenuiserieAvecJoint > surfaceMenuiserieSansJoint) { presenceJointsMenuiserie = '1'; - } else if ( - bug_for_bug_compat && - presenceJointNonRenseignee && - this.tvStore.getQ4paConvById(ventilationDE.tv_q4pa_conv_id) - ?.presence_joints_menuiserie === '1' - ) { - // Issue #294 : aucune menuiserie n'exporte presence_joint, rien ne contredit la ligne - // q4pa_conv « avant 1948 avec joints » retenue par le DPE => on la reprend. - presenceJointsMenuiserie = '1'; } } } else if (enumPeriodeConstructionId === '2') { diff --git a/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js b/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js index f227d59f7..cc8c596a9 100644 --- a/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js +++ b/src/features/engine/domain/enveloppe/ventilation/deperdition-ventilation.service.spec.js @@ -1,14 +1,5 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; -/** `bug_for_bug_compat` (utils.js) piloté par le test. */ -const state = vi.hoisted(() => ({ bug: false })); -vi.mock('../../../../../utils.js', async (importOriginal) => ({ - ...(await importOriginal()), - get bug_for_bug_compat() { - return state.bug; - } -})); - const { DeperditionVentilationService } = await import('./deperdition-ventilation.service.js'); /** @@ -27,74 +18,35 @@ const ctx = { enumPeriodeConstructionId: '1', typeHabitation: 'APPARTEMENT' }; describe('DeperditionVentilationService.q4paConv', () => { beforeEach(() => { tvStore = { - getQ4paConv: vi.fn().mockReturnValue({ q4pa_conv: '4.6' }), - getQ4paConvById: vi.fn() + getQ4paConv: vi.fn().mockReturnValue({ q4pa_conv: '4.6' }) }; service = new DeperditionVentilationService(tvStore); - state.bug = false; }); test('Q4Pa mesuré saisi : utilisé directement', () => { - expect(service.q4paConv({ q4pa_conv_saisi: 1.7 }, ctx, 0, 100, 0, 10, true)).toBe(1.7); + expect(service.q4paConv({ q4pa_conv_saisi: 1.7 }, ctx, 0, 100, 0, 10)).toBe(1.7); expect(tvStore.getQ4paConv).not.toHaveBeenCalled(); }); test('avant 1948, menuiseries majoritairement avec joints : ligne « avec joints »', () => { - service.q4paConv({}, ctx, 0, 100, 8, 2, false); + service.q4paConv({}, ctx, 0, 100, 8, 2); expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', '1'); }); test('avant 1948, menuiseries sans joint : ligne sans critère de joints', () => { - service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 0, 100, 0, 10, false); + service.q4paConv({}, ctx, 0, 100, 0, 10); expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', undefined); }); - describe('presence_joint non exporté (issue #294)', () => { - test('hors bug_for_bug_compat : la ligne du DPE est ignorée', () => { - state.bug = false; - tvStore.getQ4paConvById.mockReturnValue({ presence_joints_menuiserie: '1' }); - - service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 0, 100, 0, 10, true); - - expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', undefined); - }); - - test('bug_for_bug_compat et ligne DPE « avec joints » : joints repris', () => { - state.bug = true; - tvStore.getQ4paConvById.mockReturnValue({ presence_joints_menuiserie: '1' }); - - service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 0, 100, 0, 10, true); - - expect(tvStore.getQ4paConvById).toHaveBeenCalledWith(12); - expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', '1'); - }); - - test('bug_for_bug_compat mais presence_joint renseigné : pas de reprise', () => { - state.bug = true; - tvStore.getQ4paConvById.mockReturnValue({ presence_joints_menuiserie: '1' }); - - service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 0, 100, 0, 10, false); - - expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', undefined); - }); - - test('bug_for_bug_compat et ligne DPE sans joints : pas de reprise', () => { - state.bug = true; - tvStore.getQ4paConvById.mockReturnValue({ presence_joints_menuiserie: '' }); - - service.q4paConv({ tv_q4pa_conv_id: 1 }, ctx, 0, 100, 0, 10, true); - - expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', undefined); - }); - - test('surfaces majoritairement isolées : le critère des joints ne s’applique pas', () => { - state.bug = true; - tvStore.getQ4paConvById.mockReturnValue({ presence_joints_menuiserie: '1' }); - - service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 100, 0, 0, 10, true); + test('ligne q4pa_conv du DPE « avec joints » contradictoire : valeur recalculée', () => { + // La ligne 12 « avant 1948 avec joints » choisie par le logiciel n'est pas reprise + const q4pa = service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 0, 100, 0, 10); + expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '0', undefined); + expect(q4pa).toBe('4.6'); + }); - expect(tvStore.getQ4paConvById).not.toHaveBeenCalled(); - expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '1', undefined); - }); + test('surfaces majoritairement isolées : le critère des joints ne s’applique pas', () => { + service.q4paConv({ tv_q4pa_conv_id: 12 }, ctx, 100, 0, 0, 10); + expect(tvStore.getQ4paConv).toHaveBeenCalledWith('1', 'APPARTEMENT', '1', undefined); }); }); diff --git a/test/open3cl-q4pa-ligne-dpe.spec.js b/test/open3cl-q4pa-ligne-dpe.spec.js index bc4a66d4f..efe2ada01 100644 --- a/test/open3cl-q4pa-ligne-dpe.spec.js +++ b/test/open3cl-q4pa-ligne-dpe.spec.js @@ -7,31 +7,30 @@ import { set_bug_for_bug_compat, set_tv_match_optimized_version } from '../src/u * Non-régression https://github.com/Open3CL/engine/issues/312 * * Le DPE 2287E1201982M (LICIEL) retient la ligne q4pa_conv 10 « avant 1948, surfaces isolées » - * (q4pa_conv = 2) alors que toutes ses parois sont non isolées (ligne 1 : 4,6). En mode - * bug_for_bug_compat, la ligne du DPE est reprise : q4pa_conv, hperm et conso_ch sont ceux du DPE. + * (q4pa_conv = 2) alors que toutes ses parois sont non isolées (ligne 1 : 4,6). La ligne du DPE + * n'est pas reprise : q4pa_conv est recalculé selon la méthode à partir des parois, si bien que + * l'écart avec le DPE est détecté. */ -describe('q4pa_conv repris de la ligne du DPE - issue #312', () => { - test('2287E1201982M : q4pa_conv, hperm et conso_ch identiques au DPE', () => { +describe('q4pa_conv recalculé sans reprendre la ligne du DPE - issue #312', () => { + test('2287E1201982M : ligne q4pa du DPE contradictoire, valeur recalculée (4,6)', () => { set_bug_for_bug_compat(); set_tv_match_optimized_version(); /** @type {FullDpe} */ const input = getAdemeFileJson('2287E1201982M'); const output = calcul_3cl(structuredClone(input)); - const ventIn = input.logement.ventilation_collection.ventilation[0].donnee_intermediaire; - const ventOut = output.logement.ventilation_collection.ventilation[0].donnee_intermediaire; - expect(ventOut.q4pa_conv).toBe(2); - expect(ventOut.hperm).toBeCloseTo(ventIn.hperm, 1); - expect(output.logement.sortie.ef_conso.conso_ch).toBeCloseTo( - input.logement.sortie.ef_conso.conso_ch, - 0 - ); + const ventIn = input.logement.ventilation_collection.ventilation[0]; + const ventOut = output.logement.ventilation_collection.ventilation[0]; + expect(ventIn.donnee_intermediaire.q4pa_conv).toBe(2); + expect(ventOut.donnee_intermediaire.q4pa_conv).toBe(4.6); + expect(Number(ventOut.donnee_entree.tv_q4pa_conv_id)).toBe(1); + expect(ventOut.donnee_intermediaire.hperm).toBeGreaterThan(ventIn.donnee_intermediaire.hperm); }); /** - * Sens unique : le DPE 2469E2650374G retient la ligne q4pa_conv 1 « avant 1948, surfaces non - * isolées » (4,6). Un scénario de rénovation isole tous ses murs puis recalcule le DPE modifié : - * la ligne du DPE (non isolée) contredit alors les parois isolées et ne doit pas être reprise. + * Le DPE 2469E2650374G retient la ligne q4pa_conv 1 « avant 1948, surfaces non isolées » (4,6). + * Un scénario de rénovation isole tous ses murs puis recalcule le DPE modifié : q4pa_conv suit + * les parois isolées. */ test('2469E2650374G murs isolés : la ligne non isolée du DPE n’est pas reprise', () => { set_bug_for_bug_compat();