c1c868622f
- motore-fisici.test.ts confrontava i tiers letti dal file di riferimento, non le costanti TIERS_* del codice: ventiquattro numeri senza custode. Ora identifica la tabella dagli argomenti dell'oracolo ma usa le costanti vere per calcolare, quindi una costante alterata non combacia più con nessun caso. - motore-caso-reale.test.ts: aggiunto il caso end-to-end mancante, un asse valido (Composizione Corporea, via due righe aggiunte al registro) e sei insufficienti — oggi la suite copriva solo "tutto insufficiente". - test-fisici.ts: commento in testa che dichiara le venti curve non collegate al motore (vocabolario delle curve senza nomi per loro, applicaCurva senza modo di ricevere età/sesso) — aggancio è lavoro del piano degli import, non "una modifica ai dati" come la spec lascia intendere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XhLbMQ1q7wHwJSykgXRwQF
92 lines
4.7 KiB
TypeScript
92 lines
4.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import * as F from '../../src/lib/longevity/motore/test-fisici';
|
|
|
|
const RIF = JSON.parse(
|
|
readFileSync(join(process.cwd(), 'tests/longevity/riferimento/riferimento.json'), 'utf8')
|
|
) as { casi: { fn: string; args: unknown[]; atteso: number }[] };
|
|
|
|
/** Ogni funzione dell'oracolo con la sua gemella in TypeScript. */
|
|
const COPPIE: [string, (...a: never[]) => number][] = [
|
|
['score_fat_percent', F.scoreGrassoPercento as never],
|
|
['score_muscle_percent', F.scoreMuscoloPercento as never],
|
|
['score_whr', F.scoreWhr as never],
|
|
['score_vo2max', F.scoreVo2max as never],
|
|
['score_spo2', F.scoreSpo2 as never],
|
|
['score_blood_pressure', F.scorePressione as never],
|
|
['score_hrr', F.scoreRecuperoCardiaco as never],
|
|
['score_handgrip', F.scoreHandgrip as never],
|
|
['score_pushup', F.scorePushup as never],
|
|
['score_flexed_arm_hang', F.scoreTrazioneIsometrica as never],
|
|
['score_sit_to_stand_1min', F.scoreSitToStand as never],
|
|
['score_plank', F.scorePlank as never],
|
|
['score_back_scratch', F.scoreBackScratch as never],
|
|
['score_shoulder_mobility_wt', F.scoreMobilitaSpalla as never],
|
|
['score_flamingo', F.scoreFlamingo as never],
|
|
['score_sit_and_reach', F.scoreSitAndReach as never],
|
|
['vo2max_from_step_test', F.vo2maxDaStepTest as never],
|
|
['vo2max_from_2km_walk', F.vo2maxDa2kmWalk as never],
|
|
['vo2max_from_mutt', F.vo2maxDaMutt as never],
|
|
['vo2max_from_milfit', F.vo2maxDaMilfit as never],
|
|
];
|
|
|
|
describe('curve dei test fisici, confrontate con l oracolo caso per caso', () => {
|
|
for (const [nomePython, fnTs] of COPPIE) {
|
|
it(`${nomePython} combacia su tutti i casi del riferimento`, () => {
|
|
const casi = RIF.casi.filter((c) => c.fn === nomePython);
|
|
expect(casi.length, `nessun caso per ${nomePython}: la griglia non lo copre`).toBeGreaterThan(0);
|
|
const divergenti: string[] = [];
|
|
for (const c of casi) {
|
|
const ottenuto = (fnTs as (...a: unknown[]) => number)(...c.args);
|
|
if (Math.abs(ottenuto - c.atteso) > 0.05) {
|
|
divergenti.push(`${nomePython}(${c.args.join(', ')}): atteso ${c.atteso}, ottenuto ${ottenuto}`);
|
|
}
|
|
}
|
|
expect(divergenti.slice(0, 5).join('\n')).toBe('');
|
|
});
|
|
}
|
|
|
|
it('i sollevamenti sul peso corporeo combaciano su tutte e tre le tabelle DEFINITE NEL CODICE', () => {
|
|
// Prima versione di questo test: passava alla funzione le tabelle lette dagli
|
|
// argomenti del riferimento (c.args), non le costanti TIERS_* definite nel
|
|
// codice — le sei tabelle non erano confrontate contro l'oracolo da nessuno.
|
|
// Qui si usano SOLO le costanti del modulo: se una venisse alterata, nessun
|
|
// caso combacerebbe piu' con gli argomenti del riferimento e il test cade.
|
|
const casi = RIF.casi.filter((c) => c.fn === 'score_bw_ratio_lift');
|
|
expect(casi.length).toBeGreaterThan(50);
|
|
|
|
const TABELLE: [string, [number, number][], [number, number][]][] = [
|
|
['bench', F.TIERS_BENCH_M, F.TIERS_BENCH_F],
|
|
['squat', F.TIERS_SQUAT_M, F.TIERS_SQUAT_F],
|
|
['row', F.TIERS_ROW_M, F.TIERS_ROW_F],
|
|
];
|
|
const perTabella: Record<string, number> = { bench: 0, squat: 0, row: 0 };
|
|
|
|
for (const c of casi) {
|
|
const [carico, peso, rip, tiersMRif, , sesso] = c.args as [number, number, number, unknown, unknown, 'M' | 'F'];
|
|
// Identifica QUALE tabella del codice questo caso sta esercitando confrontando
|
|
// gli argomenti del riferimento con le costanti vere — non il contrario.
|
|
const trovata = TABELLE.find(([, tm]) => JSON.stringify(tm) === JSON.stringify(tiersMRif));
|
|
expect(trovata, `nessuna tabella del codice combacia con gli argomenti del caso: carico ${carico} ${sesso}`).toBeDefined();
|
|
const [nome, tiersM, tiersF] = trovata!;
|
|
perTabella[nome]++;
|
|
|
|
const ottenuto = F.scoreSollevamentoSuPeso(carico, peso, rip, tiersM, tiersF, sesso);
|
|
expect(ottenuto, `${nome} carico ${carico} ${sesso}`).toBeCloseTo(c.atteso, 1);
|
|
}
|
|
// Tutte e tre le tabelle sono state esercitate davvero, non solo dichiarate.
|
|
expect(perTabella.bench).toBeGreaterThan(0);
|
|
expect(perTabella.squat).toBeGreaterThan(0);
|
|
expect(perTabella.row).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('le due funzioni escluse dal porting non esistono nel modulo', () => {
|
|
// Agilita: il cliente l'ha esclusa dallo score il 13/08 (scale non comparabili fra protocolli).
|
|
expect((F as Record<string, unknown>).scoreAgilita).toBeUndefined();
|
|
expect((F as Record<string, unknown>).scoreAgilityMs).toBeUndefined();
|
|
// score_generic_range_local: definita in fondo all'oracolo ma non chiamata da nessun peso.
|
|
expect((F as Record<string, unknown>).scoreGenericRangeLocal).toBeUndefined();
|
|
});
|
|
});
|