Refs gitea:Adriano/AI-OS#310 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.7 KiB
Task 2: Il questionario in pagina
Sostituisce il prototipo del cliente, che salva nel browser e che il documento stesso dichiara da buttare in produzione.
Files:
- Create:
src/components/longevity/Questionario.tsx,src/pages/longevity/questionario.astro,src/pages/api/longevity/questionario.ts - Test:
tests/longevity/questionario-pagina.test.ts
Interfaces:
- Consumes:
testAttividaregistro.ts;salvaCompilazione,QUEST_VERSION,CAMPI_LIBERIdaquestionario.ts;codicePerUtentedaanagrafica.ts - Produces:
POST /api/longevity/questionario→{ sessioneId }o errore
Le domande vengono dal registro, non dal codice. La pagina legge testAttivi e costruisce i campi da lì: etichetta, tipo, minimo e massimo. Aggiungere una domanda resta una modifica ai dati — è la promessa della spec, e qui va mantenuta.
⚠️ Tre regole che l'endpoint deve rispettare, e sono di sostanza:
- Il codice cliente si ricava dalla sessione, mai dalla richiesta. Un codice che arriva dal browser va ignorato. È la regola §8 della spec, e questo è il primo endpoint su cui si può finalmente provare.
- Il consenso al trattamento dei dati sanitari non è «compilato», è «dato». Il prototipo del cliente blocca il salvataggio solo se il campo è vuoto: rispondendo «No» i dati vengono salvati lo stesso. È l'esatto contrario di ciò per cui il campo esiste. Qui: se il consenso non è affermativo, la compilazione non si salva, e la risposta lo dice chiaramente.
- Il consenso va registrato, non solo controllato. Un consenso è un fatto che un domani va dimostrato: chi, quando, a cosa. Salvalo fra le note di profilo con la sua data e la versione del questionario, anche quando la risposta è no — il rifiuto è precisamente ciò di cui bisogna poter provare il rispetto.
- Step 1: Write the failing test
// tests/longevity/questionario-pagina.test.ts
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { createLongevityDb } from '../../src/lib/longevity/db';
import { seedRegistro } from '../../src/lib/longevity/registro';
import { campiDelQuestionario, salvaDalForm } from '../../src/lib/longevity/vista';
function dbPronto() {
const db = createLongevityDb(':memory:');
seedRegistro(db);
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001','F')`).run();
return db;
}
describe('i campi del questionario vengono dal registro', () => {
it('costruisce i campi leggendo il registro, non una lista nel codice', () => {
const db = dbPronto();
const campi = campiDelQuestionario(db, '2026-08-22');
expect(campi.length).toBe(20);
const alcol = campi.find((c) => c.id === 'q_alcol_life')!;
expect(alcol.etichetta).toMatch(/alcoliche/i);
expect(alcol.max).toBe(30);
});
it('una domanda disattivata sparisce dal modulo senza toccare il codice', () => {
const db = dbPronto();
db.prepare(`UPDATE registro_test SET attivo_a = '2026-01-01' WHERE test_id = 'q_sigarette'`).run();
const campi = campiDelQuestionario(db, '2026-08-22');
expect(campi.map((c) => c.id)).not.toContain('q_sigarette');
expect(campi.length).toBe(19);
});
});
describe('il salvataggio, e il consenso', () => {
const risposte = { q_riposato: 8, q_ore_sonno: 7.5 };
it('salva una compilazione quando il consenso e dato', () => {
const db = dbPronto();
const esito = salvaDalForm(db, {
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
risposte, consensoSanitario: 'Si', liberi: {},
});
expect(esito.ok).toBe(true);
const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number };
expect(n.n).toBe(1);
});
it('NON salva se il consenso e rifiutato: "No" non e un consenso', () => {
const db = dbPronto();
const esito = salvaDalForm(db, {
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
risposte, consensoSanitario: 'No', liberi: {},
});
expect(esito.ok).toBe(false);
const n = db.prepare(`SELECT COUNT(*) n FROM sessioni`).get() as { n: number };
expect(n.n).toBe(0); // niente sessione, niente misure
});
it('NON salva se il consenso manca del tutto', () => {
const db = dbPronto();
const esito = salvaDalForm(db, {
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
risposte, consensoSanitario: '', liberi: {},
});
expect(esito.ok).toBe(false);
});
it('registra il consenso anche quando e rifiutato: il rifiuto va dimostrato', () => {
const db = dbPronto();
salvaDalForm(db, {
client_code: 'ISL-0001', data: '2026-08-22', eta: 35,
risposte, consensoSanitario: 'No', liberi: {},
});
const note = db.prepare(
`SELECT campo_id, testo FROM profilo_note WHERE campo_id LIKE 'consenso%'`
).all() as { campo_id: string; testo: string }[];
expect(note.length).toBeGreaterThan(0);
expect(note[0].testo).toMatch(/No/);
});
it('il codice cliente NON puo arrivare da chi chiama la pagina', () => {
const src = readFileSync(join(process.cwd(), 'src/pages/api/longevity/questionario.ts'), 'utf8');
// il codice si prende dalla sessione dell'utente, mai dal corpo della richiesta
expect(src).toMatch(/codicePerUtente/);
expect(src).not.toMatch(/body\.client_code|body\.clientId|corpo\.client_code/);
});
});
- Step 2: Run test to verify it fails
Run: npm test -- tests/longevity/questionario-pagina.test.ts
Expected: FAIL — src/lib/longevity/vista.ts non esiste
- Step 3: Write minimal implementation
vista.ts espone campiDelQuestionario(db, alla) — che legge testAttivi e tiene i soli test_id che iniziano per q_ — e salvaDalForm(db, input), che controlla il consenso prima di scrivere qualsiasi cosa, registra il consenso fra le note col suo esito e la versione del questionario, e delega a salvaCompilazione.
L'isola React mostra i quattro blocchi uno per schermata, con avanti e indietro, e invia in fondo. L'endpoint ricava il codice cliente dalla sessione con codicePerUtente e passa a salvaDalForm.
⚠️ Il consenso rifiutato va scritto comunque fra le note: apri la sessione solo se il consenso è affermativo, ma la nota del consenso si scrive in entrambi i casi, legata al cliente.
- Step 4: Run test to verify it passes
Run: npm test — atteso 1 fallito pre-esistente, il resto verde. Poi npx tsc --noEmit pulito.
- Step 5: Commit
git add src/lib/longevity/vista.ts src/components/longevity/Questionario.tsx \
src/pages/longevity/questionario.astro src/pages/api/longevity/questionario.ts \
tests/longevity/questionario-pagina.test.ts
git commit -m "longevity: il questionario in pagina, e il consenso che si puo negare davvero"