// dimension.ts — dimension_optimale(typo, inputs, contexte_derived, coefficients) → Dimension | null // Spec: V2-algo-spec-FONCTIONS.md §2 — origine calculs/src/lib/autonomie/dimension.js. // Correction LOT P1.0 : facteurs d'orientation et DJU par défaut lus dans la // table de coefficients passée en paramètre (plus de constante de module). import { zoneRtToClass, clamp, roundToStep } from './calibrage.js'; import { parseParam } from './types.js'; /** * Calcule la valeur optimale du paramètre dimensionnant pour une typologie. * * Particularité ballon_tampon (th_ballon_500) : * - unite BDD = "forfait" (contrainte schéma NocoDB) * - prix_par_unite = 1.2 EUR/L * - parametre_dim.unite = "L" (source de vérité pour l'affichage et le calcul) * → L'algo utilise toujours parametre_dim.unite quand présent, sinon fallback option.unite */ export function dimension_optimale(typo, inputs, contexte_derived, coefficients) { const param = parseParam(typo.parametre_dim); if (!param) return null; const strategie = param.strategie || 'fixe'; let valeur; switch (strategie) { case 'par_personne': { const base = param.base != null ? param.base : 0; const coef = param.coef != null ? param.coef : 1; valeur = base + (inputs.nb_personnes || 4) * coef; break; } case 'par_surface_toit': { const table = coefficients.orientation_pv.valeur; const orient = (inputs.orientation_toit ? table[inputs.orientation_toit] : undefined) || 0.8; const coef = param.coef != null ? param.coef : 0.1; valeur = (inputs.surface_toit_m2 || 0) * coef * orient; break; } case 'par_surface_parcelle': { const reserve = param.reserve_construit != null ? param.reserve_construit : 100; const utile = Math.max(0, (inputs.surface_parcelle_m2 || 0) - reserve); const coef = param.coef != null ? param.coef : 0.05; valeur = utile * coef; break; } case 'par_dju': { const zone_class = zoneRtToClass(contexte_derived.zone_climatique_rt); const dju = contexte_derived.dju || coefficients.dju_defaut_par_classe_rt.valeur[zone_class] || 2200; const coef = param.coef != null ? param.coef : 0.005; valeur = dju * coef; break; } case 'par_metres_haie': { const coef = param.coef != null ? param.coef : 1.0; valeur = (inputs.metres_haie_possibles || 0) * coef; break; } case 'fixe': default: valeur = param.default != null ? param.default : 1; break; } // Clamp + arrondi step const min = param.min != null ? param.min : 0; const max = param.max != null ? param.max : Infinity; const step = param.step != null ? param.step : 0; valeur = clamp(valeur, min, max); valeur = roundToStep(valeur, step); // Unite : parametre_dim.unite prioritaire sur option.unite (cas ballon_tampon) const unite = param.unite || typo.unite || ''; return { valeur, unite, strategie }; } /** * Calcule le coût HT d'une typologie pour une dimension donnée. * * Hiérarchie des prix (3 cas BDD) : * 1. prix_fixe + prix_par_unite × dim.valeur (V2 enrichi, ex: cuve béton, PV) * 2. prix_par_unite × dim.valeur seul (ex: th_ballon_500) * 3. prix_mat_min + pose si pro requis (forfaits legacy sans parametre_dim enrichi) * * Pour les postes pro (complexite_pose=3) et temps_dispo < 3, ajouter prix_pro_min. * Fallback final : (prix_mat_min + prix_mat_max) / 2 * * Les prix sont des DONNÉES de l'option (publiées par calculs) ; la seule * heuristique du code est « pose pro = 50 % du matériel si prix_pro absent ». */ export function cout_typologie(typo, dim_result, inputs) { const prix_fixe = typo.prix_fixe != null ? typo.prix_fixe : 0; const prix_par_unite = typo.prix_par_unite; const prix_mat_min = typo.prix_mat_min != null ? typo.prix_mat_min : 0; const prix_mat_max = typo.prix_mat_max != null ? typo.prix_mat_max : prix_mat_min; const prix_pro_min = typo.prix_pro_min != null ? typo.prix_pro_min : 0; // Cas 1 & 2 : prix_par_unite disponible (schéma V2 enrichi) if (prix_par_unite != null && prix_par_unite > 0) { const dim_val = dim_result ? dim_result.valeur : typo.quantite_default || 1; const base = prix_fixe + prix_par_unite * dim_val; // Forfait pro si complexite=3 et temps_dispo=1 if (typo.complexite_pose === 3 && inputs && inputs.temps_dispo === 1 && prix_pro_min > 0) { return Math.max(base, prix_pro_min); } return base; } // Cas 3 : forfait legacy (prix_mat_min/max seulement) if (prix_mat_min > 0) { const mid_mat = (prix_mat_min + prix_mat_max) / 2; // Si complexite_pose = 3 et pose pro : ajouter coût pose pro if (typo.complexite_pose === 3) { const pose = prix_pro_min > 0 ? prix_pro_min : mid_mat * 0.5; return mid_mat + pose; } // complexite 1-2 : coût mat seul (DIY inclus) return mid_mat; } // Fallback ultime : 0 (sécurité, ne devrait pas arriver) return 0; }