longevity: export pseudonimizzato per costruzione
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// L'export per le statistiche legge SOLO longevity.db, che per costruzione non contiene
|
||||||
|
// nomi: non serve nessuna funzione di anonimizzazione, e non c'è niente da ricordarsi.
|
||||||
|
// I campi liberi (farmaci, patologie) restano fuori: sono per il coach, non per l'analisi.
|
||||||
|
import type Database from 'better-sqlite3';
|
||||||
|
|
||||||
|
const INTESTAZIONI = [
|
||||||
|
'client_code', 'sesso', 'eta', 'data', 'tipo_sessione',
|
||||||
|
'quest_version', 'test_id', 'valore', 'unita', 'fonte', 'fuori_range',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function esportaMisure(db: Database.Database): {
|
||||||
|
intestazioni: string[];
|
||||||
|
righe: (string | number | null)[][];
|
||||||
|
} {
|
||||||
|
const righe = db.prepare(
|
||||||
|
`SELECT sg.client_code, sg.sesso, s.eta_alla_data, s.data, s.tipo, s.quest_version,
|
||||||
|
m.test_id, COALESCE(m.valore_num, m.valore_txt) AS valore, m.unita, m.fonte, m.fuori_range
|
||||||
|
FROM misure m
|
||||||
|
JOIN sessioni s ON s.id = m.sessione_id
|
||||||
|
JOIN soggetti sg ON sg.client_code = m.client_code
|
||||||
|
ORDER BY s.data, m.id`
|
||||||
|
).raw().all() as (string | number | null)[][];
|
||||||
|
|
||||||
|
return { intestazioni: INTESTAZIONI, righe };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function esportaCsv(db: Database.Database): string {
|
||||||
|
const { intestazioni, righe } = esportaMisure(db);
|
||||||
|
const cella = (v: string | number | null) => {
|
||||||
|
if (v === null) return '';
|
||||||
|
const s = String(v);
|
||||||
|
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||||
|
};
|
||||||
|
return [intestazioni.join(','), ...righe.map((r) => r.map(cella).join(','))].join('\n') + '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { createLongevityDb } from '../../src/lib/longevity/db';
|
||||||
|
import { seedRegistro } from '../../src/lib/longevity/registro';
|
||||||
|
import { salvaCompilazione } from '../../src/lib/longevity/questionario';
|
||||||
|
import { esportaMisure, esportaCsv } from '../../src/lib/longevity/export';
|
||||||
|
|
||||||
|
function dbConDati() {
|
||||||
|
const db = createLongevityDb(':memory:');
|
||||||
|
seedRegistro(db);
|
||||||
|
db.prepare(`INSERT INTO soggetti (client_code, sesso) VALUES ('ISL-0001', 'F')`).run();
|
||||||
|
salvaCompilazione(db, {
|
||||||
|
client_code: 'ISL-0001', data: '2026-08-21', eta: 35,
|
||||||
|
risposte: { q_ore_sonno: 7, q_riposato: 8 },
|
||||||
|
liberi: { farmaci: 'nessuno' },
|
||||||
|
});
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('export per le statistiche', () => {
|
||||||
|
it('esporta una riga per misura con codice, non con nome', () => {
|
||||||
|
const db = dbConDati();
|
||||||
|
const out = esportaMisure(db);
|
||||||
|
expect(out.intestazioni).toEqual(
|
||||||
|
['client_code', 'sesso', 'eta', 'data', 'tipo_sessione', 'quest_version', 'test_id', 'valore', 'unita', 'fonte', 'fuori_range']
|
||||||
|
);
|
||||||
|
expect(out.righe.length).toBe(2);
|
||||||
|
expect(out.righe[0][0]).toBe('ISL-0001');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('l export non contiene campi identificativi', () => {
|
||||||
|
const db = dbConDati();
|
||||||
|
const testo = JSON.stringify(esportaMisure(db));
|
||||||
|
for (const vietato of ['nome', 'cognome', 'email', 'telefono', 'data_nascita']) {
|
||||||
|
expect(testo).not.toContain(vietato);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('i campi liberi restano fuori dall export statistico', () => {
|
||||||
|
const db = dbConDati();
|
||||||
|
expect(JSON.stringify(esportaMisure(db))).not.toContain('nessuno');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produce un CSV con intestazione e una riga per misura', () => {
|
||||||
|
const db = dbConDati();
|
||||||
|
const righe = esportaCsv(db).trim().split('\n');
|
||||||
|
expect(righe.length).toBe(3); // intestazione + 2 misure
|
||||||
|
expect(righe[0]).toBe('client_code,sesso,eta,data,tipo_sessione,quest_version,test_id,valore,unita,fonte,fuori_range');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user