import type Database from 'better-sqlite3'; export type Curva = 'bell' | 'lin_dec' | 'inc_plateau' | 'x10' | 'x10_inv' | 'decstep' | 'incstep'; export type VoceRegistro = { test_id: string; etichetta: string; unita?: string; tipo_valore: 'num' | 'txt'; curva?: Curva; params?: unknown; asse?: string; sotto_dominio?: string; range_min?: number; range_max?: number; attivo_da: string; attivo_a?: string; }; const DA = '2026-01-01'; // I 20 campi punteggiati, presi dal prototipo del cliente (questionario_longevity_score.html). // Gli id portano il prefisso q_ per distinguerli dai test fisici; i params sono nella forma // dichiarata per ciascuna curva, non nell'array posizionale del prototipo. const QUESTIONARIO: VoceRegistro[] = [ { test_id: 'q_ore_sonno', etichetta: 'Ore di sonno per notte (media)', unita: 'h', tipo_valore: 'num', curva: 'bell', params: { low: 4, peakLow: 7, peakHigh: 9, high: 12 }, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 14, attivo_da: DA }, { test_id: 'q_min_addorm', etichetta: 'Minuti per addormentarti', unita: 'min', tipo_valore: 'num', curva: 'decstep', params: { steps: [[15, 100], [30, 75], [60, 50], [999, 25]] }, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 180, attivo_da: DA }, { test_id: 'q_risvegli', etichetta: 'Risvegli notturni (numero)', tipo_valore: 'num', curva: 'decstep', params: { steps: [[0, 100], [1, 80], [2, 50], [999, 25]] }, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_riposato', etichetta: 'Quanto ti senti riposato al risveglio', tipo_valore: 'num', curva: 'x10', params: {}, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_caffeina', etichetta: 'Caffeina dopo le 16:00 (volte/settimana)', tipo_valore: 'num', curva: 'lin_dec', params: { best: 0, worst: 7 }, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 14, attivo_da: DA }, { test_id: 'q_sonnolenza_diurna', etichetta: 'Sonnolenza/fatica a concentrarti durante il giorno (volte/settimana)', tipo_valore: 'num', curva: 'lin_dec', params: { best: 0, worst: 7 }, asse: 'Recupero & Sistema Nervoso', sotto_dominio: 'questionario_sonno', range_min: 0, range_max: 14, attivo_da: DA }, { test_id: 'q_energia_media', etichetta: 'Energia media nella giornata, ultima settimana', tipo_valore: 'num', curva: 'x10', params: {}, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_esaurimento', etichetta: 'Episodi di esaurimento senza motivo fisico (volte/sett.)', tipo_valore: 'num', curva: 'lin_dec', params: { best: 0, worst: 7 }, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 14, attivo_da: DA }, { test_id: 'q_calo_pomeridiano', etichetta: 'Quanto forte e il calo di energia dal mattino al pomeriggio', tipo_valore: 'num', curva: 'x10_inv', params: {}, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_sopraffatto', etichetta: 'Sopraffatto da imprevisti (volte/mese)', tipo_valore: 'num', curva: 'decstep', params: { steps: [[2, 100], [5, 70], [10, 40], [999, 20]] }, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 30, attivo_da: DA }, { test_id: 'q_controllo', etichetta: 'Percezione di controllo sulla tua vita', tipo_valore: 'num', curva: 'x10', params: {}, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_sicurezza_gestione', etichetta: 'Ti senti sicuro/a nella tua capacita di gestire i problemi personali', tipo_valore: 'num', curva: 'x10', params: {}, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_tensione', etichetta: 'Tensione/irritabilita (volte/settimana)', tipo_valore: 'num', curva: 'lin_dec', params: { best: 0, worst: 7 }, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 14, attivo_da: DA }, { test_id: 'q_pensieri_lavoro', etichetta: 'Ore/giorno di pensieri di lavoro fuori orario', unita: 'h', tipo_valore: 'num', curva: 'lin_dec', params: { best: 0, worst: 4 }, asse: 'Energia & Regolazione Stress', sotto_dominio: 'questionario_energia_stress', range_min: 0, range_max: 8, attivo_da: DA }, { test_id: 'q_attivita', etichetta: 'Giorni/settimana attivita fisica extra ISL', tipo_valore: 'num', curva: 'incstep', params: { steps: [[0, 20], [2, 50], [4, 80], [999, 100]] }, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 7, attivo_da: DA }, { test_id: 'q_alimentazione', etichetta: 'Qualita percepita alimentazione abituale', tipo_valore: 'num', curva: 'x10', params: {}, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 10, attivo_da: DA }, { test_id: 'q_sigarette', etichetta: 'Sigarette al giorno', tipo_valore: 'num', curva: 'decstep', params: { steps: [[0, 100], [5, 60], [10, 40], [20, 20], [999, 0]] }, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 40, attivo_da: DA }, // Soglia 0-7 = punteggio pieno: scelta deliberata del cliente (basso rischio NIAAA/WHO), // confermata il 21/08. Non e' un refuso per 0=100 -> 7=0. { test_id: 'q_alcol_life', etichetta: 'Unita alcoliche/settimana', tipo_valore: 'num', curva: 'lin_dec', params: { best: 7, worst: 14 }, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 30, attivo_da: DA }, { test_id: 'q_luce', etichetta: 'Ore/giorno luce naturale/esterno', unita: 'h', tipo_valore: 'num', curva: 'incstep', params: { steps: [[0.5, 60], [1, 90], [999, 100]], zeroVal: 20 }, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 8, attivo_da: DA }, { test_id: 'q_schermi', etichetta: 'Minuti schermi nell\'ora pre-sonno', unita: 'min', tipo_valore: 'num', curva: 'decstep', params: { steps: [[15, 85], [30, 70], [60, 40], [999, 10]], zeroVal: 100 }, asse: 'Stile di Vita & Sonno', sotto_dominio: 'questionario_lifestyle', range_min: 0, range_max: 90, attivo_da: DA }, ]; export function seedRegistro(db: Database.Database): void { const ins = db.prepare( `INSERT OR REPLACE INTO registro_test (test_id, etichetta, unita, tipo_valore, curva, params, asse, sotto_dominio, range_min, range_max, attivo_da, attivo_a, note) VALUES (@test_id, @etichetta, @unita, @tipo_valore, @curva, @params, @asse, @sotto_dominio, @range_min, @range_max, @attivo_da, @attivo_a, @note)` ); const tx = db.transaction((voci: VoceRegistro[]) => { for (const v of voci) { ins.run({ test_id: v.test_id, etichetta: v.etichetta, unita: v.unita ?? null, tipo_valore: v.tipo_valore, curva: v.curva ?? null, params: v.params === undefined ? null : JSON.stringify(v.params), asse: v.asse ?? null, sotto_dominio: v.sotto_dominio ?? null, range_min: v.range_min ?? null, range_max: v.range_max ?? null, attivo_da: v.attivo_da, attivo_a: v.attivo_a ?? null, note: null, }); } }); tx(QUESTIONARIO); } // Pesi presi dal motore del cliente (AXIS_SUBDOMAIN_WEIGHTS, MACRO_SCORE_WEIGHTS, // FITNESS_AGE_WEIGHTS). I macro con peso 0 non si inseriscono: assenza e zero sono // la stessa cosa per la rinormalizzazione, e una riga a zero confonde chi legge. const PESI_ASSE: Record> = { 'Forza & Struttura': { handgrip: 0.25, spinta: 0.20, trazione: 0.20, arti_inferiori: 0.20, core: 0.15 }, 'Composizione Corporea': { grasso: 0.40, muscolo: 0.35, whr: 0.25 }, 'Cardio-Respiratorio': { vo2max: 0.60, spirometria: 0.20, wellness_tower_cardio: 0.20 }, 'Recupero & Sistema Nervoso': { hrv: 0.50, pressione: 0.15, hrr: 0.15, questionario_sonno: 0.20 }, 'Energia & Regolazione Stress': { hrv: 0.50, questionario_energia_stress: 0.50 }, 'Stabilità & Mobilità Funzionale': { flamingo: 0.35, sit_and_reach: 0.25, plank: 0.20, back_scratch: 0.14, wellness_tower_shoulder: 0.06 }, 'Stile di Vita & Sonno': { questionario_lifestyle: 1.00 }, }; const PESI_MACRO: Record> = { PERFORMANCE: { 'Forza & Struttura': 0.25, 'Cardio-Respiratorio': 0.25, 'Composizione Corporea': 0.15, 'Stabilità & Mobilità Funzionale': 0.20, 'Recupero & Sistema Nervoso': 0.10, 'Energia & Regolazione Stress': 0.05, }, ENERGY: { 'Energia & Regolazione Stress': 0.30, 'Stile di Vita & Sonno': 0.20, 'Recupero & Sistema Nervoso': 0.25, 'Cardio-Respiratorio': 0.15, 'Composizione Corporea': 0.10, }, RECOVERY: { 'Recupero & Sistema Nervoso': 0.45, 'Energia & Regolazione Stress': 0.15, 'Stile di Vita & Sonno': 0.20, 'Stabilità & Mobilità Funzionale': 0.15, 'Forza & Struttura': 0.05, }, }; const PESI_FITNESS_AGE: Record = { cardio: 0.30, handgrip_isolato: 0.20, hrv_isolato: 0.20, forza_resto: 0.15, composizione: 0.10, stabilita: 0.05, }; export function seedPesi(db: Database.Database, modelVersion: string): void { const ins = db.prepare( `INSERT OR REPLACE INTO pesi (model_version, livello, contenitore, elemento, peso) VALUES (?, ?, ?, ?, ?)` ); const tx = db.transaction(() => { for (const [asse, sd] of Object.entries(PESI_ASSE)) for (const [el, p] of Object.entries(sd)) ins.run(modelVersion, 'asse', asse, el, p); for (const [macro, assi] of Object.entries(PESI_MACRO)) for (const [el, p] of Object.entries(assi)) ins.run(modelVersion, 'macro', macro, el, p); for (const [el, p] of Object.entries(PESI_FITNESS_AGE)) ins.run(modelVersion, 'fitness_age', 'fitness_age', el, p); }); tx(); } export function testAttivi(db: Database.Database, alla: string): VoceRegistro[] { const righe = db.prepare( `SELECT * FROM registro_test WHERE attivo_da <= ? AND (attivo_a IS NULL OR attivo_a > ?) ORDER BY test_id` ).all(alla, alla) as Record[]; return righe.map((r) => ({ ...r, params: r.params ? JSON.parse(r.params as string) : undefined, })) as VoceRegistro[]; } export function esisteTest(db: Database.Database, testId: string): boolean { const r = db.prepare(`SELECT 1 FROM registro_test WHERE test_id = ?`).get(testId); return r !== undefined; }