longevity: collega i test fisici a calcolaSessione, misure valide fra sessioni

Un check-up e un questionario sono sessioni separate: prima di questo
commit calcolaSessione leggeva solo le misure della sessione corrente,
quindi l'una non vedeva mai i dati dell'altra. misureValideAlla (nuova,
motore/index.ts) legge invece, per ogni test_id, la misura valida piu'
recente del cliente fra tutte le sue sessioni fino a quella data.
sottoDominiDellaSessione unisce sotto-domini da curva (questionario) e
sotto-domini fisici (sottoDominiFisici) in un'unica mappa, usata sia da
calcolaSessione (livello 1) sia da vista.ts (sottoDominiPresenti, per
"cosa manca" nel referto — non piu' una query separata sulla tabella
misure). Aggiornati i commenti di seed-demo.ts (il registro ora contiene
anche le voci f_*, anche se questa demo non le compila).

Test nuovi in motore-fisici-sessione.test.ts: il caso demo() dell'oracolo
end-to-end, un ingresso mancante o l'eta assente che lascia il
sotto-dominio fuori, la combinazione fra sessioni (check-up dopo il
questionario, sessione precedente che non vede il futuro, misura fuori
range sostituita dalla precedente valida). 202 test esistenti restano
verdi, 9 nuovi: 211 totali. tsc --noEmit: 0 errori (0 anche su 13d8cdd).

Refs #1
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 12:38:34 +00:00
parent 48910d84ba
commit 4968b75472
4 changed files with 379 additions and 63 deletions
+16 -16
View File
@@ -11,7 +11,7 @@
import type Database from 'better-sqlite3';
import { testAttivi, pesiDi, MODEL_VERSION } from './registro';
import { salvaCompilazione, CONSENT_VERSION } from './questionario';
import { leggiScore, calcolaSessione, salvaScore } from './motore';
import { leggiScore, calcolaSessione, salvaScore, sottoDominiDellaSessione } from './motore';
export type CampoQuestionario = {
id: string;
@@ -137,11 +137,13 @@ export type MacroVista = { nome: string; valore: number | null; copertura: numbe
/**
* Le etichette leggibili dei sotto-domini che alimentano un asse: le chiavi sono quelle
* di `PESI_ASSE` in registro.ts. Non tutte hanno oggi una riga in `registro_test` — i
* test fisici (handgrip, VO2max, plank...) arrivano con un piano successivo, il
* questionario è l'unica fonte di misure che esiste davvero — quindi la traduzione non
* può venire da lì: è dato locale a questo modulo, non duplica il registro, lo completa
* dove il registro non arriva ancora.
* di `PESI_ASSE` in registro.ts. Non sono chiavi che si leggono da `registro_test.sotto_dominio`
* per tutte: quelle del questionario sì, ma quelle fisiche (handgrip, VO2max, plank...) le
* produce `sottoDominiFisici` (`motore/fisici.ts`) come chiave del suo risultato — le voci
* FISICI del registro non portano un `sotto_dominio` proprio (vedi il commento su `FISICI`
* in registro.ts) — quindi la traduzione non può venire da una query sul registro: è dato
* locale a questo modulo. Unica eccezione rimasta davvero scoperta: `spirometria`, fuori
* dal perimetro di `sottoDominiFisici` (nessun calcolatore GLI, vedi quel file).
*/
const ETICHETTE_SOTTO_DOMINIO: Record<string, string> = {
handgrip: 'Forza di presa (handgrip)',
@@ -178,17 +180,15 @@ function contenitoriDi(db: Database.Database, livello: 'asse' | 'macro'): string
).all(MODEL_VERSION, livello) as { contenitore: string }[]).map((r) => r.contenitore);
}
/** I sotto-domini con almeno una misura valida (nel range atteso) in questa sessione:
* l'informazione grezza da cui `mancano` si costruisce, non un punteggio — leggere quali
* test sono stati fatti non è ricalcolare come pesano. */
/** I sotto-domini con un punteggio calcolabile per questa sessione — le chiavi di
* `sottoDominiDellaSessione`, che a differenza di una query sulla sola tabella `misure`
* vede anche le misure valide portate avanti da sessioni precedenti dello stesso cliente
* (`misureValideAlla` in `motore/index.ts`) e i sotto-domini fisici (`sottoDominiFisici`),
* non solo quelli da curva del questionario. Usa lo stesso calcolo che alimenta gli assi,
* non lo duplica: `mancano` si costruisce da qui, mai da un ricalcolo separato che
* potrebbe divergere. */
function sottoDominiPresenti(db: Database.Database, sessioneId: number): Set<string> {
const righe = db.prepare(
`SELECT DISTINCT rt.sotto_dominio AS sd
FROM misure m JOIN registro_test rt ON rt.test_id = m.test_id
WHERE m.sessione_id = ? AND m.fuori_range = 0 AND m.valore_num IS NOT NULL
AND rt.sotto_dominio IS NOT NULL`
).all(sessioneId) as { sd: string }[];
return new Set(righe.map((r) => r.sd));
return new Set(Object.keys(sottoDominiDellaSessione(db, sessioneId)));
}
/** Cosa manca per completare un asse insufficiente: i sotto-domini pesati per quell'asse