Files
InsanityLab-web/apps/platforms/tests/longevity/motore-fisici-gruppi.test.ts
T
Adriano f57a07a964 longevity: test unitari e di sessione per la regola dei gruppi (#13)
motore-fisici-gruppi.test.ts (nuovo): un caso per ogni alternativa che da
punteggio (handgrip/spinta/arti_inferiori/vo2max/hrr/flamingo/back_scratch,
media dx/sx e storico), un lato solo -> nessun punteggio, recenza fra
alternative (pushup/panca in entrambe le direzioni), alternativa piu recente
ma incompleta esclusa (sit-to-stand senza altezza), i test "solo registrati"
non producono chiavi.

motore-fisici-sessione.test.ts: due nuovi casi end-to-end su DB (due sessioni,
due test diversi per la stessa sotto-metrica) che confermano che lo score usa
il test piu recente, in entrambe le direzioni.

Refs #13

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-27 22:48:07 +02:00

180 lines
7.8 KiB
TypeScript

// La regola dei gruppi (issue #13, manuale batteria test di Nicola del 27/09/2026): per
// alcune sotto-metriche (handgrip, spinta, arti_inferiori, vo2max, hrr, flamingo,
// back_scratch) sottoDominiFisici sceglie fra piu' alternative COMPLETE quella la cui
// misura piu' recente vince (`recenza`, opzionale). Questo file copre la regola da sola,
// con `sottoDominiFisici` chiamata direttamente (mappe di misure a mano, non un DB): i
// valori attesi si calcolano con le STESSE funzioni di test-fisici.ts (gia' verificate
// contro l'oracolo in motore-fisici.test.ts), mai a mano.
import { describe, it, expect } from 'vitest';
import { sottoDominiFisici } from '../../src/lib/longevity/motore/fisici';
import * as F from '../../src/lib/longevity/motore/test-fisici';
describe('un caso per ogni alternativa che da punteggio', () => {
it('handgrip: media dx/sx (manuale)', () => {
const m = new Map([['f_handgrip_dx_kg', 45], ['f_handgrip_sx_kg', 51]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.handgrip).toBe(F.scoreHandgrip(48, 38, 'M')); // media (45+51)/2 = 48
});
it('handgrip: storico f_handgrip_kg (ancora letto)', () => {
const m = new Map([['f_handgrip_kg', 44]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.handgrip).toBe(F.scoreHandgrip(44, 38, 'M'));
});
it('spinta: panca con bilanciere (carico, ripetizioni, peso)', () => {
const m = new Map([['f_panca_bil_carico_kg', 60], ['f_panca_bil_rip', 8], ['f_peso_kg', 80]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.spinta).toBe(F.scoreSollevamentoSuPeso(60, 80, 8, F.TIERS_BENCH_M, F.TIERS_BENCH_F, 'M'));
});
it('spinta: push-up (invariato)', () => {
const m = new Map([['f_pushup_rip', 24]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.spinta).toBe(F.scorePushup(24, 38, 'M'));
});
it('arti_inferiori: sit-to-stand (ripetizioni, eta, sesso, bmi da peso/altezza)', () => {
const m = new Map([['f_sit_to_stand_rip', 14], ['f_peso_kg', 65], ['f_altezza_cm', 160]]);
const r = sottoDominiFisici(m, 65, 'F');
const bmi = 65 / (160 / 100) ** 2;
expect(r.arti_inferiori).toBe(F.scoreSitToStand(14, 65, 'F', bmi));
});
it('vo2max: MILFIT (potenza, peso)', () => {
const m = new Map([['f_milfit_watt', 150], ['f_peso_kg', 80]]);
const r = sottoDominiFisici(m, 40, 'M');
const vo2 = F.vo2maxDaMilfit(150, 80);
expect(r.vo2max).toBe(F.scoreVo2max(vo2, 40, 'M'));
});
it('vo2max: camminata 2 km UKK (tempo, fc, eta, bmi)', () => {
const m = new Map([
['f_walk2km_min', 18], ['f_walk2km_fc_bpm', 130], ['f_peso_kg', 80], ['f_altezza_cm', 180],
]);
const r = sottoDominiFisici(m, 40, 'M');
const bmi = 80 / (180 / 100) ** 2;
const vo2 = F.vo2maxDa2kmWalk(18, 130, 40, bmi);
expect(r.vo2max).toBe(F.scoreVo2max(vo2, 40, 'M'));
});
it('hrr: MUTT (FC max - FC dopo 1 minuto, manuale)', () => {
const m = new Map([['f_mutt_fc_max_bpm', 180], ['f_mutt_fc_1min_bpm', 140]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.hrr).toBe(F.scoreRecuperoCardiaco(40)); // calo = 180 - 140
});
it('hrr: storico f_hrr_1min_bpm (gia un calo, ancora letto)', () => {
const m = new Map([['f_hrr_1min_bpm', 18]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.hrr).toBe(F.scoreRecuperoCardiaco(18));
});
it('flamingo: media dx/sx occhi aperti (manuale)', () => {
const m = new Map([['f_flamingo_oa_dx', 3], ['f_flamingo_oa_sx', 5]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.flamingo).toBe(F.scoreFlamingo(4)); // media (3+5)/2 = 4
});
it('flamingo: storico f_flamingo_cadute (ancora letto)', () => {
const m = new Map([['f_flamingo_cadute', 6]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.flamingo).toBe(F.scoreFlamingo(6));
});
it('back_scratch: media dx/sx (manuale)', () => {
const m = new Map([['f_back_scratch_dx_cm', -8], ['f_back_scratch_sx_cm', -4]]);
const r = sottoDominiFisici(m, 45, 'F');
expect(r.back_scratch).toBe(F.scoreBackScratch(-6, 45, 'F')); // media (-8-4)/2 = -6
});
it('back_scratch: storico f_back_scratch_cm (ancora letto)', () => {
const m = new Map([['f_back_scratch_cm', -10]]);
const r = sottoDominiFisici(m, 45, 'F');
expect(r.back_scratch).toBe(F.scoreBackScratch(-10, 45, 'F'));
});
it('la cascata del vo2max per una sessione con solo f_vo2max resta uguale', () => {
const m = new Map([['f_vo2max', 45]]);
const r = sottoDominiFisici(m, 40, 'M');
expect(r.vo2max).toBe(F.scoreVo2max(45, 40, 'M'));
});
});
describe('un lato solo, senza storico, non da punteggio', () => {
it('handgrip solo destro: nessun punteggio (nessuna media possibile, nessuno storico)', () => {
const m = new Map([['f_handgrip_dx_kg', 45]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.handgrip).toBeUndefined();
});
it('flamingo occhi aperti solo sinistro: nessun punteggio', () => {
const m = new Map([['f_flamingo_oa_sx', 4]]);
const r = sottoDominiFisici(m, 38, 'M');
expect(r.flamingo).toBeUndefined();
});
it('back_scratch solo sinistro: nessun punteggio', () => {
const m = new Map([['f_back_scratch_sx_cm', -6]]);
const r = sottoDominiFisici(m, 45, 'F');
expect(r.back_scratch).toBeUndefined();
});
});
describe('la regola dei gruppi sceglie per recenza, non solo per ordine di priorita', () => {
it('pushup a gennaio + panca a marzo: vale panca (piu recente)', () => {
const m = new Map([
['f_pushup_rip', 24], ['f_panca_bil_carico_kg', 60], ['f_panca_bil_rip', 8], ['f_peso_kg', 80],
]);
// recenza: posizione 0 = piu recente. Panca (marzo) piu recente del pushup (gennaio).
const recenza = new Map([
['f_panca_bil_carico_kg', 0], ['f_panca_bil_rip', 0], ['f_pushup_rip', 1],
]);
const r = sottoDominiFisici(m, 38, 'M', recenza);
expect(r.spinta).toBe(F.scoreSollevamentoSuPeso(60, 80, 8, F.TIERS_BENCH_M, F.TIERS_BENCH_F, 'M'));
});
it('panca a gennaio + pushup a marzo: vale pushup (piu recente) — il contrario del caso sopra', () => {
const m = new Map([
['f_pushup_rip', 24], ['f_panca_bil_carico_kg', 60], ['f_panca_bil_rip', 8], ['f_peso_kg', 80],
]);
const recenza = new Map([
['f_pushup_rip', 0], ['f_panca_bil_carico_kg', 1], ['f_panca_bil_rip', 1],
]);
const r = sottoDominiFisici(m, 38, 'M', recenza);
expect(r.spinta).toBe(F.scorePushup(24, 38, 'M'));
});
it('alternativa incompleta ma piu recente (sit-to-stand senza altezza) non entra: vale lo squat precedente', () => {
const m = new Map([
['f_sit_to_stand_rip', 14], // manca f_altezza_cm: bmi non calcolabile, alternativa incompleta
['f_squat_carico_kg', 100], ['f_squat_rip', 5], ['f_peso_kg', 80],
]);
// sit-to-stand risulterebbe il piu recente SE fosse completo — ma non lo e', quindi
// resta escluso dal gruppo a prescindere dalla recenza (regola: solo le alternative
// complete entrano).
const recenza = new Map([
['f_sit_to_stand_rip', 0], ['f_squat_carico_kg', 1], ['f_squat_rip', 1],
]);
const r = sottoDominiFisici(m, 65, 'M', recenza);
expect(r.arti_inferiori).toBe(F.scoreSollevamentoSuPeso(100, 80, 5, F.TIERS_SQUAT_M, F.TIERS_SQUAT_F, 'M'));
});
});
describe('i test "solo registrati" (nessuna tabella nel manuale) non producono chiavi', () => {
it('panca manubri, trazioni, plank ginocchia, 6MWT, flamingo occhi chiusi: nessuna chiave nel risultato', () => {
const m = new Map([
['f_panca_man_carico_kg', 12], ['f_panca_man_rip', 10],
['f_trazioni_rip', 8],
['f_plank_ginocchia_sec', 90],
['f_6mwt_m', 550],
['f_flamingo_oc_dx', 10], ['f_flamingo_oc_sx', 12],
['f_mutt_fc_2min_bpm', 110],
['f_fc_riposo_bpm', 60],
]);
const r = sottoDominiFisici(m, 38, 'M');
// nessuna di queste misure alimenta una funzione di scoring: il risultato resta {}
expect(r).toEqual({});
});
});