Épingles : écarts correctifs face à l'épingle d'origine, écarts d'harmonisation refigés (05/10)
L'épingle d'origine (fixtures/sorties-origine-2026-09-28.json, générée depuis le code d'astro-pro) porte les défauts corrigés par91c755f(eg_phytostation ×3 sur C/eau, basse_planche_bec ×2 sur B et C/alim, lignes forcées orphelines, FORCE ×4 dans le journal, A/eau par axe en INSUFFISANT). La regénérer depuis astro-pro reproduirait les défauts ; la regénérer depuis ce paquet en ferait une épingle sur soi-même. Elle reste donc telle quelle, et : - scripts/ecarts-correctifs.mjs + fixtures/ecarts-correctifs-2026-10-05.json : chaque cellule (profil × mode × axe × champ) qui s'écarte de l'origine est figée, avec le détail des lignes ; une cellule absente doit rester identique. - test/decoupage.test.mjs : test 1 compare aux écarts figés (et vérifie qu'un correctif n'introduit jamais un INSUFFISANT ni un total hors ±5 % en global ; budgets alloués et poids des axes inchangés) ; test 2 vérifie la propriété du découpage contre selectionner() de ce paquet ; test 3 inchangé dans l'esprit. - Écarts d'harmonisation REFIGÉS (npm run ecarts -- --ecrire 2026-10-05) après relecture de docs/CONSTANTES.md : neuf lignes au lieu de onze. Les deux lignes perdues (A énergie global 22 → 6 %, C énergie par axe 10 → 3 %) étaient des taux calculés sur des kWc fantômes hérités par une batterie ; la couverture y est null avec les deux tables. Aucun coefficient ne change. Note datée posée dans CONSTANTES.md §1. (La suppression du fichier du 28/09 est partie par erreur dans8465bf7, avec la version : même intention, deux commits.) - fixtures/README.md et AGENTS.md (règles 6 et 8) : l'épingle d'origine ne se regénère pas pour absorber un correctif ; invariants tenus depuis v0.1.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgtn7bJxTLiL2fRCkQMgtR
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
8465bf7204
commit
4f593d2edf
@@ -0,0 +1,99 @@
|
||||
// ecarts-correctifs.mjs — compare, sur les trois profils de référence et avec le
|
||||
// jeu de coefficients D'ORIGINE, ce que le paquet rend aujourd'hui à l'épingle
|
||||
// générée depuis le code d'astro-pro (fixtures/sorties-origine-<date>.json).
|
||||
//
|
||||
// Pourquoi ce script existe (v0.1.1) : l'épingle d'origine porte les défauts
|
||||
// que le correctif répare (SPEC menu §12 n° 1, 2, 3, 5 : doublons, accessoires
|
||||
// forcés orphelins, statut non monotone, journal FORCE répété). La regénérer
|
||||
// depuis astro-pro reproduirait les défauts ; la regénérer depuis ce paquet en
|
||||
// ferait une épingle sur soi-même. Elle reste donc telle quelle, et chaque écart
|
||||
// voulu est écrit ici, cellule par cellule, dans fixtures/ecarts-correctifs-<date>.json.
|
||||
// test/decoupage.test.mjs exige que les écarts calculés soient EXACTEMENT ceux
|
||||
// figés : une cellule non listée doit rester identique à l'origine.
|
||||
// Usage : node scripts/ecarts-correctifs.mjs [--ecrire AAAA-MM-JJ]
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { eau, energie, alimentation, autonomie } from '../dist/index.js'
|
||||
|
||||
const fx = new URL('../fixtures/', import.meta.url)
|
||||
const lire = (f) => JSON.parse(readFileSync(new URL(f, fx), 'utf8'))
|
||||
const options = lire('options.json')
|
||||
const postes = lire('postes.json')
|
||||
const france_climat = lire('france_climat.json')
|
||||
const algo_config = lire('algo-config.json')
|
||||
const profils = lire('profils-reference.json')
|
||||
const origine = lire('coefficients-2026-05.json')
|
||||
export const PIN_ORIGINE = 'sorties-origine-2026-09-28.json'
|
||||
const pin = lire(PIN_ORIGINE)
|
||||
const AXES = ['eau', 'energie', 'alim']
|
||||
const MOTEURS = { eau, energie, alim: alimentation }
|
||||
|
||||
const resumePostes = (liste) => liste.map((p) => ({ poste_id: p.poste_id, typologie_choisie: p.typologie_choisie, dimension: p.dimension ? { valeur: p.dimension.valeur, unite: p.dimension.unite } : null, cout_eur_ht: p.cout_eur_ht, force_par_couplage: !!p.force_par_couplage }))
|
||||
const lignePostes = (liste) => liste.map((p) => `${p.typologie_choisie}${p.force_par_couplage ? '*' : ''} ${p.cout_eur_ht}`).join(' + ')
|
||||
|
||||
/** Compare une cellule (profil × mode × axe) à l'épingle ; rend les écarts champ par champ. */
|
||||
function compareAxe(profil, mode, axe, attendu, obtenu) {
|
||||
const ecarts = []
|
||||
const push = (champ, o, c) => ecarts.push({ profil, mode, axe, champ, origine: o, corrige: c })
|
||||
if (attendu.sous_total_eur_ht !== obtenu.sous_total_eur_ht) push('sous_total_eur_ht', attendu.sous_total_eur_ht, obtenu.sous_total_eur_ht)
|
||||
const po = JSON.stringify(attendu.postes)
|
||||
const pc = JSON.stringify(resumePostes(obtenu.postes))
|
||||
if (po !== pc) push('postes', lignePostes(attendu.postes), lignePostes(obtenu.postes))
|
||||
return ecarts
|
||||
}
|
||||
|
||||
export function calculeEcartsCorrectifs() {
|
||||
const ecarts = []
|
||||
const cellules = {}
|
||||
for (const profil of profils) {
|
||||
const p = pin.profils[profil.nom]
|
||||
const g = autonomie.noyau.selectionner(profil.inputs, options, postes, france_climat, { config: algo_config, coefficients: origine }, false)
|
||||
for (const axe of AXES) ecarts.push(...compareAxe(profil.nom, 'global', axe, p.global.axes[axe], g.axes[axe]))
|
||||
if (p.global.total_eur_ht !== g.total_eur_ht) ecarts.push({ profil: profil.nom, mode: 'global', axe: 'total', champ: 'total_eur_ht', origine: p.global.total_eur_ht, corrige: g.total_eur_ht })
|
||||
if (p.global.status !== g.status) ecarts.push({ profil: profil.nom, mode: 'global', axe: 'total', champ: 'status', origine: p.global.status, corrige: g.status })
|
||||
if (p.global.ecart_budget_pct !== g.ecart_budget_pct) ecarts.push({ profil: profil.nom, mode: 'global', axe: 'total', champ: 'ecart_budget_pct', origine: p.global.ecart_budget_pct, corrige: g.ecart_budget_pct })
|
||||
if (JSON.stringify(p.global.couplages) !== JSON.stringify(g.couplages)) ecarts.push({ profil: profil.nom, mode: 'global', axe: 'total', champ: 'couplages', origine: p.global.couplages, corrige: g.couplages })
|
||||
cellules[`${profil.nom} / global`] = { total: g.total_eur_ht, status: g.status, couplages: g.couplages, axes: Object.fromEntries(AXES.map((a) => [a, { sous_total: g.axes[a].sous_total_eur_ht, postes: resumePostes(g.axes[a].postes) }])) }
|
||||
|
||||
for (const axe of AXES) {
|
||||
const a = p.par_axe[axe]
|
||||
const r = MOTEURS[axe].calculer({ autodiag: profil.inputs, budget_axe: a.budget_axe, contexte: g.contexte }, { postes, options, algo_config, coefficients: origine }).sorties
|
||||
const obtenu = { sous_total_eur_ht: r.sous_total_eur_ht, postes: r.postes }
|
||||
ecarts.push(...compareAxe(profil.nom, 'par_axe', axe, a.axes[axe], obtenu))
|
||||
if (a.status !== r.status) ecarts.push({ profil: profil.nom, mode: 'par_axe', axe, champ: 'status', origine: a.status, corrige: r.status })
|
||||
if (JSON.stringify(a.couplages) !== JSON.stringify(r.couplages)) ecarts.push({ profil: profil.nom, mode: 'par_axe', axe, champ: 'couplages', origine: a.couplages, corrige: r.couplages })
|
||||
cellules[`${profil.nom} / par_axe / ${axe}`] = { budget_axe: a.budget_axe, sous_total: r.sous_total_eur_ht, status: r.status, couplages: r.couplages, postes: resumePostes(r.postes) }
|
||||
}
|
||||
}
|
||||
return { ecarts, cellules }
|
||||
}
|
||||
|
||||
const estCli = process.argv[1]?.replace(/\\/g, '/').endsWith('ecarts-correctifs.mjs')
|
||||
if (estCli) {
|
||||
const { ecarts, cellules } = calculeEcartsCorrectifs()
|
||||
console.log('| Profil | Mode | Axe | Champ | Origine (astro-pro) | Corrigé (ce paquet) |')
|
||||
console.log('|---|---|---|---|---|---|')
|
||||
const court = (v) => (Array.isArray(v) ? `${v.length} ligne(s)` : String(v))
|
||||
for (const e of ecarts) console.log(`| ${e.profil} | ${e.mode} | ${e.axe} | ${e.champ} | ${court(e.origine)} | ${court(e.corrige)} |`)
|
||||
if (ecarts.length === 0) console.log('| (aucun écart) | | | | | |')
|
||||
const i = process.argv.indexOf('--ecrire')
|
||||
if (i > 0) {
|
||||
const date = process.argv[i + 1] ?? new Date().toISOString().slice(0, 10)
|
||||
const cible = new URL(`ecarts-correctifs-${date}.json`, fx)
|
||||
writeFileSync(
|
||||
cible,
|
||||
JSON.stringify(
|
||||
{
|
||||
_commentaire: `Écarts voulus entre l'épingle d'origine (${PIN_ORIGINE}, code astro-pro) et ce paquet, coefficients d'origine, trois profils de référence, figés le ${date} par scripts/ecarts-correctifs.mjs. L'épingle d'origine n'est pas regénérée : elle porte les défauts corrigés (SPEC menu branches fiches §12 n° 1, 2, 3, 5). Toute cellule absente d'ici doit rester identique à l'origine ; un nouvel écart casse test/decoupage.test.mjs, et se refige sciemment, avec sa cause dans le commit.`,
|
||||
date,
|
||||
epingle_origine: PIN_ORIGINE,
|
||||
ecarts,
|
||||
cellules,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
'utf8',
|
||||
)
|
||||
console.log(`\nécrit : fixtures/ecarts-correctifs-${date}.json`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user