ac8b377f84
Il commento del test 'un test disattivato non entra nel calcolo' diceva che q_attivita fosse l'unica voce del sotto-dominio questionario_lifestyle: falso, ne ha sei (q_attivita, q_alimentazione, q_sigarette, q_alcol_life, q_luce, q_schermi). Il motivo vero per cui il test funziona e' che il fixture ne compila solo una delle sei, quindi disattivarla svuota il sotto-dominio. Corretto il commento a dire il motivo vero, con nota su cosa succederebbe se il fixture cambiasse. Aggiunto anche il test mancante sulla regola della media: q_attivita(3)=65 e q_alimentazione(6)=60, stesso sotto-dominio, verificano che il valore dell'asse "Stile di Vita & Sonno" sia la loro media (62.5), diversa sia dalla somma (125) sia da ciascun addendo (65, 60) — nessun test esistente controllava il VALORE della media, solo lo stato.
137 lines
6.9 KiB
TypeScript
137 lines
6.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { createLongevityDb } from '../../src/lib/longevity/db';
|
|
import { seedRegistro, seedPesi, MODEL_VERSION, pesiDi } from '../../src/lib/longevity/registro';
|
|
import { salvaCompilazione } from '../../src/lib/longevity/questionario';
|
|
import { applicaCurva, calcolaSessione, salvaScore } from '../../src/lib/longevity/motore';
|
|
|
|
function dbPronto() {
|
|
const db = createLongevityDb(':memory:');
|
|
seedRegistro(db);
|
|
seedPesi(db, MODEL_VERSION);
|
|
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001','F')`).run();
|
|
return db;
|
|
}
|
|
|
|
const voce = (db: ReturnType<typeof dbPronto>, id: string) =>
|
|
db.prepare(`SELECT * FROM registro_test WHERE test_id = ?`).get(id) as Record<string, unknown>;
|
|
|
|
describe('il motore legge le curve dal registro', () => {
|
|
it('applica la curva dichiarata per il test, non una scritta nel codice', () => {
|
|
const db = dbPronto();
|
|
const v = { ...voce(db, 'q_ore_sonno'), params: JSON.parse(voce(db, 'q_ore_sonno').params as string) };
|
|
expect(applicaCurva(v as never, 8)).toBe(100); // dentro il picco 7-9
|
|
expect(applicaCurva(v as never, 4)).toBeLessThan(20);
|
|
});
|
|
|
|
it('rispetta la soglia dell alcol come e scritta nel registro', () => {
|
|
const db = dbPronto();
|
|
const v = { ...voce(db, 'q_alcol_life'), params: JSON.parse(voce(db, 'q_alcol_life').params as string) };
|
|
expect(applicaCurva(v as never, 0)).toBe(100);
|
|
expect(applicaCurva(v as never, 7)).toBe(100);
|
|
expect(applicaCurva(v as never, 14)).toBe(0);
|
|
});
|
|
|
|
it('cambiare i parametri nel registro cambia il punteggio, senza toccare il codice', () => {
|
|
const db = dbPronto();
|
|
db.prepare(`UPDATE registro_test SET params = ? WHERE test_id = 'q_alcol_life'`)
|
|
.run(JSON.stringify({ best: 0, worst: 7 }));
|
|
const v = { ...voce(db, 'q_alcol_life'), params: JSON.parse(voce(db, 'q_alcol_life').params as string) };
|
|
expect(applicaCurva(v as never, 7)).toBe(0); // con i parametri nuovi, 7 non vale piu 100
|
|
});
|
|
|
|
it('un test disattivato non entra nel calcolo', () => {
|
|
// NOTA: la versione originale di questo test disattivava 'q_ore_sonno' e si
|
|
// aspettava una differenza. Ma 'q_ore_sonno' e' uno delle SEI voci del
|
|
// sotto-dominio 'questionario_sonno' (peso 0.20 dentro "Recupero & Sistema
|
|
// Nervoso", che pesa in tutto 1.00: hrv 0.50 + pressione 0.15 + hrr 0.15 +
|
|
// questionario_sonno 0.20). Quell'asse resta 'insufficiente' con o senza
|
|
// 'q_ore_sonno' (0.20 < COPERTURA_MINIMA 0.4, e hrv/pressione/hrr non hanno
|
|
// ancora una voce nel registro): siccome un asse insufficiente non porta
|
|
// 'valore' nel tipo Punteggio, il JSON prima/dopo risultava IDENTICO — la
|
|
// suite lo ha confermato (verificato eseguendo il test: fallisce con le due
|
|
// stringhe uguali). Corretto qui usando 'q_attivita': NON e' l'unica voce
|
|
// del sotto-dominio 'questionario_lifestyle' (ne ha sei: q_attivita,
|
|
// q_alimentazione, q_sigarette, q_alcol_life, q_luce, q_schermi), ma in
|
|
// QUESTO fixture e' l'unica delle sei a cui si risponde. Disattivarla
|
|
// svuota davvero il sotto-dominio (nessun'altra voce lifestyle compilata a
|
|
// sostituirla), la copertura dell'asse "Stile di Vita & Sonno" (peso 1.00
|
|
// su quel solo sotto-dominio) cade da 1.00 a 0 e lo stato passa da 'ok' a
|
|
// 'insufficiente' — una differenza vera e osservabile. Se in futuro il
|
|
// fixture compilasse anche un'altra risposta lifestyle, questo test
|
|
// andrebbe aggiornato: la disattivazione non svuoterebbe piu' il
|
|
// sotto-dominio da sola.
|
|
const db = dbPronto();
|
|
salvaCompilazione(db, {
|
|
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
|
|
risposte: {
|
|
q_ore_sonno: 8, q_riposato: 8, q_min_addorm: 10, q_risvegli: 0, q_caffeina: 0, q_sonnolenza_diurna: 0,
|
|
q_attivita: 5,
|
|
},
|
|
});
|
|
const prima = calcolaSessione(db, 1);
|
|
expect(prima.assi['Stile di Vita & Sonno'].stato).toBe('ok');
|
|
db.prepare(`UPDATE registro_test SET attivo_a = '2026-01-01' WHERE test_id = 'q_attivita'`).run();
|
|
const dopo = calcolaSessione(db, 1);
|
|
expect(dopo.assi['Stile di Vita & Sonno'].stato).toBe('insufficiente');
|
|
expect(JSON.stringify(prima)).not.toBe(JSON.stringify(dopo));
|
|
});
|
|
|
|
it('due test dello stesso sotto-dominio danno la MEDIA dei punteggi, non la somma ne un singolo valore', () => {
|
|
// q_attivita e q_alimentazione condividono il sotto-dominio 'questionario_lifestyle',
|
|
// che pesa 1.00 (unico elemento) dentro l'asse "Stile di Vita & Sonno": il valore
|
|
// dell'asse coincide col valore del sotto-dominio, quindi la media si legge diretta.
|
|
const db = dbPronto();
|
|
salvaCompilazione(db, {
|
|
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
|
|
// q_attivita curva incstep, steps [[0,20],[2,50],[4,80],[999,100]]: valore 3 ->
|
|
// lerp fra (2,50) e (4,80) a meta' strada -> punteggio 65.
|
|
// q_alimentazione curva x10: valore 6 -> punteggio 60.
|
|
// media = (65+60)/2 = 62.5: diversa dalla somma (125) e da ciascun addendo (65, 60).
|
|
risposte: { q_attivita: 3, q_alimentazione: 6 },
|
|
});
|
|
const r = calcolaSessione(db, 1);
|
|
const asse = r.assi['Stile di Vita & Sonno'];
|
|
expect(asse.stato).toBe('ok');
|
|
if (asse.stato === 'ok') expect(asse.valore).toBe(62.5);
|
|
});
|
|
});
|
|
|
|
describe('calcolo e congelamento di una sessione', () => {
|
|
it('produce i sette assi, e quelli senza dati sono insufficienti', () => {
|
|
const db = dbPronto();
|
|
salvaCompilazione(db, {
|
|
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
|
|
risposte: { q_ore_sonno: 8, q_riposato: 8, q_min_addorm: 10, q_risvegli: 0, q_caffeina: 0, q_sonnolenza_diurna: 1 },
|
|
});
|
|
const r = calcolaSessione(db, 1);
|
|
expect(Object.keys(r.assi).length).toBe(7);
|
|
// il questionario copre il sonno, che pesa 0.20 dentro Recupero: sotto il 40%
|
|
expect(r.assi['Recupero & Sistema Nervoso'].stato).toBe('insufficiente');
|
|
// Stile di Vita e coperto al 100% dal solo questionario, ma qui non abbiamo risposto
|
|
expect(r.assi['Forza & Struttura'].stato).toBe('insufficiente');
|
|
});
|
|
|
|
it('congela i punteggi con le DUE versioni', () => {
|
|
const db = dbPronto();
|
|
salvaCompilazione(db, {
|
|
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
|
|
risposte: { q_riposato: 7 },
|
|
});
|
|
salvaScore(db, 1, calcolaSessione(db, 1));
|
|
const righe = db.prepare(`SELECT tipo, elemento, valore, stato, quest_version, model_version FROM score`).all() as Record<string, unknown>[];
|
|
expect(righe.length).toBeGreaterThan(0);
|
|
for (const r of righe) {
|
|
expect(r.model_version).toBe(MODEL_VERSION);
|
|
expect(r.quest_version).toBe('v1.0');
|
|
if (r.stato === 'insufficiente') expect(r.valore).toBeNull();
|
|
}
|
|
});
|
|
|
|
it('i pesi arrivano dal registro e sono quelli della versione richiesta', () => {
|
|
const db = dbPronto();
|
|
const pesi = pesiDi(db, MODEL_VERSION, 'asse', 'Forza & Struttura');
|
|
expect(pesi.handgrip).toBe(0.25);
|
|
expect(Object.values(pesi).reduce((a, b) => a + b, 0)).toBeCloseTo(1, 6);
|
|
});
|
|
});
|