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
This commit is contained in:
Jules
2026-09-28 17:06:49 +02:00
co-authored by Claude Fable 5.1
commit 69809df5d1
123 changed files with 13225 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
// 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';
}