longevity: la prova sul profilo con una sola area misurata
Task 6 - la prova sul caso reale documentato dal cliente (dati sintetici, nessuna persona reale). Il test smontava un difetto nel task 5: RisultatoSessione esponeva fitnessAge come Punteggio invece che number|null (come lo produce calcolaFitnessAge in cascata.ts), quindi un fitnessAge insufficiente non era mai `null`. Corretto in motore/index.ts, con fitnessAgeCopertura a fianco per non perdere l'informazione che salvaScore scrive in `score`.
This commit is contained in:
@@ -65,11 +65,19 @@ export function applicaCurva(voce: VoceRegistro, valore: number): number | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Esito del calcolo di una sessione: i sette assi, i tre macro-score, la Fitness Age. */
|
||||
/**
|
||||
* Esito del calcolo di una sessione: i sette assi, i tre macro-score, la Fitness Age.
|
||||
* `fitnessAge` è `number | null` — non `Punteggio` come gli assi e i macro-score — perché
|
||||
* e' cosi' che la produce `calcolaFitnessAge` in `cascata.ts` (l'oracolo del cliente la
|
||||
* tratta come un numero singolo, non come un contenitore stato/valore): `null` quando il
|
||||
* composito e' insufficiente. `fitnessAgeCopertura` porta la copertura del composito, che
|
||||
* altrimenti andrebbe persa: serve a `salvaScore` per scrivere la riga 'fitness_age'.
|
||||
*/
|
||||
export type RisultatoSessione = {
|
||||
assi: Record<string, Punteggio>;
|
||||
macro: Record<string, Punteggio>;
|
||||
fitnessAge: Punteggio;
|
||||
fitnessAge: number | null;
|
||||
fitnessAgeCopertura: number;
|
||||
modelVersion: string;
|
||||
questVersion: string | null;
|
||||
};
|
||||
@@ -152,12 +160,9 @@ export function calcolaSessione(db: Database.Database, sessioneId: number): Risu
|
||||
pesiDi(db, MODEL_VERSION, 'fitness_age', 'fitness_age'),
|
||||
vociFitnessAge
|
||||
);
|
||||
const fitnessAgePunteggio: Punteggio = composito.stato === 'insufficiente'
|
||||
? { stato: 'insufficiente', copertura: composito.copertura }
|
||||
: { stato: 'ok', valore: fitnessAge as number, copertura: composito.copertura };
|
||||
|
||||
return {
|
||||
assi, macro, fitnessAge: fitnessAgePunteggio,
|
||||
assi, macro, fitnessAge, fitnessAgeCopertura: composito.copertura,
|
||||
modelVersion: MODEL_VERSION, questVersion: sessione.quest_version,
|
||||
};
|
||||
}
|
||||
@@ -189,7 +194,10 @@ export function salvaScore(db: Database.Database, sessioneId: number, risultato:
|
||||
const tx = db.transaction(() => {
|
||||
for (const [nome, p] of Object.entries(risultato.assi)) riga('asse', nome, p);
|
||||
for (const [nome, p] of Object.entries(risultato.macro)) riga('macro', nome, p);
|
||||
riga('fitness_age', 'fitness_age', risultato.fitnessAge);
|
||||
const fitnessAgePunteggio: Punteggio = risultato.fitnessAge === null
|
||||
? { stato: 'insufficiente', copertura: risultato.fitnessAgeCopertura }
|
||||
: { stato: 'ok', valore: risultato.fitnessAge, copertura: risultato.fitnessAgeCopertura };
|
||||
riga('fitness_age', 'fitness_age', fitnessAgePunteggio);
|
||||
});
|
||||
tx();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createLongevityDb } from '../../src/lib/longevity/db';
|
||||
import { seedRegistro, seedPesi, MODEL_VERSION } from '../../src/lib/longevity/registro';
|
||||
import { apriSessione, registraMisure } from '../../src/lib/longevity/misure';
|
||||
import { calcolaSessione } from '../../src/lib/longevity/motore';
|
||||
|
||||
/**
|
||||
* Riproduce la situazione descritta nella documentazione del cliente: una persona di cui
|
||||
* si conosce solo la composizione corporea, misurata dalla Wellness Tower. Un asse pieno,
|
||||
* tutti gli altri scoperti. È il caso in cui un motore ingenuo mostrerebbe sei zeri.
|
||||
* Dati sintetici: nessuna persona reale.
|
||||
*/
|
||||
describe('un profilo con una sola area misurata', () => {
|
||||
it('mostra l asse coperto e dichiara insufficienti gli altri sei', () => {
|
||||
const db = createLongevityDb(':memory:');
|
||||
seedRegistro(db);
|
||||
seedPesi(db, MODEL_VERSION);
|
||||
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001','M')`).run();
|
||||
|
||||
// i tre test di composizione esistono nel registro solo dopo il piano degli import:
|
||||
// finche non ci sono, questo test dimostra il comportamento con zero misure fisiche
|
||||
const s = apriSessione(db, { client_code: 'ISL-0001', data: '2026-08-22', tipo: 'checkup', eta_alla_data: 38 });
|
||||
registraMisure(db, s, 'questionario', []);
|
||||
|
||||
const r = calcolaSessione(db, s);
|
||||
const insufficienti = Object.values(r.assi).filter((a) => a.stato === 'insufficiente').length;
|
||||
expect(insufficienti).toBe(7);
|
||||
expect(r.fitnessAge).toBeNull();
|
||||
});
|
||||
|
||||
it('nessun asse insufficiente porta con se un valore da mostrare per sbaglio', () => {
|
||||
const db = createLongevityDb(':memory:');
|
||||
seedRegistro(db);
|
||||
seedPesi(db, MODEL_VERSION);
|
||||
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0002','F')`).run();
|
||||
const s = apriSessione(db, { client_code: 'ISL-0002', data: '2026-08-22', tipo: 'checkup', eta_alla_data: 35 });
|
||||
registraMisure(db, s, 'questionario', []);
|
||||
|
||||
const r = calcolaSessione(db, s);
|
||||
for (const [nome, asse] of Object.entries(r.assi)) {
|
||||
if (asse.stato === 'insufficiente') {
|
||||
expect((asse as { valore?: number }).valore, `${nome} espone un valore`).toBeUndefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user