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
60 lines
2.4 KiB
JavaScript
60 lines
2.4 KiB
JavaScript
// prix.ts — fourchettes de prix d'une option selon le mode de pose, prix d'un
|
||
// preset, preset le plus proche d'un montant. Origine
|
||
// calculs/src/scripts/autonomie/prix.js + matchPresetNiveaux de simulation.js.
|
||
// Les prix sont des DONNÉES des options (publiées par calculs) ; ce module ne
|
||
// porte aucun montant. Le formatage (« 1 200 € HT ») reste côté site.
|
||
/** Fourchette d'une option × quantité : colonnes prix_mat_* (auto-pose) ou prix_pro_* (faire poser). */
|
||
export function prixOption(opt, qty, pose) {
|
||
const q = qty || opt.quantite_default || 1;
|
||
let min;
|
||
let max;
|
||
if (pose === 'auto') {
|
||
min = opt.prix_mat_min;
|
||
max = opt.prix_mat_max;
|
||
}
|
||
else {
|
||
min = opt.prix_pro_min != null ? opt.prix_pro_min : opt.prix_mat_min;
|
||
max = opt.prix_pro_max != null ? opt.prix_pro_max : opt.prix_mat_max;
|
||
}
|
||
if (min == null && max == null)
|
||
return { min: null, max: null, autoOnly: false };
|
||
return {
|
||
min: min != null ? min * q : null,
|
||
max: max != null ? max * q : null,
|
||
autoOnly: pose === 'pro' && opt.prix_pro_min == null && opt.prix_pro_max == null,
|
||
};
|
||
}
|
||
/** Prix total d'un preset (somme de ses options × quantités) pour un mode de pose. */
|
||
export function prixPreset(preset, presetOptions, optionsById, pose) {
|
||
let min = 0;
|
||
let max = 0;
|
||
for (const po of presetOptions) {
|
||
if (po.preset_id !== preset.Id)
|
||
continue;
|
||
const opt = optionsById[po.option_id];
|
||
if (!opt)
|
||
continue;
|
||
const p = prixOption(opt, po.quantite || 1, pose);
|
||
if (p.min == null)
|
||
continue;
|
||
min += p.min;
|
||
max += p.max != null ? p.max : p.min;
|
||
}
|
||
return { min, max };
|
||
}
|
||
/** Niveau de preset (light/standard/complet) dont le prix médian est le plus proche du montant cible, pour un axe (code BDD). */
|
||
export function presetLePlusProche(cible, axe_bdd, presets, presetOptions, optionsById, pose) {
|
||
let best = null;
|
||
let bestDist = Infinity;
|
||
for (const preset of presets.filter((p) => p.axe === axe_bdd && p.actif !== false)) {
|
||
const prix = prixPreset(preset, presetOptions, optionsById, pose);
|
||
const mid = (prix.min + prix.max) / 2;
|
||
const dist = Math.abs(mid - cible);
|
||
if (dist < bestDist) {
|
||
bestDist = dist;
|
||
best = preset;
|
||
}
|
||
}
|
||
return best ? best.niveau : 'standard';
|
||
}
|