# Ristrutturazione delle Evidence — disegno approvato **Stato:** approvato il 24 agosto 2026 **Sostituisce:** `docs/plans/2026-08-18-evidence-canonica-design.md` **Ambito:** authoring, revisione, pubblicazione, indicizzazione e uso runtime delle Evidence ## 1. Obiettivo Questo disegno introduce un processo semplice e verificabile per trasformare documenti di partenza non necessariamente ben organizzati in Evidence strutturate, revisionabili da una persona e ricercabili in modo efficace da ThothII. La soluzione deve: 1. partire dai testi oggi presenti nel repository del workspace; 2. riorganizzarli senza inventare informazioni; 3. conservare sorgenti e risultato nello stesso repository Git; 4. affidare a Git la revisione e l'approvazione umana; 5. indicizzare soltanto le versioni approvate; 6. sfruttare Qdrant senza moltiplicare collezioni e componenti; 7. inserirsi nel workflow modulare attuale, nel quale Evidence è un modulo autonomo. La fonte di verità rimane sempre il repository Git. Qdrant è un indice derivato che può essere ricostruito. ## 2. Principio guida Il processo è diviso in due percorsi distinti. - Il **percorso di authoring** prepara e revisiona le Evidence fuori dalle sessioni domanda→SQL. - Il **percorso runtime** è in sola lettura e consulta esclusivamente Evidence già pubblicate. ```mermaid flowchart LR S["Testi sorgente"] --> P["Pre-processing"] P --> C["Evidence curate"] C --> R["Revisione Git umana"] R --> M["Merge e attivazione revisione"] M --> I["Indicizzazione atomica"] I --> Q["Qdrant: indice attivo"] Q --> E["Evidence Module"] E --> W["Workflow F1-F8"] ``` Una sessione può proporre una nuova formula o segnalare una lacuna, ma non modifica il repository e non pubblica autonomamente conoscenza. ## 3. Struttura nel repository del workspace Ogni workspace adotta questa struttura sotto la propria directory `evidence/`: ```text evidence/ ├── README.md ├── source/ │ └── ... documenti originali ... ├── curated/ │ ├── glossary/ │ ├── domain/ │ ├── enum/ │ ├── example/ │ ├── mapping/ │ ├── normalization/ │ ├── formula/ │ └── reference/ ├── manifest.yaml └── evaluation.yaml ``` ### 3.1 `source/` Contiene i documenti originali. La prima versione accetta file Markdown, testo UTF-8 e file `.sql.md`. Un URL può essere descritto in un documento, ma non viene scaricato né interpretato automaticamente. I sorgenti vengono preservati: il pre-processing non li riscrive. ### 3.2 `curated/` Contiene una Evidence Unit per file. Le sottodirectory rendono immediatamente visibile il tipo anche a un lettore umano. Il campo `kind` nel documento resta comunque obbligatorio: la directory aiuta la navigazione, il campo è il contratto macchina. ### 3.3 `manifest.yaml` È gestito dal comando di preparazione e registra: - hash di ciascun sorgente; - Evidence Unit derivate da quel sorgente; - identificatori stabili; - versione del processo di preparazione; - unità orfane da controllare. Il manifest permette di elaborare soltanto ciò che è cambiato. Non sostituisce Git e non contiene lo stato di approvazione. ### 3.4 `evaluation.yaml` Contiene inizialmente circa venti domande rappresentative e gli identificatori delle Evidence che ci aspettiamo di recuperare. È il controllo minimo per evitare di considerare “migliore” una ricerca soltanto perché sembra sofisticata. ## 4. Una struttura comune, otto tipi distinti La separazione tra tipi non viene eliminata. Ogni documento ha un involucro comune e una parte specializzata determinata da `kind`. ### 4.1 Campi comuni ```yaml schema_version: 1 id: formula:fascia-pediatrica title: Fascia pediatrica kind: formula purposes: - sql_generation - schema_linking applies_to: concepts: - fascia pediatrica tables: - clinical.patient columns: - clinical.patient.birth_date language: it provenance: source_file: source/10-domini-clinici/paziente.md source_sha256: sha256:0123456789abcdef... review_items: [] ``` I campi hanno ruoli diversi: - `kind` dice **che cosa contiene** il documento; - `purposes` dice **in quali attività può essere utile**; - `applies_to` dice **a quali concetti o elementi del database si riferisce**; - `provenance` permette di risalire al testo di origine; - `review_items` rende visibili i dubbi ancora da risolvere. ### 4.2 Tipi iniziali | `kind` | Contenuto | Esempio d'uso | | --- | --- | --- | | `glossary` | Definizione, sinonimi e varianti linguistiche | Capire che “ricovero” e “degenza” possono indicare lo stesso concetto | | `domain` | Regole e vincoli del dominio | Interpretare correttamente un episodio clinico | | `enum` | Valori ammessi e loro significato | Tradurre “dimesso” nel codice memorizzato nel DWH | | `example` | Domanda esemplificativa e interpretazione attesa | Riconoscere una formulazione già documentata | | `mapping` | Collegamento fra concetto e schema fisico | Individuare tabella e colonne pertinenti | | `normalization` | Regole di normalizzazione | Uniformare codici, date o varianti testuali | | `formula` | Espressione SQL riutilizzabile e relativi input | Calcolare la fascia pediatrica dalla data di nascita | | `reference` | Un riferimento esterno che è esso stesso contenuto recuperabile | Proporre all'utente il link a una specifica linea guida | Un URL che documenta un'altra Evidence appartiene alla sua `provenance`. Un URL che deve essere recuperato come risposta autonoma è invece una Evidence `reference`. ### 4.3 Dati specifici per tipo La parte specializzata è una unione discriminata: ogni `kind` ammette e richiede campi diversi. Alcuni esempi: ```yaml # formula formula: concept: fascia pediatrica columns: - clinical.patient.birth_date sql: | CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END ``` ```yaml # reference reference: url: https://example.org/linea-guida label: Linea guida clinica description: Criteri usati per classificare gli episodi. ``` ```yaml # enum enum: column: clinical.episode.discharge_status values: D: dimesso T: trasferito ``` I tipi restano quindi sfruttabili sia in validazione sia in ricerca. Una formula non è un semplice testo etichettato: possiede obbligatoriamente un concetto, le colonne di input e SQL valido come contenuto strutturato. ## 5. Pre-processing dei testi sorgente Il comando concettuale è: ```text tht evidence prepare ``` Per l'utente è una sola operazione. Internamente esegue quattro passaggi. ### 5.1 Estrazione deterministica Il sistema: - individua i file ammessi in `evidence/source/`; - verifica dimensione, codifica UTF-8 e percorso sicuro; - calcola l'hash del contenuto; - confronta il risultato con `manifest.yaml`; - carica, quando esiste, la precedente versione curata collegata al sorgente. Un sorgente invariato non viene nuovamente elaborato. ### 5.2 Normalizzazione deterministica Prima del modello vengono normalizzati soltanto aspetti meccanici: - terminatori di riga e Unicode; - spaziatura e intestazioni palesemente riconoscibili; - elenchi, tabelle, blocchi SQL e URL; - metadati già esplicitamente presenti; - riferimenti a tabelle e colonne riconoscibili. Questa fase non interpreta il significato e non inventa strutture semantiche. ### 5.3 Una sola ristrutturazione assistita dal modello Per ogni sorgente cambiato il modello riceve: - il testo normalizzato; - gli otto schemi ammessi; - le regole “non inventare” e “segnala il dubbio”; - le precedenti Evidence curate derivate da quel sorgente; - gli identificatori già assegnati. Può: - assegnare titoli; - classificare il tipo; - separare un sorgente in più Evidence Unit; - riordinare e riscrivere per chiarezza; - compilare campi strutturati con fatti presenti nel sorgente. Non può: - fondere automaticamente sorgenti diversi; - aggiungere fatti non documentati; - risolvere silenziosamente un'ambiguità; - cancellare un'unità precedentemente revisionata. Pi viene usato in modalità non interattiva e senza strumenti di scrittura. È un dettaglio interno del comando, non una nuova tipologia di sessione ThothII. ### 5.4 Validazione deterministica L'output del modello non viene scritto direttamente. Viene prima controllato: - schema comune e schema specifico del `kind`; - unicità e stabilità degli identificatori; - appartenenza alle enumerazioni ammesse; - esistenza e hash del sorgente; - correttezza sintattica di URL, tabelle, colonne e SQL dove applicabile; - assenza di credenziali; - coerenza tra directory e `kind`; - assenza di collegamenti a sorgenti diversi nella stessa unità. Esistono tre esiti. | Esito | Comportamento | | --- | --- | | Valido | Il documento è pronto per la revisione Git | | Valido con dubbi | Il documento viene scritto con `review_items`; non è indicizzabile | | Non valido | Il documento non è pubblicabile e il rapporto spiega l'errore | Un dubbio reale può essere mantenuto soltanto se il revisore lo trasforma in una limitazione esplicita del contenuto e svuota `review_items`. ## 6. Aggiornamenti incrementali e protezione delle correzioni umane La precedente versione curata è un input, non un file usa-e-getta. In questo modo il modello può proporre una modifica minima senza ricominciare da zero. Il comando: - si rifiuta di operare se `evidence/curated/` o `evidence/manifest.yaml` contengono modifiche Git non salvate; - mantiene gli ID associati a contenuti che rappresentano ancora la stessa unità; - mostra come diff le variazioni proposte; - non modifica i file derivati da sorgenti invariati; - segnala come orfana un'unità il cui sorgente è stato rimosso; - non elimina mai automaticamente un'unità orfana. Git fornisce confronto, revisione, cronologia e recupero. Non viene introdotto un database di authoring parallelo. ## 7. Revisione e pubblicazione Il flusso di pubblicazione è: ```text prepare → revisione Git → validate → merge → attivazione workspace → preprocess evidence → nuova generazione Qdrant attiva ``` ### 7.1 Approvazione umana L'approvazione coincide con il normale processo Git del repository del workspace: 1. il curatore esegue `prepare` in un clone di authoring; 2. legge i documenti e il diff; 3. corregge i contenuti; 4. esegue `tht evidence validate`; 5. apre o approva la pull request; 6. esegue il merge. La prima versione non crea automaticamente branch, commit o pull request. ### 7.2 Quando un documento diventa Published Evidence Una Curated Evidence diventa Published Evidence soltanto quando: - appartiene a una revisione Git approvata e pulita; - la revisione è stata attivata dal registry di ThothII; - non contiene `review_items` irrisolti; - l'intero corpus supera la validazione; - la generazione Qdrant viene pubblicata atomicamente. Il descriptor filesystem deve indicizzare solo `curated/**/*.md`. I sorgenti e i file di supporto restano materializzati per tracciabilità, ma non entrano nell'indice. ## 8. Indicizzazione e generazioni L'indicizzazione continua a usare il meccanismo già implementato dal modulo Evidence: 1. legge la radice materializzata della revisione Git attiva; 2. valida nuovamente tutte le Evidence; 3. costruisce Evidence Fragment secondo sezioni semantiche; 4. genera le rappresentazioni dense; 5. chiede a Qdrant di generare la rappresentazione lessicale BM25; 6. carica i punti con la nuova `vector_generation`; 7. verifica manifest, conteggi e leggibilità; 8. rende attiva la nuova generazione; 9. conserva le generazioni precedenti previste dalla policy. Se uno dei passaggi fallisce, la generazione precedente rimane attiva. I punti caricati parzialmente vengono compensati secondo il meccanismo transazionale già esistente. ## 9. Qdrant spiegato senza presupporre conoscenze vettoriali ### 9.1 L'analogia della biblioteca Si può immaginare Qdrant come il catalogo di una biblioteca. - Le **Evidence Unit** sono i documenti completi conservati negli scaffali Git. - Gli **Evidence Fragment** sono le schede del catalogo relative alle singole sezioni. - I **vettori** sono rappresentazioni numeriche usate per confrontare una domanda con quelle schede. - Il **payload** è l'insieme delle etichette leggibili: tipo, scopo, tabelle, colonne, revisione e documento di origine. Qdrant non decide se una Evidence è vera e non sostituisce il documento. Aiuta soltanto a trovare rapidamente le schede più promettenti. ### 9.2 Ricerca per significato: vettore dense La rappresentazione dense descrive il significato generale di una frase. Permette, per esempio, di avvicinare “pazienti minorenni” a “fascia pediatrica” anche quando le parole non coincidono. È utile per il linguaggio naturale, ma può essere meno precisa con codici, acronimi, nomi di colonne e formule. ### 9.3 Ricerca per parole e identificatori: BM25 sparse La rappresentazione sparse conserva il peso delle parole presenti. È adatta a termini come `ICD-10`, `discharge_status`, `ADT`, un valore enum o un nome esatto di colonna. Qdrant 1.18.2 può generare questa rappresentazione direttamente sul server usando `qdrant/bm25`; per il corpus italiano si passa `language: italian` sia durante il caricamento sia durante la ricerca. Non serve aggiungere FastEmbed o un nuovo servizio. Il nome “sparse” significa soltanto che, tra moltissime parole possibili, ogni testo ne usa poche. Qdrant mantiene anche l'IDF: una parola rara pesa più di una parola presente quasi ovunque. ### 9.4 Perché combinarle Una domanda può richiedere contemporaneamente comprensione e precisione lessicale: > “Qual è la formula per distinguere la fascia pediatrica usando > `patient.birth_date`?” La ricerca dense riconosce il concetto; BM25 riconosce con forza “formula” e il nome della colonna. Qdrant esegue entrambe e produce due graduatorie. ### 9.5 Reciprocal Rank Fusion Reciprocal Rank Fusion, o RRF, combina le due graduatorie usando la posizione dei risultati invece di confrontare direttamente punteggi di natura diversa. In termini pratici: - un documento alto in entrambe le liste sale; - un documento molto forte in una sola lista può comunque emergere; - non occorre inventare una conversione fragile fra “similarità semantica” e “punteggio delle parole”. Si parte con i pesi predefiniti. Pesi diversi saranno introdotti soltanto se `evaluation.yaml` dimostrerà un miglioramento. ### 9.6 Il ruolo dei metadati Ogni punto Qdrant conserva almeno: ```text workspace_id workspace_revision vector_generation record_kind evidence_id evidence_kind purposes concepts tables columns language source_file source_sha256 fragment_ordinal ``` I metadati hanno due usi: - workspace, revisione e generazione sono filtri obbligatori di sicurezza; - tipo, scopo e ambito orientano la ricerca oppure diventano filtri quando il chiamante formula una richiesta esplicita. Durante la generazione SQL, per esempio, `formula` e `mapping` ricevono priorità, ma una regola `domain` molto pertinente può ancora apparire. Se il workflow chiede esplicitamente soltanto formule, `evidence_kind=formula` diventa invece un filtro vincolante. ### 9.7 Perché non creare una collezione per tipo Una domanda spesso attraversa più tipi: una formula può dipendere da un mapping, da un enum e da una regola di dominio. Collezioni separate richiederebbero più interrogazioni, fusione applicativa e più operazioni di manutenzione. La soluzione usa la collezione semantica già posseduta dal workspace e aggiunge vettori denominati `dense` e `bm25`. I payload indicizzati distinguono i tipi. È più semplice e permette a Qdrant di eseguire ricerca ibrida e filtri nella stessa Query API. ### 9.8 Perché indicizzare frammenti ma restituire unità Un documento lungo può contenere sezioni diverse. Un unico vettore ne diluirebbe il significato; frammenti arbitrari di lunghezza fissa spezzerebbero invece formule o regole. La divisione segue intestazioni e campi tipizzati. Qdrant trova i frammenti, poi l'Evidence Module li raggruppa per `evidence_id` e restituisce l'unità completa con provenienza e citazione. ### 9.9 Cosa non introduciamo nella prima versione - una collezione per ogni tipo; - ColBERT o multivettori late-interaction; - un reranker basato su un altro modello; - pesi RRF regolati a mano senza misurazioni; - un servizio separato per BM25; - ricerca automatica sul web. Queste possibilità rimangono future ottimizzazioni, non prerequisiti. Riferimenti tecnici ufficiali: - [Qdrant: Text Search](https://qdrant.tech/documentation/search/text-search/) - [Qdrant: server-side BM25](https://qdrant.tech/documentation/inference/inference-bm25/) - [Qdrant: Hybrid Queries e RRF](https://qdrant.tech/documentation/search/hybrid-queries/) - [Qdrant: payload indexing](https://qdrant.tech/documentation/manage-data/indexing/) - [Qdrant: multitenancy](https://qdrant.tech/documentation/manage-data/multitenancy/) ## 10. Contratto di ricerca del modulo Evidence Il workflow non costruisce query Qdrant. Usa una sola interfaccia concettuale: ```python search( query: str, purpose: EvidencePurpose, context: EvidenceSearchContext, ) -> list[EvidenceResult] ``` `EvidenceSearchContext` può specificare tabelle, colonne, concetti e, solo quando necessario, tipi obbligatori. Il modulo Evidence possiede interamente: - generazione della query dense; - query BM25 con lingua coerente; - filtri su revisione e generazione; - RRF; - preferenze per `kind`, `purpose` e `applies_to`; - raggruppamento dei frammenti; - risoluzione di provenienza e citazioni; - controllo della revisione attiva. Il workflow riceve candidati spiegabili, mai verità automatiche. ## 11. Inserimento nel workflow modulare ThothII Evidence rimane un modulo autonomo con due responsabilità pubbliche. ### 11.1 Authoring ```text prepare → validate → evaluate ``` Questa superficie è usata dal curatore e non dalle sessioni. ### 11.2 Runtime ```text search → resolve citation → project into session ``` F1, F3 e F4 passano `purpose` e contesto al modulo. Non conoscono collezioni, nomi di vettori, generazioni o sintassi Qdrant. Le istruzioni Pi relative alla consultazione delle Evidence vengono spostate in frammenti del modulo Evidence e poi proiettate nel `SKILL.md` generato, seguendo il meccanismo modulare già usato da Disambiguation e Memory. ## 12. Formule Le formule approvate oggi presenti nello store `formulas/*.sql.md` vengono convertite in Evidence `kind: formula`. Dopo la migrazione non esistono due archivi runtime. Una nuova formula scoperta in F4 segue invece questo percorso: ```text sessione → Formula proposal nell'artefatto di sessione → importazione di manutenzione → Curated Evidence formula → revisione Git → Published Evidence ``` Le decisioni `concept_formula_approved` e `concept_formula_rejected` continuano a descrivere la scelta fatta nella singola sessione. Non equivalgono alla pubblicazione globale nel workspace. ## 13. Comportamento in caso di errore ### 13.1 Durante l'authoring - un file non UTF-8, troppo grande o strutturalmente invalido produce un errore chiaro; - un dubbio semantico produce un `review_item`; - un albero Git sporco impedisce la sovrascrittura delle modifiche umane; - un sorgente rimosso produce un'unità orfana, non una cancellazione. ### 13.2 Durante l'indicizzazione - la nuova generazione viene preparata senza toccare quella attiva; - un caricamento o una verifica falliti non cambiano il puntatore attivo; - i dati parziali vengono rimossi quando possibile e comunque non sono leggibili dal runtime perché manca l'attivazione. ### 13.3 Durante una sessione Se Qdrant, il corpus attivo o la revisione attesa non sono disponibili: - il risultato Evidence è vuoto; - viene emesso un avviso esplicito; - non vengono usate revisioni precedenti; - la sessione può continuare con gli altri meccanismi e con i normali gate umani. Questo è il comportamento fail-closed già presente e viene preservato. ## 14. Valutazione minima `tht evidence evaluate` esegue le domande in `evaluation.yaml` contro l'indice attivo e riporta almeno: - quante domande hanno trovato una Evidence attesa nei primi 5 e nei primi 10 risultati; - quali tipi attesi sono mancati; - quali query non hanno prodotto risultati; - revisione Git, generazione e configurazione di ricerca usate. La prima baseline deve essere salvata prima di regolare pesi o introdurre altri modelli. Il comando non modifica l'indice. ## 15. Comandi e responsabilità | Comando | Dove opera | Scrive | | --- | --- | --- | | `tht evidence prepare ` | clone Git di authoring | `curated/`, `manifest.yaml` | | `tht evidence validate ` | clone Git o CI | nulla | | `tht evidence evaluate ...` | indice attivo | solo rapporto su stdout/JSON | | `tht ... workspace preprocess evidence` | installazione/runtime | nuova generazione corpus e Qdrant | `prepare` non crea commit. `preprocess evidence` non modifica il repository Git. ## 16. Migrazione iniziale del workspace PSD Il corpus attuale comprende 36 file Markdown organizzati in glossario, domini clinici, enum, esempi NLQ, mapping e normalizzazione. La migrazione avviene così: 1. spostare gli originali sotto `evidence/source/`, conservandone la gerarchia; 2. eseguire `prepare` e generare `curated/`; 3. revisionare tutte le unità e risolvere i `review_items`; 4. importare eventuali formule approvate come `kind: formula`; 5. compilare circa venti query in `evaluation.yaml`; 6. configurare il descriptor con `patterns: ["curated/**/*.md"]`; 7. validare, fare merge e attivare la revisione; 8. ricostruire in modo controllato la collezione per il nuovo contratto dense+BM25; 9. eseguire `preprocess evidence`; 10. salvare la baseline di valutazione e svolgere una verifica umana F1/F3/F4. Non serve mantenere v1 e v2 attivi contemporaneamente nel runtime: Git conserva la vecchia revisione e il meccanismo delle generazioni conserva il rollback dell'indice. ## 17. Criteri di accettazione La prima versione è completa quando: 1. un sorgente poco strutturato produce una o più unità tipizzate senza perdere la provenienza; 2. sorgenti invariati sono un no-op; 3. modifiche umane non vengono sovrascritte; 4. un `review_item` impedisce l'indicizzazione; 5. tutte le otto varianti hanno validazione specifica; 6. le formule approvate sono ricercate tramite lo stesso modulo delle altre Evidence; 7. soltanto `curated/**/*.md` entra nel corpus runtime; 8. la collezione Qdrant espone `dense` e `bm25` e gli indici payload richiesti; 9. la Query API esegue i due prefetch e la fusione RRF; 10. risultati di frammenti della stessa unità vengono raggruppati; 11. una revisione o generazione non corrispondente restituisce zero Evidence e un avviso; 12. il set di valutazione produce un rapporto ripetibile; 13. una sessione completa continua a funzionare anche con Evidence non disponibili; 14. documentazione e comandi descrivono lo stesso contratto. ## 18. Decisioni rinviate Saranno considerate soltanto dopo la baseline: - pesi RRF diversi da quelli predefiniti; - reranking; - ColBERT o multivettori; - acquisizione automatica di PDF, Word, HTML o pagine web; - creazione automatica di branch e pull request; - fusione assistita di Evidence provenienti da sorgenti diversi. Queste esclusioni mantengono la prima implementazione comprensibile, realizzabile, manutenibile e documentabile.