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:
@@ -0,0 +1,104 @@
|
||||
// Tests du paquet lui-même : version, schémas en phase, tables de coefficients
|
||||
// bien formées, pureté du code (aucune I/O, aucune horloge, aucun aléa dans
|
||||
// src/), et absence des anciennes constantes dupliquées (LOT P1.0 point 6).
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { VERSION, SCHEMAS, NOMS_TABLES, COEFFICIENTS, verifieTableCoefficients, MOTEURS } from '../dist/index.js'
|
||||
|
||||
const racine = fileURLToPath(new URL('..', import.meta.url))
|
||||
const lire = (rel) => readFileSync(join(racine, rel), 'utf8')
|
||||
|
||||
function fichiersTs(dossier) {
|
||||
const out = []
|
||||
for (const nom of readdirSync(dossier)) {
|
||||
const chemin = join(dossier, nom)
|
||||
if (statSync(chemin).isDirectory()) out.push(...fichiersTs(chemin))
|
||||
else if (nom.endsWith('.ts')) out.push(chemin)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** Lignes de code hors commentaires (// et /* *\/ sur une ligne) et hors chaînes de `note:`/`source:`. */
|
||||
function lignesDeCode(texte) {
|
||||
return texte
|
||||
.split('\n')
|
||||
.map((l) => l.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, ''))
|
||||
.filter((l) => l.trim() && !/^\s*\*/.test(l) && !/^\s*(note|source):\s*['"`]/.test(l))
|
||||
}
|
||||
|
||||
test('VERSION du code = version de package.json', () => {
|
||||
const pkg = JSON.parse(lire('package.json'))
|
||||
assert.equal(VERSION, pkg.version)
|
||||
assert.equal(pkg.name, '@transformer/moteurs')
|
||||
assert.deepEqual(pkg.dependencies ?? {}, {}, 'zéro dépendance de production')
|
||||
})
|
||||
|
||||
test('schemas/*.schema.json en phase avec src/commun/schemas.ts', () => {
|
||||
for (const table of NOMS_TABLES) {
|
||||
const json = JSON.parse(lire(`schemas/${table}.schema.json`))
|
||||
delete json.$schema
|
||||
assert.deepEqual(json, JSON.parse(JSON.stringify(SCHEMAS[table])), `${table}.schema.json diffère de SCHEMAS.${table} : relancer npm run build`)
|
||||
}
|
||||
})
|
||||
|
||||
test('les deux tables de coefficients sont complètes, sourcées et datées', () => {
|
||||
assert.deepEqual(verifieTableCoefficients(COEFFICIENTS), [])
|
||||
const origine = JSON.parse(lire('fixtures/coefficients-2026-05.json'))
|
||||
assert.deepEqual(verifieTableCoefficients(origine), [])
|
||||
for (const cle of Object.keys(COEFFICIENTS)) assert.ok(cle in origine, `fixture d'origine : clé ${cle} manquante`)
|
||||
})
|
||||
|
||||
test('six moteurs livrés, identifiants stables', () => {
|
||||
assert.deepEqual([...MOTEURS], ['commun.contexte', 'commun.geometrie', 'autonomie.eau', 'autonomie.energie', 'autonomie.alimentation', 'autonomie.integrateur'])
|
||||
})
|
||||
|
||||
test('pureté : src/ ne lit ni réseau, ni disque, ni horloge, ni DOM, ni aléa, ni console', () => {
|
||||
const interdits = [
|
||||
[/\bfetch\s*\(/, 'fetch'],
|
||||
[/\brequire\s*\(/, 'require'],
|
||||
[/from\s+['"]node:/, 'import node:'],
|
||||
[/from\s+['"](fs|path|os|http|https|child_process)['"]/, 'module Node'],
|
||||
[/\bprocess\./, 'process'],
|
||||
[/\bDate\.now\b|\bnew Date\b/, 'horloge'],
|
||||
[/\bMath\.random\b/, 'aléa'],
|
||||
[/\b(localStorage|sessionStorage|document|window)\b/, 'DOM'],
|
||||
[/\bconsole\./, 'console'],
|
||||
[/\bimport\s.*\.json['"]/, 'import JSON (les données s\'injectent)'],
|
||||
]
|
||||
const fautes = []
|
||||
for (const f of fichiersTs(join(racine, 'src'))) {
|
||||
const lignes = lignesDeCode(readFileSync(f, 'utf8'))
|
||||
for (const [re, nom] of interdits) {
|
||||
const l = lignes.find((x) => re.test(x))
|
||||
if (l) fautes.push(`${f.slice(racine.length)} : ${nom} — ${l.trim()}`)
|
||||
}
|
||||
}
|
||||
assert.deepEqual(fautes, [])
|
||||
})
|
||||
|
||||
test('anciennes constantes : plus aucune définition en double dans src/', () => {
|
||||
// Chaque grandeur existe UNE fois, dans coefficients.ts (LOT P1.0 point 6).
|
||||
const patrons = [
|
||||
[/ORIENTATION_FACTORS|DJU_DEFAULTS_BY_ZONE|FALLBACK_PVGIS|RENDEMENT_CAPTAGE|BESOIN_EAU_L_PERS_AN|PART_SUBSTITUABLE|BESOIN_ELEC_KWH_PERS_AN|ML_HAIE_PAR_PERS/, 'constante de module d\'origine'],
|
||||
[/\b0\.93\b|\b0\.78\b|\b0\.40\b/, 'facteurs d\'orientation d\'origine'],
|
||||
[/\b1550\b|\b1480\b|\b54750\b/, 'productible 1 550 / 1 480 ou besoin 54 750'],
|
||||
[/\(pluvio \/ 1000\) \* 0\.7\b/, 'rendement de captage 0,7 dans score_pluviometrie'],
|
||||
[/\b0\.85\b/, 'rendement 0,85'],
|
||||
]
|
||||
const fautes = []
|
||||
for (const f of fichiersTs(join(racine, 'src'))) {
|
||||
if (f.endsWith('coefficients.ts')) continue
|
||||
const lignes = lignesDeCode(readFileSync(f, 'utf8'))
|
||||
for (const [re, nom] of patrons) {
|
||||
const l = lignes.find((x) => re.test(x))
|
||||
if (l) fautes.push(`${f.slice(racine.length)} : ${nom} — ${l.trim()}`)
|
||||
}
|
||||
}
|
||||
assert.deepEqual(fautes, [])
|
||||
// Dans coefficients.ts, les anciennes valeurs n'apparaissent qu'en note (chaîne), jamais en valeur.
|
||||
const coefs = lignesDeCode(lire('src/commun/coefficients.ts')).join('\n')
|
||||
assert.ok(!/valeur:\s*0\.85\b/.test(coefs) && !/\b1550\b/.test(coefs) && !/nord:\s*0\.4\b/.test(coefs), 'coefficients.ts porte une valeur d\'origine')
|
||||
})
|
||||
Reference in New Issue
Block a user