Files
ThothII/docs/plans/2026-08-24-evidence-restructuring-design.md
T

23 KiB

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.
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/:

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

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:

# formula
formula:
  concept: fascia pediatrica
  columns:
    - clinical.patient.birth_date
  sql: |
    CASE WHEN age < 18 THEN 'pediatrica' ELSE 'adulta' END
# reference
reference:
  url: https://example.org/linea-guida
  label: Linea guida clinica
  description: Criteri usati per classificare gli episodi.
# 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 è:

tht evidence prepare <workspace-root>

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 è:

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:

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:

10. Contratto di ricerca del modulo Evidence

Il workflow non costruisce query Qdrant. Usa una sola interfaccia concettuale:

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

prepare → validate → evaluate

Questa superficie è usata dal curatore e non dalle sessioni.

11.2 Runtime

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:

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 <workspace-root> clone Git di authoring curated/, manifest.yaml
tht evidence validate <workspace-root> 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.