longevity: correggiCheckup e misureDellaSessione, sovrascrittura misure
Aggiunge la correzione di un check-up gia' salvato: aggiorna sessione e misure (cambiate/tolte/nuove), azzera la verifica sulle misure cambiate, ricalcola i punteggi della sessione e delle successive. Estratto fuoriRangeDi in misure.ts per non duplicare il calcolo min/max. Refs #12 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkWhSnSLvAyRCoWBuCpXvp
This commit is contained in:
@@ -9,8 +9,9 @@ import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { createLongevityDb, createIdentityDb } from '../../src/lib/longevity/db';
|
||||
import { seedRegistro, seedPesi, MODEL_VERSION } from '../../src/lib/longevity/registro';
|
||||
import { salvaCheckup, sessioniDi } from '../../src/lib/longevity/gestionale';
|
||||
import { misureDaVerificare } from '../../src/lib/longevity/misure';
|
||||
import { salvaCheckup, sessioniDi, correggiCheckup, misureDellaSessione } from '../../src/lib/longevity/gestionale';
|
||||
import { misureDaVerificare, verificaMisura } from '../../src/lib/longevity/misure';
|
||||
import { leggiScore } from '../../src/lib/longevity/motore';
|
||||
import { salvaDalForm, refertoDi } from '../../src/lib/longevity/vista';
|
||||
import {
|
||||
creaCliente, elencoClientiParam, clienteDaCodiceParam, etaDelClienteParam, apriFascicolo,
|
||||
@@ -170,6 +171,204 @@ describe('sessioniDi', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('misureDellaSessione', () => {
|
||||
it('null se la sessione non esiste; altrimenti le misure ordinate per asse come campiDelCheckup', () => {
|
||||
const db = dbPronto();
|
||||
expect(misureDellaSessione(db, 999999)).toBeNull();
|
||||
|
||||
const esito = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-26', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }, { test_id: 'f_handgrip_kg', valore_num: 45 }],
|
||||
});
|
||||
expect(esito.ok).toBe(true);
|
||||
if (!esito.ok) throw new Error(esito.errore);
|
||||
|
||||
const dettaglio = misureDellaSessione(db, esito.sessioneId);
|
||||
expect(dettaglio?.tipo).toBe('checkup');
|
||||
expect(dettaglio?.operatore).toBe('nicola');
|
||||
// 'Forza & Struttura' precede 'Composizione Corporea' nei pesi (PESI_ASSE, registro.ts):
|
||||
// stesso ordine che userebbe campiDelCheckup per raggruppare il modulo.
|
||||
expect(dettaglio?.misure.map((m) => m.test_id)).toEqual(['f_handgrip_kg', 'f_peso_kg']);
|
||||
expect(dettaglio?.misure[1]).toMatchObject({ etichetta: 'Peso corporeo', valore_num: 80, verifica: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe('correggiCheckup: sovrascrittura, nessuno storico delle misure (#12)', () => {
|
||||
it('corregge un valore: misura aggiornata, score della sessione ricalcolato; la successiva viene ricalcolata, la precedente resta intatta', () => {
|
||||
const db = dbPronto();
|
||||
const precedente = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-01', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
const target = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-10', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 82 }],
|
||||
});
|
||||
const successiva = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-20', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 83 }],
|
||||
});
|
||||
expect(precedente.ok && target.ok && successiva.ok).toBe(true);
|
||||
if (!precedente.ok || !target.ok || !successiva.ok) throw new Error('setup fallito');
|
||||
|
||||
const contaScore = (id: number) => (db.prepare(`SELECT COUNT(*) n FROM score WHERE sessione_id = ?`).get(id) as { n: number }).n;
|
||||
const primaPrecedente = contaScore(precedente.sessioneId);
|
||||
const primaTarget = contaScore(target.sessioneId);
|
||||
const primaSuccessiva = contaScore(successiva.sessioneId);
|
||||
|
||||
const esito = correggiCheckup(db, {
|
||||
sessioneId: target.sessioneId, data: '2026-09-10', eta: 38, operatore: 'donata',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 90 }],
|
||||
});
|
||||
expect(esito.ok).toBe(true);
|
||||
if (!esito.ok) throw new Error(esito.errore);
|
||||
expect(esito.sessioneId).toBe(target.sessioneId);
|
||||
|
||||
const riga = db.prepare(
|
||||
`SELECT valore_num FROM misure WHERE sessione_id = ? AND test_id = 'f_peso_kg'`
|
||||
).get(target.sessioneId) as { valore_num: number };
|
||||
expect(riga.valore_num).toBe(90);
|
||||
|
||||
const sessioneAggiornata = db.prepare(`SELECT operatore FROM sessioni WHERE id = ?`).get(target.sessioneId) as { operatore: string };
|
||||
expect(sessioneAggiornata.operatore).toBe('donata'); // l'operatore diventa chi corregge
|
||||
|
||||
// score ricalcolato (righe nuove in append): target e successiva sì, precedente no.
|
||||
expect(contaScore(target.sessioneId)).toBeGreaterThan(primaTarget);
|
||||
expect(contaScore(successiva.sessioneId)).toBeGreaterThan(primaSuccessiva);
|
||||
expect(contaScore(precedente.sessioneId)).toBe(primaPrecedente);
|
||||
});
|
||||
|
||||
it('campo svuotato cancella la misura; campo nuovo la inserisce con fonte manuale', () => {
|
||||
const db = dbPronto();
|
||||
const esito = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-26', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
expect(esito.ok).toBe(true);
|
||||
if (!esito.ok) throw new Error(esito.errore);
|
||||
|
||||
const corretto = correggiCheckup(db, {
|
||||
sessioneId: esito.sessioneId, data: '2026-09-26', eta: 38, operatore: 'nicola',
|
||||
// f_peso_kg non c'è più (campo svuotato nel form), f_handgrip_kg è nuovo.
|
||||
misure: [{ test_id: 'f_handgrip_kg', valore_num: 45 }],
|
||||
});
|
||||
expect(corretto.ok).toBe(true);
|
||||
|
||||
const righe = db.prepare(`SELECT test_id, fonte FROM misure WHERE sessione_id = ?`).all(esito.sessioneId) as
|
||||
{ test_id: string; fonte: string }[];
|
||||
expect(righe).toEqual([{ test_id: 'f_handgrip_kg', fonte: 'manuale' }]);
|
||||
});
|
||||
|
||||
it('valore invariato conserva la verifica; valore cambiato azzera la verifica e ricalcola fuori_range', () => {
|
||||
const db = dbPronto();
|
||||
const esito = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-26', eta: 38, operatore: 'nicola',
|
||||
// 500 kg è oltre il range di plausibilità (max 250): fuori range da subito.
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 500 }, { test_id: 'f_handgrip_kg', valore_num: 45 }],
|
||||
});
|
||||
expect(esito.ok).toBe(true);
|
||||
if (!esito.ok) throw new Error(esito.errore);
|
||||
|
||||
const misuraPeso = db.prepare(
|
||||
`SELECT id FROM misure WHERE sessione_id = ? AND test_id = 'f_peso_kg'`
|
||||
).get(esito.sessioneId) as { id: number };
|
||||
const conferma = verificaMisura(db, { misuraId: misuraPeso.id, esito: 'confermata', operatore: 'nicola' });
|
||||
expect(conferma.ok).toBe(true);
|
||||
|
||||
// Correzione: f_peso_kg invariato (500), f_handgrip_kg cambiato (45 -> 50).
|
||||
const corretto = correggiCheckup(db, {
|
||||
sessioneId: esito.sessioneId, data: '2026-09-26', eta: 38, operatore: 'nicola',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 500 }, { test_id: 'f_handgrip_kg', valore_num: 50 }],
|
||||
});
|
||||
expect(corretto.ok).toBe(true);
|
||||
|
||||
const pesoDopo = db.prepare(
|
||||
`SELECT verifica, fuori_range FROM misure WHERE id = ?`
|
||||
).get(misuraPeso.id) as { verifica: string | null; fuori_range: number };
|
||||
expect(pesoDopo.verifica).toBe('confermata'); // invariato: la verifica resta
|
||||
expect(pesoDopo.fuori_range).toBe(1);
|
||||
|
||||
const handgripDopo = db.prepare(
|
||||
`SELECT valore_num, fuori_range FROM misure WHERE sessione_id = ? AND test_id = 'f_handgrip_kg'`
|
||||
).get(esito.sessioneId) as { valore_num: number; fuori_range: number };
|
||||
expect(handgripDopo.valore_num).toBe(50);
|
||||
expect(handgripDopo.fuori_range).toBe(0); // 50 è nel range (0-100): ricalcolato, non ereditato
|
||||
});
|
||||
|
||||
it('cambio data che sposta la sessione dopo un\'altra: entrambe ricalcolate', () => {
|
||||
const db = dbPronto();
|
||||
const a = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-01', eta: 38, operatore: 'n',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
const b = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-15', eta: 38, operatore: 'n',
|
||||
misure: [{ test_id: 'f_handgrip_kg', valore_num: 45 }],
|
||||
});
|
||||
expect(a.ok && b.ok).toBe(true);
|
||||
if (!a.ok || !b.ok) throw new Error('setup fallito');
|
||||
|
||||
const contaScore = (id: number) => (db.prepare(`SELECT COUNT(*) n FROM score WHERE sessione_id = ?`).get(id) as { n: number }).n;
|
||||
const primaA = contaScore(a.sessioneId);
|
||||
const primaB = contaScore(b.sessioneId);
|
||||
|
||||
// Sposta A dopo B: la posizione minima (min fra vecchia e nuova data di A) resta
|
||||
// 2026-09-01, quindi la ricalcolata copre sia A sia B.
|
||||
const esito = correggiCheckup(db, {
|
||||
sessioneId: a.sessioneId, data: '2026-09-20', eta: 38, operatore: 'n',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
expect(esito.ok).toBe(true);
|
||||
|
||||
expect(contaScore(a.sessioneId)).toBeGreaterThan(primaA);
|
||||
expect(contaScore(b.sessioneId)).toBeGreaterThan(primaB);
|
||||
});
|
||||
|
||||
it('rifiuti: sessione inesistente, sessione questionario, nessuna misura, test non f_* — nessuna scrittura', () => {
|
||||
const db = dbPronto();
|
||||
|
||||
const inesistente = correggiCheckup(db, {
|
||||
sessioneId: 999999, data: '2026-09-26', eta: 38, operatore: 'n', misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
expect(inesistente.ok).toBe(false);
|
||||
|
||||
const q = salvaDalForm(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-01', eta: 38,
|
||||
risposte: { q_ore_sonno: 7.5, q_min_addorm: 15, q_risvegli: 0, q_riposato: 8, q_caffeina: 1, q_sonnolenza_diurna: 1 },
|
||||
consensoSanitario: 'Si', liberi: {},
|
||||
});
|
||||
expect(q.ok).toBe(true);
|
||||
if (!q.ok) throw new Error('setup questionario fallito');
|
||||
const suQuestionario = correggiCheckup(db, {
|
||||
sessioneId: q.sessioneId, data: '2026-09-01', eta: 38, operatore: 'n', misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
expect(suQuestionario.ok).toBe(false);
|
||||
|
||||
const checkup = salvaCheckup(db, {
|
||||
client_code: 'ISL-0001', data: '2026-09-26', eta: 38, operatore: 'n',
|
||||
misure: [{ test_id: 'f_peso_kg', valore_num: 80 }],
|
||||
});
|
||||
expect(checkup.ok).toBe(true);
|
||||
if (!checkup.ok) throw new Error('setup checkup fallito');
|
||||
|
||||
const nessunaMisura = correggiCheckup(db, {
|
||||
sessioneId: checkup.sessioneId, data: '2026-09-26', eta: 38, operatore: 'n', misure: [],
|
||||
});
|
||||
expect(nessunaMisura.ok).toBe(false);
|
||||
|
||||
const nonFisico = correggiCheckup(db, {
|
||||
sessioneId: checkup.sessioneId, data: '2026-09-26', eta: 38, operatore: 'n', misure: [{ test_id: 'q_ore_sonno', valore_num: 7 }],
|
||||
});
|
||||
expect(nonFisico.ok).toBe(false);
|
||||
|
||||
// Nessuna delle chiamate rifiutate ha scritto: la misura del checkup è ancora quella originale.
|
||||
const rigaFinale = db.prepare(
|
||||
`SELECT valore_num FROM misure WHERE sessione_id = ? AND test_id = 'f_peso_kg'`
|
||||
).get(checkup.sessioneId) as { valore_num: number };
|
||||
expect(rigaFinale.valore_num).toBe(80);
|
||||
});
|
||||
});
|
||||
|
||||
describe('anagrafica per il gestionale, versioni parametrizzate (testabili su :memory:, come codicePerUtente accanto a codicePerUtenteLoggato)', () => {
|
||||
it('elencoClientiParam ordina per cognome poi nome, e dice chi ha già un accesso', () => {
|
||||
const id = createIdentityDb(':memory:');
|
||||
|
||||
Reference in New Issue
Block a user