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
136 lines
6.3 KiB
JavaScript
136 lines
6.3 KiB
JavaScript
// 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 } };
|
|
}
|