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:
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
// taux.ts — taux de couverture par axe, depuis une sélection de postes.
|
||||
// Origine calculs/src/scripts/autonomie/taux.js (formules 99-brief §7), sorti
|
||||
// du view-model : les entrées sont des grandeurs, plus l'état de l'interface.
|
||||
// Coefficients lus dans la table injectée ; chaque lecture est tracée.
|
||||
//
|
||||
// Changement d'harmonisation (LOT P1.0, point 5) : la couverture ÉNERGIE
|
||||
// applique le taux d'autoconsommation (0,30 sans batterie, 0,70 avec 10 kWh,
|
||||
// interpolation linéaire entre les deux) — l'origine comparait la production
|
||||
// brute au besoin, ce qui surestimait la couverture.
|
||||
import { Trace } from '../commun/contrat.js';
|
||||
function lit(trace, coefficients, cle, valeur) {
|
||||
const c = coefficients[cle];
|
||||
trace.source(cle, valeur, c.source, c.date);
|
||||
}
|
||||
/** kWc installés dans la sélection (dimensions en kWc). */
|
||||
export function kwcSelectionnes(postes) {
|
||||
let kwc = 0;
|
||||
for (const p of postes)
|
||||
if (p.dimension && p.dimension.unite === 'kWc')
|
||||
kwc += p.dimension.valeur || 0;
|
||||
return kwc;
|
||||
}
|
||||
/** kWh de batterie dans la sélection (dimensions en kWh). */
|
||||
export function batterieSelectionnee(postes) {
|
||||
let kwh = 0;
|
||||
for (const p of postes)
|
||||
if (p.dimension && p.dimension.unite === 'kWh')
|
||||
kwh += p.dimension.valeur || 0;
|
||||
return kwh;
|
||||
}
|
||||
/** Mètres linéaires de haie dans la sélection. */
|
||||
export function mlHaiesSelectionnes(postes) {
|
||||
let ml = 0;
|
||||
for (const p of postes)
|
||||
if (p.dimension && p.dimension.unite === 'ml')
|
||||
ml += p.dimension.valeur || 0;
|
||||
return ml;
|
||||
}
|
||||
/** Vrai si la sélection porte un équipement de captage. */
|
||||
export function aCaptage(postes) {
|
||||
return postes.some((p) => String(p.typologie_choisie).startsWith('captage') || String(p.poste_id).includes('captage'));
|
||||
}
|
||||
/**
|
||||
* EAU : volume captable vs besoin substituable. null si aucun captage.
|
||||
*/
|
||||
export function tauxEau(entrees, coefficients, trace) {
|
||||
if (!entrees.a_captage)
|
||||
return { taux: null, detail: null };
|
||||
const nb = entrees.nb_personnes || 1;
|
||||
const rendement = coefficients.captage_rendement.valeur;
|
||||
const besoin_l_an = coefficients.besoin_eau_l_j_pers.valeur * 365;
|
||||
const part = coefficients.part_eau_substituable.valeur;
|
||||
lit(trace, coefficients, 'captage_rendement', rendement);
|
||||
lit(trace, coefficients, 'besoin_eau_l_j_pers', coefficients.besoin_eau_l_j_pers.valeur);
|
||||
lit(trace, coefficients, 'part_eau_substituable', part);
|
||||
const captableL = (entrees.surface_toit_m2 || 0) * (entrees.pluviometrie_mm / 1000) * rendement * 1000;
|
||||
const besoinSubstL = nb * besoin_l_an * part;
|
||||
if (!(besoinSubstL > 0 && captableL > 0))
|
||||
return { taux: null, detail: null };
|
||||
return {
|
||||
taux: Math.min(100, Math.round((captableL / besoinSubstL) * 100)),
|
||||
detail: {
|
||||
captable_m3: Math.round(captableL / 1000),
|
||||
besoin_m3: Math.round((nb * besoin_l_an) / 1000),
|
||||
besoin_substituable_m3: Math.round(besoinSubstL / 1000),
|
||||
},
|
||||
};
|
||||
}
|
||||
/** Taux d'autoconsommation selon la batterie : 0 kWh → sans, ≥ 10 kWh → avec, linéaire entre. */
|
||||
export function tauxAutoconso(batterie_kwh, coefficients) {
|
||||
const sans = coefficients.autoconso_sans_batterie.valeur;
|
||||
const avec = coefficients.autoconso_avec_batterie_10kwh.valeur;
|
||||
const t = Math.max(0, Math.min(1, batterie_kwh / 10));
|
||||
return sans + (avec - sans) * t;
|
||||
}
|
||||
/** Productible PV (kWh/kWc/an) pour un contexte, depuis la table de coefficients. */
|
||||
export function productiblePv(contexte, coefficients, trace) {
|
||||
const c = coefficients.productible_pv_kwh_kwc.valeur;
|
||||
const cle = c.par === 'zone_rt' ? contexte.zone_climatique_rt : String(contexte.zone_biogeo_code);
|
||||
const v = c.table[cle];
|
||||
if (v == null) {
|
||||
trace.avertit(`productible PV : aucune valeur pour ${c.par} « ${cle} », défaut ${c.defaut} kWh/kWc`);
|
||||
return c.defaut;
|
||||
}
|
||||
if (c.par === 'zone_rt' && cle === 'H2d')
|
||||
trace.avertit('productible PV H2d interpolé entre H2c et H3 (non sourcé)');
|
||||
return v;
|
||||
}
|
||||
/**
|
||||
* ÉNERGIE : production PV autoconsommée vs besoin d'électricité spécifique.
|
||||
* null si aucun kWc dans la sélection.
|
||||
*/
|
||||
export function tauxEnergie(entrees, coefficients, trace) {
|
||||
if (!(entrees.kwc > 0))
|
||||
return { taux: null, detail: null };
|
||||
const nb = entrees.nb_personnes || 1;
|
||||
const kwh_par_kwc = productiblePv(entrees.contexte, coefficients, trace);
|
||||
lit(trace, coefficients, 'productible_pv_kwh_kwc', kwh_par_kwc);
|
||||
const table = coefficients.orientation_pv.valeur;
|
||||
const coef = (entrees.orientation_toit ? table[entrees.orientation_toit] : undefined) ?? 1;
|
||||
if (entrees.orientation_toit && table[entrees.orientation_toit] == null)
|
||||
trace.avertit(`orientation « ${entrees.orientation_toit} » inconnue : facteur 1 appliqué`);
|
||||
lit(trace, coefficients, 'orientation_pv', coef);
|
||||
const production = entrees.kwc * kwh_par_kwc * coef;
|
||||
const autoconso = tauxAutoconso(entrees.batterie_kwh, coefficients);
|
||||
lit(trace, coefficients, 'autoconso_sans_batterie', coefficients.autoconso_sans_batterie.valeur);
|
||||
lit(trace, coefficients, 'autoconso_avec_batterie_10kwh', coefficients.autoconso_avec_batterie_10kwh.valeur);
|
||||
const besoin = nb * coefficients.besoin_elec_kwh_pers_an.valeur;
|
||||
lit(trace, coefficients, 'besoin_elec_kwh_pers_an', coefficients.besoin_elec_kwh_pers_an.valeur);
|
||||
const autoconsomme = production * autoconso;
|
||||
return {
|
||||
taux: Math.min(100, Math.round((autoconsomme / besoin) * 100)),
|
||||
detail: {
|
||||
kwc: entrees.kwc,
|
||||
kwh_par_kwc,
|
||||
facteur_orientation: coef,
|
||||
production_kwh: Math.round(production),
|
||||
batterie_kwh: entrees.batterie_kwh,
|
||||
taux_autoconso: +autoconso.toFixed(3),
|
||||
autoconsomme_kwh: Math.round(autoconsomme),
|
||||
besoin_kwh: besoin,
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* ALIMENTATION : mètres de haie vs recommandation. null si aucune haie.
|
||||
*/
|
||||
export function tauxAlim(entrees, coefficients, trace) {
|
||||
const nb = entrees.nb_personnes || 1;
|
||||
const reco = nb * coefficients.ml_haie_par_pers.valeur;
|
||||
lit(trace, coefficients, 'ml_haie_par_pers', coefficients.ml_haie_par_pers.valeur);
|
||||
if (!(entrees.ml > 0 && reco > 0))
|
||||
return { taux: null, detail: null };
|
||||
return { taux: Math.min(100, Math.round((entrees.ml / reco) * 100)), detail: { ml: entrees.ml, reco_ml: reco } };
|
||||
}
|
||||
Reference in New Issue
Block a user