Files
JulesandClaude Fable 5.1 69809df5d1 LOT P1.0 : paquet @transformer/moteurs v0.1.0
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
2026-09-28 17:06:49 +02:00

60 lines
2.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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';
}