longevity: schema misure con verifica/verificata_da/verificata_at + migrazione
Aggiunge le tre colonne allo schema (db.ts) e una funzione migra() idempotente che le aggiunge via ALTER TABLE a un longevity.db esistente (PRAGMA table_info per capire cosa manca): CREATE TABLE IF NOT EXISTS non tocca una tabella gia' creata, e in produzione un longevity.db esiste dal 03/09/2026. misureValideAlla (motore/index.ts) lascia entrare anche le misure fuori range confermate (verifica = 'confermata'), non solo quelle nel range. Refs #7 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,13 @@ CREATE TABLE IF NOT EXISTS misure (
|
|||||||
fonte TEXT NOT NULL CHECK (fonte IN
|
fonte TEXT NOT NULL CHECK (fonte IN
|
||||||
('manuale','questionario','wellness_tower','vald','calibre','stress_index')),
|
('manuale','questionario','wellness_tower','vald','calibre','stress_index')),
|
||||||
fuori_range INTEGER NOT NULL DEFAULT 0,
|
fuori_range INTEGER NOT NULL DEFAULT 0,
|
||||||
|
-- Verifica di una misura fuori range (issue #7): il trainer la conferma (entra nello
|
||||||
|
-- score come le altre, vedi misureValideAlla in motore/index.ts) o la scarta (resta
|
||||||
|
-- esclusa, per sempre). NULL = non ancora verificata. Su un longevity.db creato PRIMA
|
||||||
|
-- di questa modifica queste tre colonne NON ci sono: le aggiunge migra(), qui sotto.
|
||||||
|
verifica TEXT CHECK (verifica IN ('confermata','scartata')),
|
||||||
|
verificata_da TEXT,
|
||||||
|
verificata_at TEXT,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_misure_cliente_test ON misure (client_code, test_id);
|
CREATE INDEX IF NOT EXISTS idx_misure_cliente_test ON misure (client_code, test_id);
|
||||||
@@ -128,6 +135,36 @@ function apri(p: string, schema: string): Database.Database {
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrazione idempotente per un `longevity.db` aperto PRIMA di questa modifica (issue #7):
|
||||||
|
* `CREATE TABLE IF NOT EXISTS` (sopra) non aggiunge colonne a una tabella che esiste già, e
|
||||||
|
* in produzione un longevity.db esiste dal 03/09/2026 (VPS,
|
||||||
|
* /opt/docker/insanitylab-apps/apps/platforms/data/longevity.db) — la stessa trappola già
|
||||||
|
* documentata sul vincolo di `score`, qui sotto. Senza questa funzione le tre colonne di
|
||||||
|
* verifica resterebbero assenti in produzione mentre ogni database creato da zero (ogni
|
||||||
|
* test, e un domani un secondo deploy) le avrebbe già: un disallineamento silenzioso fra
|
||||||
|
* ambienti, non un errore che si vede subito.
|
||||||
|
* `PRAGMA table_info` legge le colonne DAVVERO presenti sul file aperto, non lo schema
|
||||||
|
* dichiarato sopra: per ognuna delle tre assente, un `ALTER TABLE ... ADD COLUMN` la
|
||||||
|
* aggiunge (nullable, quindi le righe esistenti restano valide contro il CHECK — NULL non
|
||||||
|
* fa mai fallire un CHECK). Su un database che le ha già il controllo salta tutto: girarla
|
||||||
|
* a ogni apertura (come seedRegistro/seedPesi) è sicuro.
|
||||||
|
*/
|
||||||
|
function migra(db: Database.Database): void {
|
||||||
|
const colonne = new Set(
|
||||||
|
(db.prepare(`PRAGMA table_info(misure)`).all() as { name: string }[]).map((c) => c.name)
|
||||||
|
);
|
||||||
|
if (!colonne.has('verifica')) {
|
||||||
|
db.exec(`ALTER TABLE misure ADD COLUMN verifica TEXT CHECK (verifica IN ('confermata','scartata'))`);
|
||||||
|
}
|
||||||
|
if (!colonne.has('verificata_da')) {
|
||||||
|
db.exec(`ALTER TABLE misure ADD COLUMN verificata_da TEXT`);
|
||||||
|
}
|
||||||
|
if (!colonne.has('verificata_at')) {
|
||||||
|
db.exec(`ALTER TABLE misure ADD COLUMN verificata_at TEXT`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⚠️ Il registro dei test e i pesi si seminano QUI, all'apertura del database.
|
* ⚠️ Il registro dei test e i pesi si seminano QUI, all'apertura del database.
|
||||||
*
|
*
|
||||||
@@ -147,6 +184,7 @@ function apri(p: string, schema: string): Database.Database {
|
|||||||
*/
|
*/
|
||||||
export function createLongevityDb(path?: string): Database.Database {
|
export function createLongevityDb(path?: string): Database.Database {
|
||||||
const db = apri(path ?? process.env.LONGEVITY_DB_PATH ?? 'data/longevity.db', SCHEMA_LONGEVITY);
|
const db = apri(path ?? process.env.LONGEVITY_DB_PATH ?? 'data/longevity.db', SCHEMA_LONGEVITY);
|
||||||
|
migra(db);
|
||||||
seedRegistro(db);
|
seedRegistro(db);
|
||||||
seedPesi(db, MODEL_VERSION);
|
seedPesi(db, MODEL_VERSION);
|
||||||
return db;
|
return db;
|
||||||
|
|||||||
@@ -40,8 +40,8 @@
|
|||||||
* applicarla, e restituisce `null` se il registro (dato modificabile senza
|
* applicarla, e restituisce `null` se il registro (dato modificabile senza
|
||||||
* migrazioni) ha una riga incompleta, invece del punteggio pieno che dava
|
* migrazioni) ha una riga incompleta, invece del punteggio pieno che dava
|
||||||
* `lerp` con estremi indefiniti; e la lettura delle misure esclude
|
* `lerp` con estremi indefiniti; e la lettura delle misure esclude
|
||||||
* `fuori_range = 1` (§9 della spec), in attesa della colonna di conferma
|
* `fuori_range = 1` (§9 della spec) salvo conferma esplicita del trainer
|
||||||
* (punto aperto in §12).
|
* (`misure.verifica = 'confermata'`, issue #7: `verificaMisura` in `misure.ts`).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type Database from 'better-sqlite3';
|
import type Database from 'better-sqlite3';
|
||||||
@@ -156,9 +156,11 @@ export const FINESTRA_GIORNI: number | null = null;
|
|||||||
*
|
*
|
||||||
* 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. La colonna della conferma non esiste ancora (§12 della spec, issue #7):
|
* una precedente — A MENO che il trainer l'abbia confermata (`verifica = 'confermata'`,
|
||||||
* quando ci sarà, questo filtro andrà rilassato per lasciar passare le misure confermate,
|
* colonna aggiunta dall'issue #7, scritta da `verificaMisura` in `misure.ts`): da quel
|
||||||
* non tolto del tutto.
|
* momento in poi entra nello score come qualunque altra misura, qui e in ogni sessione
|
||||||
|
* successiva che la eredita. `verifica = 'scartata'`, o mai verificata (NULL), resta
|
||||||
|
* esclusa come prima.
|
||||||
*/
|
*/
|
||||||
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(
|
||||||
@@ -170,7 +172,7 @@ export function misureValideAlla(db: Database.Database, sessioneId: number): Map
|
|||||||
`SELECT m.test_id AS test_id, m.valore_num AS valore_num, s2.data AS data
|
`SELECT m.test_id AS test_id, m.valore_num AS valore_num, s2.data AS data
|
||||||
FROM misure m
|
FROM misure m
|
||||||
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 OR m.verifica = 'confermata') 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, m.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
|
||||||
|
|||||||
Reference in New Issue
Block a user