From af8afebba82dd5f8647ab29c0d115a1922ced9c4 Mon Sep 17 00:00:00 2001 From: "Adriano (goldrake)" Date: Sat, 26 Sep 2026 12:40:31 +0000 Subject: [PATCH] longevity: misureValideAlla, spareggio sulla riga e nota sulla conferma dei fuori range Refs #1 Co-Authored-By: Claude Sonnet 5 --- .../src/lib/longevity/motore/index.ts | 25 +++++++++++++------ .../longevity/motore-fisici-sessione.test.ts | 14 +++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/platforms/src/lib/longevity/motore/index.ts b/apps/platforms/src/lib/longevity/motore/index.ts index c3a1d99..2e072ad 100644 --- a/apps/platforms/src/lib/longevity/motore/index.ts +++ b/apps/platforms/src/lib/longevity/motore/index.ts @@ -144,15 +144,21 @@ export const FINESTRA_GIORNI: number | null = null; * questionario del giorno D, e viceversa una sessione del giorno D-1 non vede nulla di ciò * che accadrà dopo di lei. * - * "Più recente" = data della sessione DESC, poi id DESC (a parità di data, l'inserimento - * più tardo vince) — stesso criterio di `leggiScore`. A parità di sessione (una misura - * ri-registrata più volte nella stessa sessione, oggi non capita ma non è vietato) vince - * comunque la più recente per `id` di riga in `misure`, perché la query è ordinata anche - * su quello implicitamente tramite l'ordine della sessione. + * "Più recente" = data della sessione DESC, poi id di sessione DESC (a parità di data, + * l'inserimento più tardo vince) — stesso criterio di `leggiScore`. A parità di SESSIONE + * (due misure dello stesso test_id nella stessa sessione: `registraMisure` non lo vieta, + * es. una correzione manuale dopo un refuso) l'ORDER BY aggiunge un terzo criterio + * esplicito, `m.id` di riga in `misure` DESC: vince quella scritta dopo. Senza questo + * terzo criterio le due righe uscirebbero in un ordine NON definito da SQL (nessuna delle + * due chiavi precedenti le distingue), non "implicito" come diceva una versione precedente + * di questo commento — un ordine che sembra stabile per come SQLite le restituisce oggi, + * ma su cui non si può contare. * * Stessa esclusione di `fuori_range = 1` che c'era prima nella lettura diretta (§9 della * spec): una misura fuori dal range dichiarato non entra, né dalla sessione corrente né da - * una precedente. + * una precedente. La colonna della conferma non esiste ancora (§12 della spec, issue #7): + * quando ci sarà, questo filtro andrà rilassato per lasciar passare le misure confermate, + * non tolto del tutto. */ export function misureValideAlla(db: Database.Database, sessioneId: number): Map { const sessione = db.prepare( @@ -166,13 +172,16 @@ export function misureValideAlla(db: Database.Database, sessioneId: number): Map JOIN sessioni s2 ON s2.id = m.sessione_id WHERE m.client_code = ? AND m.fuori_range = 0 AND m.valore_num IS NOT NULL AND (s2.data < ? OR (s2.data = ? AND s2.id <= ?)) - ORDER BY s2.data DESC, s2.id DESC` + ORDER BY s2.data DESC, s2.id DESC, m.id DESC` ).all(sessione.client_code, sessione.data, sessione.data, sessione.id) as { test_id: string; valore_num: number; data: string }[]; const mappa = new Map(); for (const r of righe) { - if (mappa.has(r.test_id)) continue; // gia' trovato un valore piu' recente per questo test_id + // gia' trovato un valore per questo test_id: e' il piu' recente, per data di sessione, + // poi id di sessione, poi (spareggio) id di riga in misure — tutti e tre espliciti + // nell'ORDER BY qui sopra, la prima riga per test_id in questo ciclo e' sempre quella giusta. + if (mappa.has(r.test_id)) continue; if (FINESTRA_GIORNI !== null) { const giorni = (Date.parse(sessione.data) - Date.parse(r.data)) / 86_400_000; if (giorni > FINESTRA_GIORNI) continue; diff --git a/apps/platforms/tests/longevity/motore-fisici-sessione.test.ts b/apps/platforms/tests/longevity/motore-fisici-sessione.test.ts index 46115c9..b67c909 100644 --- a/apps/platforms/tests/longevity/motore-fisici-sessione.test.ts +++ b/apps/platforms/tests/longevity/motore-fisici-sessione.test.ts @@ -198,6 +198,20 @@ describe('un check-up e un questionario sono sessioni separate, ma un cliente e expect(sd).not.toHaveProperty('questionario_lifestyle'); }); + it('due misure dello stesso test_id nella stessa sessione: vince quella inserita dopo', () => { + // registraMisure non vieta di scrivere due volte lo stesso test_id nella stessa + // sessione (es. una correzione manuale dopo un refuso di battitura). data di sessione + // e id di sessione sono identici per le due righe: senza uno spareggio esplicito su + // m.id (la riga in `misure`), l'ordine fra le due sarebbe indefinito. + const db = dbPronto('ISL-0023', 'M'); + const s = apriSessione(db, { client_code: 'ISL-0023', data: '2026-08-05', tipo: 'checkup', eta_alla_data: 38 }); + registraMisure(db, s, 'manuale', [{ test_id: 'f_grasso_pct', valore_num: 20 }]); // scritta per prima, refuso + registraMisure(db, s, 'manuale', [{ test_id: 'f_grasso_pct', valore_num: 12.8 }]); // corretta subito dopo + + const misurate = misureValideAlla(db, s); + expect(misurate.get('f_grasso_pct')).toBe(12.8); // l'ultima scritta, non la prima + }); + it('una misura fuori range viene ignorata e sostituita dalla precedente misura valida', () => { const db = dbPronto('ISL-0022', 'M'); const s1 = apriSessione(db, { client_code: 'ISL-0022', data: '2026-08-05', tipo: 'checkup', eta_alla_data: 38 });