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
32 changes: 14 additions & 18 deletions src/3.2.2_plancher_bas.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ function tv_upb(di, de, du, pc_id, zc, effetJoule) {

const values_2s_p = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20];

function tv_ue(di, de, du, pc_id, pb_list) {
/**
* Ue du plancher bas courant, calculé plancher par plancher (issue #46).
* 2S/P est déterminé avec la surface (surface_ue, à défaut surface_paroi_opaque) et le périmètre
* (perimetre_ue) du seul plancher courant, sans sommer les autres planchers de même adjacence.
* Les valeurs absentes du tableau sont obtenues par interpolation linéaire sur upb.
*
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.2.2 (tableau ue)
*/
function tv_ue(di, de, du, pc_id) {
const type_adjacence = enums.type_adjacence[de.enum_type_adjacence_id];
let type_adjacence_plancher;
let upb1, upb2;
Expand All @@ -72,21 +80,9 @@ function tv_ue(di, de, du, pc_id, pb_list) {
[upb1, upb2] = getRange(di.upb, [0.31, 0.34, 0.37, 0.41, 0.45, 0.83, 1.43, 3.33]);
}

/**
* La surface Ue est la surface de tous les planchers bas ayant le même type d'adjacence
* Le périmètre Ue est le périmètre de tous les planchers bas ayant le même type d'adjacence
*/
const { surfaceUe, perimetreUe } = pb_list.reduce(
(acc, plancherBas) => {
if (plancherBas.donnee_entree.enum_type_adjacence_id === de.enum_type_adjacence_id) {
acc.surfaceUe +=
plancherBas.donnee_entree.surface_ue || plancherBas.donnee_entree.surface_paroi_opaque;
acc.perimetreUe += plancherBas.donnee_entree.perimetre_ue || 0;
}
return acc;
},
{ surfaceUe: 0, perimetreUe: 0 }
);
// Surface et périmètre Ue du plancher courant uniquement
const surfaceUe = de.surface_ue || de.surface_paroi_opaque;
const perimetreUe = de.perimetre_ue || 0;

const matcher = {
type_adjacence_plancher,
Expand Down Expand Up @@ -134,7 +130,7 @@ function calc_upb0(di, de, du) {
}
}

export default function calc_pb(pb, zc, pc_id, effetJoule, pb_list) {
export default function calc_pb(pb, zc, pc_id, effetJoule) {
const de = pb.donnee_entree;
const du = {};
const di = {};
Expand Down Expand Up @@ -246,7 +242,7 @@ export default function calc_pb(pb, zc, pc_id, effetJoule, pb_list) {
case 'vide sanitaire':
case 'sous-sol non chauffé':
case 'terre-plein':
tv_ue(di, de, du, pc_id, pb_list);
tv_ue(di, de, du, pc_id);
di.upb_final = de.ue;
break;
default:
Expand Down
114 changes: 111 additions & 3 deletions src/3.2.2_plancher_bas.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -561,14 +561,17 @@ describe('calc_pb - calcul de Ue (tv_ue)', () => {
enum_type_plancher_bas_id: '9',
type_adjacence: 'terre-plein',
enum_type_adjacence_id: '5',
surface_paroi_opaque: 4 // pas de surface_ue => fallback
surface_paroi_opaque: 4, // pas de surface_ue => fallback
perimetre_ue: 2
}
};

calc_pb(pb, 'h1a', '1', '0', [pb1, pb, autre]);

// surfaceUe = 20 (pb1.surface_ue) + 4 (pb.surface_paroi_opaque) = 24 ; perimetreUe = 12 + 0 = 12
// 2S/P = round(48/12) = 4 (valeur présente dans values_2s_p)
// Calcul plancher par plancher (#46) : les autres planchers (pb1, autre) sont ignorés.
// Ce test encodait auparavant la somme par adjacence (S = 20 + 4, P = 12 + 0) ; perimetre_ue
// est désormais porté par le plancher courant pour conserver 2S/P = 4.
// surfaceUe = 4 (pb.surface_paroi_opaque) ; perimetreUe = 2 => 2S/P = round(8/2) = 4
expect(tv).toHaveBeenCalledWith('ue', {
type_adjacence_plancher: 'terre plein bâtiment construit avant 2001',
'2s_p': '^4$',
Expand Down Expand Up @@ -652,4 +655,109 @@ describe('calc_pb - calcul de Ue (tv_ue)', () => {

expect(pb.donnee_intermediaire.upb_final).toBe(0.19);
});

/**
* Issue #46 : ue calculé plancher par plancher, 2S/P issu du seul plancher courant.
* @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.2.2 (tableau ue : « Les données ne figurant
* pas dans le tableau peuvent être obtenues par interpolation et extrapolation »)
*/
describe('calcul plancher par plancher (#46)', () => {
/** Plancher non isolé (methode 1) dont upb = upb0 = 1,45. */
const plancher = (donnees) => ({
donnee_entree: {
enum_methode_saisie_u_id: '1',
methode_saisie_u0:
'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
enum_type_plancher_bas_id: '2',
type_adjacence: 'terre-plein',
enum_type_adjacence_id: '5',
...donnees
}
});

/** Table ue mockée par couple (2s_p, upb) : terre-plein avant 2001. */
const UE = {
'^7$': { 0.85: 0.33, 1.5: 0.39 },
'^2$': { 0.85: 0.55, 1.5: 0.8 },
'^1$': { 0.85: 0.7, 1.5: 0.9 },
'^4$': { 0.85: 0.45, 1.5: 0.6 }
};

beforeEach(() => {
getRange.mockReturnValue([0.85, 1.5]);
tv.mockImplementation((table, matcher) => {
if (table === 'upb0') return { upb0: '1.45', tv_upb0_id: '2' };
if (table === 'ue') return { ue: String(UE[matcher['2s_p']][matcher.upb]) };
return null;
});
});

test('2187E0982591I plancher 1 : 2S/P = 7 et ue = 0,385384615 malgré un autre terre-plein', () => {
const pb1 = plancher({ surface_paroi_opaque: 44.8, perimetre_ue: 12.8 });
const pb3 = plancher({ surface_paroi_opaque: 10.5, perimetre_ue: 10 });

calc_pb(pb1, 'h1a', '1', '0', [pb1, pb3]);

// 2S/P = 2 * 44,8 / 12,8 = 7 (somme par adjacence : 2 * 55,3 / 22,8 ≈ 4,85 => 5)
expect(tv).toHaveBeenCalledWith('ue', {
type_adjacence_plancher: 'terre plein bâtiment construit avant 2001',
'2s_p': '^7$',
upb: '0.85'
});
expect(tv).toHaveBeenCalledWith('ue', {
type_adjacence_plancher: 'terre plein bâtiment construit avant 2001',
'2s_p': '^7$',
upb: '1.5'
});
expect(tv).not.toHaveBeenCalledWith('ue', expect.objectContaining({ '2s_p': '^5$' }));
// ue = 0,33 + (0,39 - 0,33) * (1,45 - 0,85) / (1,5 - 0,85) = 0,385384615 (valeur ADEME)
expect(pb1.donnee_entree.ue).toBeCloseTo(0.385384615, 9);
expect(pb1.donnee_intermediaire.upb_final).toBeCloseTo(0.385384615, 9);
});

test('deux planchers de même adjacence avec surface_ue / perimetre_ue différents : ue différents', () => {
const pbA = plancher({ surface_paroi_opaque: 50, surface_ue: 70, perimetre_ue: 20 });
const pbB = plancher({ surface_paroi_opaque: 50, surface_ue: 10, perimetre_ue: 10 });
const liste = [pbA, pbB];

liste.forEach((pb) => calc_pb(pb, 'h1a', '1', '0', liste));

// pbA : 2 * 70 / 20 = 7 => ue interpolé entre 0,33 et 0,39
expect(pbA.donnee_entree.ue).toBeCloseTo(0.385384615, 9);
// pbB : 2 * 10 / 10 = 2 => 0,55 + 0,25 * 0,6 / 0,65 (valeur de référence de régression)
expect(pbB.donnee_entree.ue).toBeCloseTo(0.780769231, 9);
expect(pbA.donnee_entree.ue).not.toBeCloseTo(pbB.donnee_entree.ue, 3);
});

test('surface_ue absente : surface_paroi_opaque du plancher courant utilisée', () => {
const pb = plancher({ surface_paroi_opaque: 20, perimetre_ue: 10 });
const autre = plancher({ surface_ue: 500, surface_paroi_opaque: 500, perimetre_ue: 10 });

calc_pb(pb, 'h1a', '1', '0', [pb, autre]);

// 2S/P = 2 * 20 / 10 = 4
expect(tv).toHaveBeenCalledWith('ue', expect.objectContaining({ '2s_p': '^4$' }));
// ue = 0,45 + 0,15 * 0,6 / 0,65 (valeur de référence de régression)
expect(pb.donnee_entree.ue).toBeCloseTo(0.588461538, 9);
});

test('surface_ue prioritaire sur surface_paroi_opaque', () => {
const pb = plancher({ surface_paroi_opaque: 20, surface_ue: 35, perimetre_ue: 10 });

calc_pb(pb, 'h1a', '1', '0', [pb]);

// 2S/P = 2 * 35 / 10 = 7
expect(tv).toHaveBeenCalledWith('ue', expect.objectContaining({ '2s_p': '^7$' }));
});

test('perimetre_ue absent : comportement inchangé (division par 0 => 2S/P = 1), même si un autre plancher en a un', () => {
const pb = plancher({ surface_paroi_opaque: 20 });
const autre = plancher({ surface_paroi_opaque: 20, perimetre_ue: 10 });

calc_pb(pb, 'h1a', '1', '0', [pb, autre]);

expect(tv).toHaveBeenCalledWith('ue', expect.objectContaining({ '2s_p': '^1$' }));
expect(pb.donnee_entree.ue).toBeCloseTo(0.884615385, 9);
});
});
});
2 changes: 1 addition & 1 deletion src/3_deperdition.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export default function calc_deperdition(cg, zc, th, effetJoule, dpe, Sh) {
*/
const versionDpe = parseFloat(dpe.administratif?.enum_version_id);
mur_list.forEach((mur) => calc_mur(mur, zc, pc, effetJoule, versionDpe));
pb_list.forEach((pb) => calc_pb(pb, zc, pc, effetJoule, pb_list));
pb_list.forEach((pb) => calc_pb(pb, zc, pc, effetJoule));
ph_list.forEach((ph) => calc_ph(ph, zc, pc, effetJoule));
bv_list.forEach((bv) => calc_bv(bv, zc));
porte_list.forEach((porte) => calc_porte(porte, zc));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,7 @@ export class DeperditionEnveloppeService {

const plancherBas = enveloppe.plancher_bas_collection.plancher_bas || [];
plancherBas?.forEach((pb) => {
pb.donnee_intermediaire = this.#deperditionPlancherBasService.execute(
ctx,
pb.donnee_entree,
plancherBas
);
pb.donnee_intermediaire = this.#deperditionPlancherBasService.execute(ctx, pb.donnee_entree);
deperditions.deperdition_plancher_bas +=
pb.donnee_intermediaire.b *
pb.donnee_entree.surface_paroi_opaque *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ describe('DeperditionEnveloppeService (§3 déperditions de l’enveloppe)', ()
});

// Délégation aux services de paroi
// Ue calculé plancher par plancher (#46) : la liste des planchers n'est plus transmise
expect(plancherBasService.execute).toHaveBeenCalledWith(
ctx,
env.plancher_bas_collection.plancher_bas[0].donnee_entree,
env.plancher_bas_collection.plancher_bas
env.plancher_bas_collection.plancher_bas[0].donnee_entree
);
expect(pontThermiqueService.execute).toHaveBeenCalledWith(
ctx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ export class DeperditionPlancherBasService extends DeperditionService {
/**
* @param ctx {Contexte}
* @param pbDE {PlancherBasDE}
* @param plancherBas {PlancherBas[]}
* @return {PlancherBasDI}
*/
execute(ctx, pbDE, plancherBas) {
execute(ctx, pbDE) {
const upb0 = this.#upb0(pbDE);
const upb = this.#upb(pbDE, upb0, ctx);
const upb_final = this.#upbFinal(pbDE, upb, ctx, plancherBas);
const upb_final = this.#upbFinal(pbDE, upb, ctx);
const b = this.b({
enumTypeAdjacenceId: pbDE.enum_type_adjacence_id,
surfaceAiu: pbDE.surface_aiu,
Expand Down Expand Up @@ -113,10 +112,9 @@ export class DeperditionPlancherBasService extends DeperditionService {
* @param pbDE {PlancherBasDE}
* @param upb {number}
* @param ctx {Contexte}
* @param plancherBas {PlancherBas[]}
* @return {number|undefined}
*/
#upbFinal(pbDE, upb, ctx, plancherBas) {
#upbFinal(pbDE, upb, ctx) {
if (pbDE.calcul_ue === 1) {
return pbDE.ue;
}
Expand All @@ -131,23 +129,13 @@ export class DeperditionPlancherBasService extends DeperditionService {
}

/**
* La surface Ue est la surface de tous les planchers bas ayant le même type d'adjacence
* Le périmètre Ue est le périmètre de tous les planchers bas ayant le même type d'adjacence
* Calcul plancher par plancher (issue #46) : 2S/P est déterminé avec la surface (surface_ue, à
* défaut surface_paroi_opaque) et le périmètre (perimetre_ue) du seul plancher courant, sans
* sommer les autres planchers de même adjacence.
* @see Methode_de_calcul_3CL_DPE_2021-338.pdf - §3.2.2 (tableau ue)
*/
const { surfaceUe, perimetreUe } = plancherBas
.filter(
(plancherBas) =>
pbDE.enum_type_adjacence_id === plancherBas.donnee_entree.enum_type_adjacence_id
)
.reduce(
(acc, plancherBas) => {
acc.surfaceUe +=
plancherBas.donnee_entree.surface_ue || plancherBas.donnee_entree.surface_paroi_opaque;
acc.perimetreUe += plancherBas.donnee_entree.perimetre_ue || 0;
return acc;
},
{ surfaceUe: 0, perimetreUe: 0 }
);
const surfaceUe = pbDE.surface_ue || pbDE.surface_paroi_opaque;
const perimetreUe = pbDE.perimetre_ue || 0;

const available2sp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20];
let dsp = perimetreUe ? Math.round((2 * surfaceUe) / perimetreUe) : 1;
Expand Down
Loading
Loading