longevity: misureValideAlla, spareggio sulla riga e nota sulla conferma dei fuori range

Refs #1
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 12:40:31 +00:00
parent 4968b75472
commit af8afebba8
2 changed files with 31 additions and 8 deletions
@@ -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ò * questionario del giorno D, e viceversa una sessione del giorno D-1 non vede nulla di ciò
* che accadrà dopo di lei. * che accadrà dopo di lei.
* *
* "Più recente" = data della sessione DESC, poi id DESC (a parità di data, l'inserimento * "Più recente" = data della sessione DESC, poi id di sessione DESC (a parità di data,
* più tardo vince) — stesso criterio di `leggiScore`. A parità di sessione (una misura * l'inserimento più tardo vince) — stesso criterio di `leggiScore`. A parità di SESSIONE
* ri-registrata più volte nella stessa sessione, oggi non capita ma non è vietato) vince * (due misure dello stesso test_id nella stessa sessione: `registraMisure` non lo vieta,
* comunque la più recente per `id` di riga in `misure`, perché la query è ordinata anche * es. una correzione manuale dopo un refuso) l'ORDER BY aggiunge un terzo criterio
* su quello implicitamente tramite l'ordine della sessione. * 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 * 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 * 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<string, number> { export function misureValideAlla(db: Database.Database, sessioneId: number): Map<string, number> {
const sessione = db.prepare( 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 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 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 <= ?)) 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 ).all(sessione.client_code, sessione.data, sessione.data, sessione.id) as
{ test_id: string; valore_num: number; data: string }[]; { test_id: string; valore_num: number; data: string }[];
const mappa = new Map<string, number>(); const mappa = new Map<string, number>();
for (const r of righe) { 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) { if (FINESTRA_GIORNI !== null) {
const giorni = (Date.parse(sessione.data) - Date.parse(r.data)) / 86_400_000; const giorni = (Date.parse(sessione.data) - Date.parse(r.data)) / 86_400_000;
if (giorni > FINESTRA_GIORNI) continue; if (giorni > FINESTRA_GIORNI) continue;
@@ -198,6 +198,20 @@ describe('un check-up e un questionario sono sessioni separate, ma un cliente e
expect(sd).not.toHaveProperty('questionario_lifestyle'); 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', () => { it('una misura fuori range viene ignorata e sostituita dalla precedente misura valida', () => {
const db = dbPronto('ISL-0022', 'M'); const db = dbPronto('ISL-0022', 'M');
const s1 = apriSessione(db, { client_code: 'ISL-0022', data: '2026-08-05', tipo: 'checkup', eta_alla_data: 38 }); const s1 = apriSessione(db, { client_code: 'ISL-0022', data: '2026-08-05', tipo: 'checkup', eta_alla_data: 38 });