Skip to content

Guida Utente

Questa guida descrive il flusso operativo attuale di SaniKey per costruire un archivio medico locale ed esportarlo in una struttura USB.

Confine dei Dati

Il repository pubblico non deve contenere dati personali, nomi reali di pazienti, documenti clinici reali o percorsi locali privati. I dati reali restano in directory locali referenziate da config/accounts.toml; le directory config e local-data sono escluse da Git.

Gli esempi pubblicabili sotto docs/config-example, docs/patients-example e docs/generated-example sono fixture di documentazione. Per compilare una configurazione o un metadato, partire dall'indice degli esempi di configurazione e dall'indice degli esempi paziente, poi verificare campi e vincoli nel riferimento metadati TOML.

Prerequisiti

L'ingestione dati e' stabilizzata e supportata su Linux. La consultazione degli artefatti statici generati resta indipendente dalla piattaforma, ma la pipeline di ingestione su Windows non e' ancora parte del contratto operativo.

Installa le dipendenze del progetto e gli strumenti del repository da un checkout locale:

python3 scripts/bootstrap_dev_environment.py

Esegui la validazione del repository prima di affidarti al checkout:

uv run python scripts/validate_repo.py

Configurare i Pazienti

Crea un file di configurazione locale privato:

mkdir -p config
cp docs/config-example/accounts.toml config/accounts.toml

Modifica config/accounts.toml in modo che ogni percorso punti a una directory locale privata. I percorsi possono essere assoluti oppure relativi alla root del repository quando il file si trova in config/accounts.toml:

  • source_documents: documenti originali ricevuti da ospedali o operatori.
  • metadata_directory: file di metadati curati per il paziente.
  • local_build: artefatti generati per il paziente.
  • usb_uuid: UUID atteso del filesystem USB.

Esempio locale:

[global]
config_version = 1

[global.ingestion]
exclude_patterns = [
    "**/Help/**",
    "**/Viewer-Windows/**",
    "**/Viewer/**",
    "**/jre/**",
    "**/assets/**",
]

[global.usb]
usb_uuid = "MANUAL-TEST-USB"
require_exfat = true
min_free_space_mb = 512
copy_strategy = "rsync-preferred"

[[person]]
id = "marco"
display_name = "Marco Coppola"
source_documents = "local-data/marco/documents"
metadata_directory = "local-data/marco/metadata"
local_build = "local-data/generated/marco"

Sintassi completa di accounts.toml

accounts.toml usa solo sezioni e campi chiusi: un campo non elencato qui viene rifiutato da validate-config.

[global]

Campo Tipo TOML Obbligatorio Default Valori ammessi
config_version intero si nessuno 1

[global.ui] e [person.ui]

[global.ui] definisce i default per tutti i pazienti. [person.ui], scritto dopo una specifica voce [[person]], sovrascrive solo i campi indicati per quel paziente.

Campo Tipo TOML Obbligatorio Default Valori ammessi
accent_color stringa no "#2563eb" colore esadecimale #rrggbb
density stringa no "comfortable" "compact", "comfortable"
default_tab stringa no "documents" "documents", "advanced", "timeline", "summary"
timeline_order stringa no "desc" "desc", "asc"
document_link_mode stringa no "usb-relative" "usb-relative"
subtitle stringa no "Archivio sanitario personale" testo fino a 120 caratteri
background_image stringa path no assente file esistente, relativo alla root repo o assoluto
background_opacity numero no 0.1 da 0.0 a 1.0

Esempio:

[global.ui]
accent_color = "#1d4ed8"
density = "compact"
default_tab = "documents"
timeline_order = "desc"
document_link_mode = "usb-relative"
subtitle = "Archivio sanitario personale"
background_image = "config/assets/background.png"
background_opacity = 0.10

[[person]]
id = "marco"
# ...

[person.ui]
default_tab = "timeline"
subtitle = "Archivio Marco"

[global.search] e [person.search]

[person.search] sovrascrive i campi indicati per il singolo paziente. Se dictionary cambia, viene caricato il nuovo file TOML.

Campo Tipo TOML Obbligatorio Default Valori ammessi
dictionary stringa path no assente file TOML esistente
advanced_index_warning_mb intero no 25 intero positivo

Il file indicato da dictionary ha solo due sezioni ammesse, entrambe opzionali. Le chiavi sono stringhe TOML; i valori sono liste non vuote di stringhe non vuote.

[terms]
rx = ["radiografia", "raggi x"]
rmn = ["risonanza magnetica", "rm"]
tac = ["tc", "tomografia"]

[months]
gennaio = ["01", "1"]
febbraio = ["02", "2"]

[global.ingestion] e [person.ingestion]

I pattern di [person.ingestion] si sommano a quelli globali; non li sostituiscono. I pattern sorgente sono relativi a source_documents; dentro un container sono relativi alla root estratta del container. Ogni pattern viene confrontato sia con il path relativo sia con il solo nome file. Il confronto e' case-insensitive, quindi "**/Help/**" esclude anche help, HELP o combinazioni miste. include_patterns ha precedenza su exclude_patterns e serve a recuperare un file specifico dentro una directory esclusa. Gli stessi pattern filtrano anche il rilevamento di viewer HTML nei supporti DICOM e la copia dei documenti originali in export-usb: un file escluso dall'ingestione non viene copiato nella directory patients/<id>/documents della chiavetta.

In TOML le chiavi exclude_patterns e include_patterns possono comparire una sola volta nella stessa tabella. Per indicare piu' pattern bisogna usare una lista nello stesso campo.

Campo Tipo TOML Obbligatorio Default Valori ammessi
exclude_patterns lista di stringhe no [] glob non vuoti, per esempio "**/Help/**" o "*.tmp"
include_patterns lista di stringhe no [] glob non vuoti che recuperano file esclusi, per esempio "**/Viewer/report.pdf"

Esempio:

[global.ingestion]
exclude_patterns = [
    "**/Help/**",
    "**/Viewer-Windows/**",
    "**/Viewer/**",
    "**/jre/**",
    "**/assets/**",
]
include_patterns = ["**/Viewer/report.pdf"]

[[person]]
id = "irene"
# ...

[person.ingestion]
exclude_patterns = ["**/documentazione-non-clinica/**"]
include_patterns = ["**/documentazione-non-clinica/referto.pdf"]

[global.usb]

Questa sezione regola l'export verso target fisici. I target locali simulati restano supportati; i controlli UUID/fstype sono applicati ai mount fisici o quando il campo globale lo richiede.

Campo Tipo TOML Obbligatorio Default Valori ammessi
usb_uuid stringa o assente no assente UUID filesystem reale, per esempio "757F-7873"
require_exfat booleano no false true, false
min_free_space_mb intero no 256 intero positivo
copy_strategy stringa no "rsync-preferred" "rsync-preferred", "python"

Esempio:

[global.usb]
usb_uuid = "757F-7873"
require_exfat = true
min_free_space_mb = 512
copy_strategy = "rsync-preferred"

usb_uuid è il default per i campi usb_uuid dei pazienti. Se non è impostato, ogni paziente deve dichiarare usb_uuid; i pazienti abilitati devono condividere lo stesso valore, che viene usato come UUID atteso quando il target sembra una chiavetta fisica montata sotto /run/media o /media.

[[person]]

Campo Tipo TOML Obbligatorio Default Valori ammessi
id stringa si nessuno minuscole, numeri e trattini, per esempio "patient-a"
display_name stringa si nessuno stringa non vuota
source_documents stringa path si nessuno path assoluto o relativo alla root repo
metadata_directory stringa path si nessuno path assoluto o relativo alla root repo
local_build stringa path si nessuno path assoluto o relativo alla root repo
usb_uuid stringa no se [global.usb].usb_uuid è impostato valore globale Override UUID filesystem coerente con [global.usb]
enabled booleano no true true, false

I percorsi dentro local-data/ sono accettati perché la directory è ignorata da Git. Percorsi dentro directory versionate del repository, per esempio docs/ o src/, vengono rifiutati.

Valida la configurazione e gli invarianti di privacy:

uv run sanikey validate-config

validate-config carica anche i metadati curati dei pazienti abilitati e segnala prima della build errori come TOML non valido, id duplicati o terapie che fanno riferimento a farmaci non presenti in medications.toml.

Elenca i pazienti abilitati:

uv run sanikey list-patients

Preparare i Documenti Sorgente

Per convenzioni di naming, categorie, cartelle di servizio, archivi DICOM e casi da rivedere, leggere prima Organizzare l'Archivio Sanitario.

Inserisci i file sorgente nella directory source_documents configurata per ciascun paziente. L'implementazione attuale esegue scansione deterministica, estrae il testo supportato, cataloga i supporti DICOM e registra i metadati in un archivio SQLite generato.

Usare directory con prefisso _ per contenuti di servizio che devono restare in testa all'ordine alfabetico senza diventare categorie cliniche ordinarie. Le directory convenzionali sono _Archivi per supporti compressi, immagini disco e altri container tecnici, _Parametri per fogli di misurazioni longitudinali e _Terapia per documenti sulla terapia corrente. Il prefisso _ non viene mostrato nella categoria del documento.

Per verificare il numero di file rilevati senza stampare l'intero inventario:

uv run sanikey scan-documents

Il comando segnala anche avvisi rilevabili senza build completa, come duplicati e file con estensione non supportata. Per impostazione predefinita estrae anche i container supportati in local_build/staging/containers/, cosi' il contenuto degli archivi puo' essere verificato manualmente prima della build completa. Il riepilogo include archivi_preparati=, membri_in_archivi= e documenti_derivati= quando lo staging e' attivo e esclusi= quando pattern di ingestion hanno saltato file deliberatamente. I file esclusi non sono avvisi. Prima della scansione viene ripetuto il controllo dei metadati curati dei pazienti selezionati, cosi' un errore in therapies.toml blocca subito il comando invece di emergere dopo una build lunga.

I nomi mostrati per i documenti derivano dal nome file: se il nome inizia con una data aaaammgg, quella parte diventa la data del documento e il resto del nome, senza estensione, diventa il titolo. Gli underscore vengono mostrati come spazi. Per esempio 20260102 Referto_Laboratorio.pdf diventa data 02/01/2026 e titolo Referto Laboratorio.

Su terminali interattivi, i passi lunghi stampano punti di avanzamento su stderr, senza modificare l'output riepilogativo su stdout. scan-documents stampa un punto ogni 20 file sorgente, lo staging un punto per ogni container processato e il catalogo DICOM un punto ogni 50 record. Per disattivarli:

uv run sanikey scan-documents --no-progress

Per eseguire una scansione solo inventariale, senza creare o aggiornare lo staging dei container:

uv run sanikey scan-documents --no-stage-containers

Per eseguire anche controlli preliminari leggeri su archivi e documenti Office:

uv run sanikey scan-documents --preflight

--preflight non esegue OCR PDF e non converte documenti legacy Office, ma può individuare prima della build archivi corrotti o documenti Office non leggibili.

Per ispezionare a schermo la lista dei documenti ingeriti:

uv run sanikey scan-documents --verbose

Per salvare l'inventario in un file riprocessabile:

uv run sanikey scan-documents --output local-data/scan.txt --format text
uv run sanikey scan-documents --output local-data/scan.csv --format csv

--format e' valido solo insieme a --output. Il formato text usa la riga tab-separated estesa con paziente, tipo, categoria, data ISO, titolo, SHA256 e path assoluto. Il formato csv usa gli stessi campi con intestazione.

Per verificare che i documenti sorgente non vengano modificati dalla pipeline, creare uno snapshot prima della build, uno dopo la build e poi confrontarli:

uv run sanikey document-integrity before
uv run sanikey document-integrity after
uv run sanikey document-integrity check

Il comando usa i pazienti abilitati in accounts.toml e scrive per ciascun id paziente i file PATIENT-before.sha256, PATIENT-before-mtime.tsv, PATIENT-after.sha256 e PATIENT-after-mtime.tsv nella directory local-data. Usare --patient per limitare il controllo a un paziente e --output-dir per salvare gli snapshot in un'altra directory.

Durante la build, SaniKey tenta di estrarre testo dai formati supportati:

  • .txt: contenuto testuale diretto;
  • .md: contenuto testuale diretto e rendering HTML Markdown nel frontend;
  • .pdf: testo digitale con PyMuPDF e OCR con OCRmyPDF quando necessario;
  • .jpg, .jpeg, .png: documento immagine consultabile, senza OCR diretto;
  • .docx, .odt, .rtf, .htm, .html: testo tramite Pandoc;
  • .xlsx, .xlsm, .xlsb, .xls, .ods: celle tramite python-calamine;
  • .doc: conversione tramite LibreOffice o soffice, se disponibile;
  • .zip, .7z, .rar, .tar.xz: inventario dei file contenuti nell'archivio.

Pandoc e' un requisito del sistema di build per l'estrazione dei documenti DOCX, ODT, RTF e HTML. LibreOffice o soffice resta richiesto soltanto per i documenti legacy .doc.

Le misurazioni longitudinali come peso, pressione, glicemia e INR si importano con un passaggio esplicito:

uv run sanikey import-observations PATIENT
uv run sanikey build-patient PATIENT

Il manifesto metadata/observation_imports.toml associa ogni file sorgente a una serie clinica e mappa le colonne. Sono accettati CSV UTF-8 e fogli .xlsx, .xlsm, .xlsb, .xls, .ods. Se manifesto o sorgenti cambiano, la build chiede di rieseguire import-observations.

Durante scan-documents, process-dicom e build-patient, gli archivi e le immagini disco supportate vengono anche estratti in una staging area generata sotto local_build/staging/containers. Il contenitore originale resta il documento autorevole; i membri estratti sono documenti derivati con provenance verso il contenitore, path interno e SHA256 proprio. Se un archivio è cifrato, corrotto o non leggibile, il contenitore resta catalogato e il problema viene registrato come avviso. process-dicom --no-stage-containers esegue il solo catalogo dei sorgenti e delle espansioni già presenti. L'output predefinito di process-dicom è una riga per archivio/supporto trovato e segnala ok, nessuno studio DICOM, piu studi DICOM: N oppure problema: ...; usare process-dicom --verbose per contatori e tabella tecnica degli studi.

I file ISO DICOM consegnati dagli ospedali sono conservati come documenti sorgente. Gli archivi .zip, .7z, .rar e .tar.xz sono trattati inizialmente come archivi generici e promossi a supporti DICOM solo se contengono DICOMDIR, file .dcm, path DICOM riconoscibili, immagini disco .iso/.img, ZIP annidati con contenuto DICOM o file con magic bytes DICOM. Quando vengono estratti in staging, eventuali immagini disco .iso o .img annidate vengono espanse a loro volta. I file DICOM interni sono catalogati come DICOM e non passano dall'OCR o dall'estrazione testo ordinaria. Nel frontend non vengono mostrati i singoli file DICOM: la consultazione mostra schede aggregate per studio, con numero istanze, UID quando disponibile e link di apertura del viewer HTML quando rilevato. I supporti e i file tecnici non vengono presentati come documenti clinici ordinari. Se uno studio DICOM viene catalogato senza viewer HTML, SaniKey usa il visualizzatore integrato basato su anteprime JPEG non diagnostiche e mostra DICOMDIR per viewer professionale quando disponibile. Resta un'anomalia solo quando non esiste nessuna azione utile. Per le immagini disco SaniKey prova prima il comando 7z; se il file è un ISO valido ma 7z non riesce ad aprirlo, ritenta con bsdtar quando disponibile. Quando le istanze DICOM sono leggibili, SaniKey usa pydicom per raggrupparle in studi clinici tramite StudyInstanceUID. Se è presente un DICOMDIR, i record STUDY vengono usati per creare gli studi anche quando il supporto contiene più arborescenze. Se lo stesso studio è rilevato sia dal DICOMDIR sia dalle istanze DICOM, SaniKey conserva un solo record di studio prima della scrittura nel database. I file DICOM privi di metadati leggibili restano catalogati singolarmente come fallback diagnostico. I file tecnici dei viewer inclusi nei supporti, per esempio runtime Java, DLL, manuali, HTML di help o asset applicativi, devono essere esclusi con pattern di ingestione espliciti come **/Viewer-Windows/**, **/jre/** o **/assets/**. I membri esclusi restano tracciati nel manifest di staging ma non entrano nella pipeline documentale ordinaria. Se lo staging contiene un viewer HTML consultabile, SaniKey preferisce entrypoint IHE PDI come IHE_PDI/PAGES/STUDIES/*.HTM, poi altre pagine HTML note come index.html, index.htm, default.htm o start.htm. In export USB questi viewer vengono copiati sotto patients/<id>/dicom-viewers/ e aperti dal frontend con un link relativo in un nuovo tab del browser. Il manifest di staging può essere molto grande perché registra ogni membro estratto dai contenitori per audit e verifica manuale; non è un report compatto da leggere integralmente in terminale.

Per i PDF, SaniKey sceglie automaticamente il provider:

  • usa PyMuPDF (fitz) quando il PDF contiene testo digitale sufficiente;
  • passa a OCRmyPDF, se disponibile come comando di sistema, quando PyMuPDF manca o produce testo vuoto/insufficiente.

OCRmyPDF è quindi una dipendenza di sistema supportata. Se nessun provider è disponibile, il PDF resta catalogato ma l'estrazione testo viene saltata con un avviso esplicito sui provider mancanti o insufficienti. SaniKey configura OCRmyPDF per produrre un PDF temporaneo normale invece di un PDF/A, perché conserva solo il sidecar testuale. Se OCRmyPDF fallisce durante l'ottimizzazione del PDF temporaneo, SaniKey ritenta senza ottimizzazione. I avvisi registrati nel report sono sintetici e non includono il log completo del tool esterno. Se anche il retry fallisce e il numero di pagine è disponibile, SaniKey ritenta su intervalli di pagine con una ricerca dicotomica per indicare la prima pagina del PDF originale che riproduce il problema.

Il testo estratto con successo viene salvato nella tabella SQLite document_text e indicizzato in document_fts insieme a titolo, categoria e tag. I file DICOM restano esclusi dall'estrazione testo. I documenti .md e i campi TOML documentati come Markdown, incluso clinical_summary.toml, vengono convertiti in HTML durante la build. L'HTML grezzo presente nel Markdown viene escapato; il frontend usa solo l'HTML generato dalla pipeline.

Glossario medico per OCR

src/sanikey/assets/ocr/tesseract-medical-it.user-words e' un glossario versionato in formato Tesseract: UTF-8, un lemma o abbreviazione per riga, senza commenti. Contiene termini clinici e di laboratorio italiani, latino-medici e inglesi correntemente presenti nei referti italiani, oltre a unita' e abbreviazioni comuni.

Il file e' pensato per essere modificato localmente dall'utilizzatore e condiviso tramite Git quando le aggiunte sono generali e verificate. Aggiungere solo forme corrette e canonicali, non errori OCR: per esempio HbA1c, non HbAlc. Conservare l'ultima riga terminata da newline e una voce per riga.

Quando SaniKey deve ricorrere all'OCR di un PDF, passa automaticamente il glossario a OCRmyPDF tramite --user-words; OCRmyPDF lo inoltra a Tesseract. Il glossario non viene applicato ai PDF il cui testo digitale viene estratto da PyMuPDF, né alle immagini sorgente aperte direttamente nel frontend.

Le immagini sorgente .jpg, .jpeg e .png restano documenti consultabili e apribili dal frontend, ma SaniKey non esegue OCR diretto con Tesseract su questi file. Questa scelta evita testo rumoroso e tempi di build non necessari. I PDF scansionati restano invece gestiti dalla pipeline PDF con OCRmyPDF quando serve. L'azione Apri originale apre il file in una nuova scheda del browser, cosi' la ricerca e la posizione di consultazione restano disponibili nella scheda iniziale.

Costruire un Archivio

Costruisci un singolo paziente:

uv run sanikey build-patient patient-a --mode full

Il comando stampa un riepilogo leggibile con conteggi, percorsi degli artefatti principali e path del report. documenti= conta i documenti sorgente deduplicati; documenti_derivati=, istanze_dicom= e record_totali= distinguono contenuti estratti dai contenitori e istanze DICOM. documenti_estratti= conta i documenti elaborati dall'estrazione testo nella run corrente; documenti_cached= conta i documenti invariati riusati dalla cache incrementale. In modalità incremental, predefinita, SaniKey riusa il testo estratto quando document_id, path, tipo, SHA256 e provenance del documento coincidono con la cache in local_build/cache/extracted_text.json. In modalità full, l'estrazione testo viene sempre rieseguita. Il database viene comunque rigenerato dall'inventario corrente, usando testo nuovo o cache, per evitare record obsoleti.

Per riesaminare i PDF acquisiti da scanner, senza alterare i PDF nativi prodotti dal laboratorio o da altri sistemi digitali, usare:

uv run sanikey build-patient patient-a --mode full --force-pdf-ocr

L'opzione classifica un PDF come scannerizzato soltanto quando ogni sua pagina contiene un'immagine raster che copre quasi tutta la pagina. Solo questi PDF vengono inviati nuovamente a OCRmyPDF/Tesseract con il glossario medico; un PDF nativo resta estratto con PyMuPDF. I PDF senza testo sufficiente continuano a usare OCRmyPDF anche senza l'opzione. Gli avvisi lunghi o ripetitivi non vengono serializzati in stdout: sono conservati nel report JSON indicato dalla riga report=.... Gli avvisi sui documenti duplicati restano visibili anche in stdout perché richiedono una decisione manuale. Anche build-patient usa punti di avanzamento su stderr quando il terminale e' interattivo; usare --no-progress per disattivarli.

Costruisci tutti i pazienti abilitati:

uv run sanikey build-all --mode full

La build scrive gli artefatti generati nella directory local_build configurata per il paziente, inclusi:

  • medical_archive.db
  • export JSON
  • file statici del frontend
  • manifest, checksum e report di build

Il frontend e' pensato per la consultazione diretta dalla chiavetta USB. I dati necessari alla prima schermata sono esportati anche in web/data.js, caricato come script locale, così Chrome e gli altri browser non devono usare fetch() su URL file://. La ricerca rapida nel box in alto cerca in documenti, farmaci, terapie, problemi, procedure, osservazioni e studi DICOM. I risultati vengono raggruppati per sezione e mostrano link contestuali con conteggi dentro il pannello dei risultati, in modo che il medico possa saltare subito alla parte utile senza una seconda barra di navigazione sotto la ricerca. Il bottone Ricerca avanzata cambia lo stesso box di ricerca e carica al primo uso web/content-search.js, cercando anche nel testo estratto da PDF, documenti Office e file testuali. La ricerca avanzata combina quei risultati documentali con gli stessi metadati clinici della ricerca rapida. Gli aiuti di ricerca base e avanzata sono separati, restano accanto al rispettivo bottone e si aprono in finestre modali locali richiudibili.

Sugli schermi larghi l'archivio usa due pannelli: il nome della sezione la apre a sinistra e la freccia associata la apre a destra. Su schermi stretti resta un solo pannello e le frecce non vengono mostrate, per evitare due controlli con lo stesso effetto.

La sintassi della ricerca avanzata è case-insensitive e accent-insensitive. Supporta parole, frasi tra virgolette, AND, OR, NOT e parentesi. Parole adiacenti equivalgono a AND, quindi creatinina 2024 è equivalente a creatinina AND 2024.

Fogli Illustrativi Della Terapia

I farmaci sono descritti normalmente in metadata/medications.toml: il paziente o l'operatore non inserisce AIC, URL o altri codici AIFA. Prima della build, su Linux con Internet, eseguire per ciascun paziente:

uv run sanikey resolve-medication-leaflets PATIENT

Il comando cerca sia il nome commerciale sia il principio attivo e confronta, quando presenti, principio attivo, forma e dosaggio curati. Per esempio una terapia Atenololo 100 mg compresse non conferma formulazioni da 25 o 50 mg. I candidati compatibili in ogni campo sono confermabili automaticamente; quelli che coincidono nel nome o nei principi attivi ma differiscono in forma o dosaggio restano esplicitamente da verificare. I risultati senza alcuna corrispondenza commerciale o di principio attivo non vengono mostrati. Se trova un solo candidato, lo conferma e scrive metadata/medication_leaflets.toml. Se ne trova più di uno, su un terminale interattivo ncurses mostra due pannelli: a sinistra le schede dei candidati AIFA con forma, dosaggio, principi attivi, titolare e confezioni; a destra il farmaco e le terapie così come risultano dai TOML curati. Usare frecce o j/k per scegliere, Invio o a per approvare, r per rifiutare e passare al farmaco successivo. s avvia una ricerca manuale AIFA nella stessa schermata; n registra che il farmaco non ha un foglio illustrativo AIFA applicabile (per esempio un integratore); q, Esc o x escono immediatamente senza modificare il file. In una pipe, in CI o in uno script stampa la lista e attende una scelta esplicita:

uv run sanikey resolve-medication-leaflets PATIENT --select FARMACO=NUMERO

Per la ricerca manuale non interattiva e per registrare lo stato non_aifa:

uv run sanikey resolve-medication-leaflets PATIENT --query FARMACO=TESTO
uv run sanikey resolve-medication-leaflets PATIENT --mark-non-aifa FARMACO

Le scelte non_aifa sono conservate in medication_leaflets.toml e non sono interrogate di nuovo; rimuovere il relativo blocco solo quando il dato curato o la disponibilità AIFA cambia.

Alle esecuzioni successive verifica ogni riferimento già confermato nella banca dati AIFA e la disponibilita' di entrambi FI e RCP. Lo conserva senza chiedere intervento quando il riferimento e i documenti sono ancora disponibili. Se FI o RCP non sono disponibili, il riferimento non viene conservato e la selezione torna da confermare; non viene mai sostituito automaticamente con un altro medicinale commerciale. Usare --query FARMACO=TESTO per cercare e sostituire esplicitamente anche un riferimento gia' salvato. Se AIFA non è raggiungibile, conserva il riferimento e segnala che la verifica non è stata possibile.

Durante build-patient o build-all, SaniKey riscarica FI e RCP dei riferimenti confermati prima di ispezionare documenti e contenitori, così un problema AIFA viene segnalato subito. La build si interrompe se un farmaco in terapia non ha un riferimento AIFA o uno stato non_aifa, oppure se FI e RCP locali non sono disponibili. La scheda Terapia preferisce i PDF locali offline, indica la data dell'ultimo download riuscito e offre anche il collegamento AIFA per il controllo online. Le informazioni dei medicinali possono cambiare anche con lo stesso AIC; la copia locale resta quindi una fotografia alla data mostrata.

FI e RCP vengono accettati solo se AIFA restituisce entrambi come PDF validi; un aggiornamento incompleto non sostituisce la coppia locale precedente. In caso di errore la build indica farmaco, tipo di documento, stato o dettaglio restituito da AIFA e URL richiesto, così da distinguere un riferimento non più disponibile da un'indisponibilita' temporanea del servizio.

La sezione Sintesi Clinica mostra una dashboard clinica sempre consultabile. Include, quando presenti, problemi, terapie, farmaci, osservazioni e procedure. Le terapie sono arricchite con nome commerciale, principio attivo, dosaggio, schedula, istruzioni, periodo e ruolo. Quando sono presenti, hanno anche un bottone di primo livello Terapia per l'accesso diretto senza passare dalla sintesi generale. Gli studi DICOM hanno una sezione autonoma Studi DICOM, visibile quando il payload contiene studi catalogati, con schede sintetiche per data/UID quando disponibili e numero di istanze. Gli studi senza viewer HTML sono segnalati come anomalie, mentre non viene mostrata la lista di ogni singola slice. Il riepilogo tecnico con conteggi e' in fondo alla sintesi clinica.

Quando sono disponibili serie longitudinali, la sezione Parametri comprende parametri estratti dai documenti, peso e pressione, osservazioni curate e serie importate da fogli CSV o Excel. Sugli schermi larghi apre l'elenco delle serie nella scheda sinistra e, nella destra, grafico, punti e documenti della serie selezionata. Sugli schermi stretti mantiene la consultazione in una sola scheda. Il clic su un punto con documento sorgente apre direttamente l'originale. Il riquadro informativo dei punti indica sempre l'origine: nome del PDF per i punti estratti, oppure file e foglio CSV/Excel per quelli importati. Per una serie di pressione arteriosa, il grafico mostra sullo stesso asse le linee Sistolica, Diastolica e, quando presente, Polso.

Personalizzare la Consultazione

L'aspetto e il comportamento iniziale del frontend possono essere configurati in config/accounts.toml. I valori in [global.ui] valgono per tutti i pazienti; un eventuale blocco ui dentro una voce [[person]] li sovrascrive solo per quel paziente.

[global.ui]
accent_color = "#2563eb"
density = "comfortable"
default_tab = "documents"
timeline_order = "desc"
document_link_mode = "usb-relative"
subtitle = "Archivio sanitario personale"
background_image = "config/assets/background.png"
background_opacity = 0.10

[global.search]
dictionary = "config/search-dictionary.toml"
advanced_index_warning_mb = 25

[global.usb]
usb_uuid = "MANUAL-TEST-USB"

[[person]]
id = "patient-a"
display_name = "Patient A"
source_documents = "local-data/patient-a/documents"
metadata_directory = "local-data/patient-a/metadata"
local_build = "local-data/generated/patient-a"

[person.ui]
default_tab = "timeline"
subtitle = "Archivio Patient A"

I valori ammessi sono:

  • density: compact, comfortable;
  • default_tab: documents, advanced, timeline, summary;
  • timeline_order: desc, asc;
  • document_link_mode: usb-relative;
  • accent_color: colore esadecimale #rrggbb;
  • subtitle: testo libero breve;
  • background_image: immagine opzionale copiata in web/assets/ durante la build e visibile su tutta la pagina, inclusa l'intestazione;
  • background_opacity: numero tra 0 e 1, da mantenere basso per non compromettere leggibilità;
  • dictionary: TOML opzionale per sinonimi e normalizzazioni della ricerca avanzata;
  • advanced_index_warning_mb: soglia oltre la quale la build segnala che content-search.js è grande, senza bloccare l'export.

Il dizionario di ricerca contiene sezioni chiuse:

[terms]
rx = ["radiografia", "raggi x"]
rmn = ["risonanza magnetica", "rm"]
tac = ["tc", "tomografia"]

[months]
gennaio = ["01", "1"]
febbraio = ["02", "2"]

Le espansioni sono simmetriche: cercare rx trova anche radiografia, e cercare radiografia trova anche rx.

Esportare su USB

Costruisci ed esporta tutti i pazienti abilitati verso una radice USB o una directory simulata:

uv run sanikey deploy-usb /path/to/usb-root

Per esportare artefatti gia' generati:

uv run sanikey export-usb /path/to/usb-root

export-usb e deploy-usb usano punti di avanzamento su stderr quando il terminale e' interattivo. Le fasi principali sono generazione immagine, manifest/checksum e copia verso il target. Usare --no-progress per un output strettamente testuale o facilmente copiabile. Quando il target e' una chiavetta fisica montata sotto /run/media o /media, export-usb verifica l'UUID atteso se configurato, controlla exFAT quando require_exfat = true, controlla lo spazio libero e preferisce rsync quando copy_strategy = "rsync-preferred". Se rsync non e' installato, usa la copia Python; se rsync parte e fallisce, il comando fallisce senza fallback silenzioso. Con rsync-preferred il target esistente non viene svuotato prima della copia: rsync riusa quindi i file invariati e, con --delete, rimuove quelli ormai obsoleti. La strategia python mantiene invece la sostituzione completa della directory di destinazione.

Valida la struttura esportata:

uv run sanikey validate-usb /path/to/usb-root

Apri la pagina iniziale generata dalla radice USB:

index.html

Struttura USB

Un export USB generato contiene:

SANIKEY-MANIFEST.json
index.html
patients/
  patient-a/
    documents/
    medical_archive.db
    web/
      index.html

SANIKEY-MANIFEST.json registra i pazienti esportati e i checksum usati da validate-usb.

Backup e Ripristino

L'export USB è un artefatto portatile di consegna, non il backup autorevole. Esegui il backup delle directory sorgente private, delle directory dei metadati curati, delle radici di build locali e di config/accounts.toml secondo la policy operativa locale.

Risoluzione dei Problemi

Usa prima questi controlli:

  • uv run sanikey validate-config
  • uv run sanikey resolve-medication-leaflets PATIENT-ID
  • uv run sanikey build-patient PATIENT-ID --mode full
  • uv run sanikey validate-usb /path/to/usb-root
  • uv run python scripts/validate_repo.py

Gli errori di configurazione indicano di solito un file privato mancante, un percorso risolto nella base sbagliata, un identificativo paziente non valido o una violazione degli invarianti di privacy.