Extraction du cœur autonomie de calculs.trans-former.fr (12 modules, sans singleton, couplages sans mutation, console derrière debug), découpage en trois moteurs d'axe + intégrateur (mode global = calculs, mode par_axe = plateforme), moteurs commun.contexte (Corse 2A/2B, DOM 97x corrigés) et commun.geometrie, table des coefficients harmonisés (docs/CONSTANTES.md pour Jules, R-7), validation structurelle des données (schemas/), tests hors ligne : 28 assertions d'origine + épingle générée depuis le code d'astro-pro + écarts d'harmonisation figés + contrat des moteurs. dist/ committé. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UBHYPdeQYy2m1GVJp6b12
114 lines
5.2 KiB
JavaScript
114 lines
5.2 KiB
JavaScript
// 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;
|
||
}
|