Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 33 additions & 6 deletions src/4_ventilation.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export function presenceJointNonRenseignee(porte_list, bv_list) {
);
}

/**
* 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));
}

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) => {
Expand Down Expand Up @@ -91,11 +100,29 @@ 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.
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4
*/
if (rowQ4paConv.isolation_surfaces !== isolation_surfaces) {
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.`
);
if (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.`
);
}
}

/**
Expand All @@ -105,9 +132,9 @@ function tv_q4pa_conv(di, de, cg, mur_list, ph_list, porte_list, bv_list) {
* sans joint.
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §4
*/
if (presenceJointNonRenseignee(porte_list, bv_list)) {
if (!ligneDpeReprise && presenceJointNonRenseignee(porte_list, bv_list)) {
pjt = rowQ4paConv.presence_joints_menuiserie === '1' ? '1' : '0';
} else if (rowQ4paConv.presence_joints_menuiserie !== pjt) {
} else if (!ligneDpeReprise && 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.`
Expand Down
108 changes: 107 additions & 1 deletion src/4_ventilation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ vi.mock('./5_conso_ventilation.js', () => ({
const {
default: calc_ventilation,
calc_hperm,
presenceJointNonRenseignee
presenceJointNonRenseignee,
isLigneQ4paMemePeriode
} = await import('./4_ventilation.js');
const { tv } = await import('./utils.js');
const { default: calc_pvent } = await import('./5_conso_ventilation.js');
Expand Down Expand Up @@ -480,6 +481,86 @@ describe('calc_ventilation - compatibilité bug_for_bug_compat activé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', () => {
const murNonIsole = [
{
donnee_intermediaire: { b: 1 },
donnee_entree: { enum_type_isolation_id: '2', surface_paroi_opaque: 100 }
}
];
const baieSansJoint = [{ donnee_entree: { surface_totale_baie: 10, presence_joint: 0 } }];
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 () => {
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const { calc_ventilation_bug, tvBug } = await chargerAvecBug(ROW_10);

calc_ventilation_bug(vtLigne(10), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint);

expect(tvBug).toHaveBeenLastCalledWith(
'q4pa_conv',
expect.objectContaining({ isolation_surfaces: '1', presence_joints_menuiserie: '0' })
);
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining('La ligne q4pa_conv 10 du DPE est conservée')
);
// les joints de la ligne reprise concordent : pas de second message
expect(errorSpy).toHaveBeenCalledTimes(1);
errorSpy.mockRestore();
});

test('ligne 12 (non isolé, avec joints) reprise en entier, malgré des baies sans joint', async () => {
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'
});
vi.spyOn(console, 'error').mockImplementation(() => {});
const { murs } = surfacesIsolantes();

calc_ventilation_bug(vtLigne(12), CG, 'th', 80, 100, murs, [], [], baieSansJoint);

expect(tvBug).toHaveBeenLastCalledWith(
'q4pa_conv',
expect.objectContaining({ isolation_surfaces: '0', presence_joints_menuiserie: '1' })
);
});

test('ligne d’une autre période : la valeur recalculée est conservé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'
});

calc_ventilation_bug(vtLigne(11), CG, 'th', 80, 100, murNonIsole, [], [], baieSansJoint);

expect(tvBug).toHaveBeenLastCalledWith(
'q4pa_conv',
expect.objectContaining({ isolation_surfaces: '0' })
);
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining('La valeur recalculée isolation_surfaces = 0 est utilisée')
);
errorSpy.mockRestore();
});
});
});

describe('presenceJointNonRenseignee', () => {
Expand All @@ -506,3 +587,28 @@ describe('presenceJointNonRenseignee', () => {
).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);
});
});
30 changes: 30 additions & 0 deletions test/open3cl-q4pa-ligne-dpe.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, test } from 'vitest';
import { getAdemeFileJson } from './test-helpers.js';
import { calcul_3cl } from '../src/index.js';
import { set_bug_for_bug_compat, set_tv_match_optimized_version } from '../src/utils.js';

/**
* 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.
*/
describe('q4pa_conv repris de la ligne du DPE - issue #312', () => {
test('2287E1201982M : q4pa_conv, hperm et conso_ch identiques au DPE', () => {
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
);
});
});