
Ho costruito una demo per riprodurre un famoso errore di IA. La mia baseline ha rifiutato di farlo.
L'errore che non riuscivo a far accadere
Ho iniziato questo build volendo ricreare un fallimento specifico e ben documentato. L'IA di matching dei pazienti legge una nota clinica come testo, quindi confonde parole che sembrano simili ma in medicina significano cose diverse. L'esempio canonico è limpido: uno studio di Fase III su anticoagulanti esclude i pazienti con un precedente cateterismo cardiaco, la nota di un paziente dice posizionamento di catetere venoso centrale, un matcher di similarità vede due procedure di cateterismo cardiovascolare, le valuta vicine e esclude un paziente che in realtà era idoneo. Valutazioni pubblicate confermano che modelli reali commettono proprio questo errore (Fierce Biotech, 2025). Volevo che la mia demo lo mostrasse in azione, e poi mostrasse il mio motore che lo intercetta.
Così ho scritto una baseline equa perché facesse da cattivo. Similarità coseno TF-IDF a livello di entità, n-grammi di parola e di carattere da 3 a 5, un vero metodo di similarità vettoriale. Gli ho perfino dato un setup generoso e ho validato in cross-validation la soglia di decisione a suo favore (ROC a 3-fold stratificata, Youden's J, seed 13, con clamping a t = 0.6932), perché un uomo di paglia non prova niente. Poi ho eseguito il caso del cateterismo cardiaco e ho aspettato l'esclusione ingiusta.
Non è arrivata. La baseline ha valutato le due frasi sul catetere ben al di sotto della propria soglia validata in cross-validation e ha restituito idoneo. L'errore attorno a cui avevo costruito l'intera demo semplicemente non si riproduceva.
Mi ero proposto di mettere in scena un fallimento famoso e ho scoperto che il mio cattivo onesto era troppo debole per commetterlo.
Il motivo si è rivelato istruttivo, e voglio essere preciso perché è facile esagerare. Una baseline lessicale sparsa non produce quella particolare falsa esclusione. Servono embedding semantici densi per avvicinare abbastanza quelle due frasi da far scattare la soglia. Aggiungere un modello di embedding pesante avrebbe gonfiato la demo in qualcosa che non puoi eseguire con un solo comando offline, quindi ho preso una decisione: tenere la baseline onesta e sparsa, e smettere di fingere che commetta un crimine che non può commettere. Quella scelta ha riorganizzato l'intero pezzo che sto costruendo. Se vuoi eseguirlo tu stesso, vive su veriprajna.com/it/demos/motore-di-ragionamento-per-l-eleggibilita-agli-studi-clinici.
Cos'è davvero una linea centrale?
Ho comunque tenuto il caso del cateterismo cardiaco: alla fine ha dimostrato qualcosa di meglio di un errore intercettato. Dimostra perché la risposta del mio motore sia affidabile in assoluto. Entrambi i concetti qui hanno identificatori SNOMED-CT reali e verificabili. Cateterismo venoso centrale è 392230005. Cateterismo cardiaco è 41976001. Puoi incollare entrambi in qualsiasi browser SNOMED pubblico e confermare che stanno su rami diversi della gerarchia. Non c'è alcun percorso is-a dall'uno all'altro. Una linea centrale non è un cateterismo cardiaco, e solo una gerarchia lo sa.
È l'intera tesi in un solo arco di un grafo. Un punteggio di similarità non può rappresentare "is-a." Può rappresentare soltanto "queste stringhe si assomigliano," e assomigliarsi non è lo stesso che significare la stessa cosa. Quando il mio motore valuta l'esclusione "nessun cateterismo cardiaco pregresso," non assegna alcun punteggio. Pone una domanda strutturale: il fatto verificato del paziente è sussunto dal concetto proibito? Cammina sull'ontologia, non trova alcun percorso di sussunzione e restituisce idoneo con una traccia in tre passaggi che nomina entrambi gli ID di concetto e l'arco del grafo che ha controllato.

Quando ho visto per la prima volta quella traccia renderizzarsi, ciò che mi ha colpito non è stato il verdetto. È stata la ricevuta sotto di esso. Il riquadro della baseline sulla stessa schermata mostra un numero di similarità senza provenance e niente di riproducibile. Il riquadro del mio motore nomina i due SCTID e l'esatta domanda is-a che ha posto. Una di queste un regolatore può depositarla. L'altra è un numero con un'alzata di spalle attaccata. Quel contrasto, non un errore intercettato, è ciò che il caso del cateterismo cardiaco guadagna davvero.
Una di queste può essere depositata da un regolatore. L'altra è un numero con un'alzata di spalle attaccata.
Avevo ancora bisogno di un paziente perso per davvero, così sono andato a cercare dove la mia baseline onesta fallisce davvero, e l'ho trovato in una sola parola: non. La cartella sintetica protagonista P-074 riporta la riga di nota "No evidence of diabetes." Una delle esclusioni del protocollo oncologico è "nessuna diagnosi di diabete mellito." La baseline vettoriale vede il token "diabetes" seduto proprio accanto al "diabetes" del criterio e li abbina a similarità 1.0. Un punteggio perfetto. Non ha alcun modello della negazione, quindi legge una frase che mette il diabete fuori come se lo avesse messo dentro, e esclude un paziente che era idoneo.
Questo è il paziente che il matcher scarta, ed è la scena che mi aspettavo originariamente dal caso del cateterismo cardiaco. La negazione è dove una baseline sparsa si rompe onestamente, alla sua stessa migliore soglia, senza trucchi.

Continuo a pensare a quanto sia silenzioso questo fallimento. Non c'è alcun messaggio di errore, alcun flag di bassa confidenza, alcun segnale che qualcosa sia andato storto. Il punteggio è 1.0, il più alto possibile, il massimo di certezza che il sistema possa mai avere. La baseline non è mai più certa che nel momento esatto in cui sbaglia di più. Un coordinatore che rivede una coda di questi non ha modo di sapere che questo particolare match perfetto è un paziente che avrebbe dovuto essere arruolato. Moltiplica tutto ciò su un protocollo e capisci perché l'80% dei trial manca le tempistiche di arruolamento (consenso di settore, 2025), e perché ogni fallimento di screening costa in media circa $1,200 (Antidote.me, 2025).
La baseline non è mai stata più fiduciosa che nel momento esatto in cui sbagliava di più. Non è un bug che puoi regolare via. È un errore di categoria.
Perché il verdetto vive fuori dal modello?
La baseline non è mai stata più fiduciosa che nel momento esatto in cui sbagliava di più. Non è un bug che puoi eliminare a colpi di tuning. È un errore di categoria.
Tutto ciò che viene dopo è codice deterministico che posso auditare. Prima che qualsiasi fatto proposto raggiunga una decisione, un verificatore avversariale lo sfida contro la nota letterale con tre controlli: lo span è davvero presente, è negato, e il soggetto è il paziente piuttosto che un familiare. Il fatto "No evidence of diabetes" fallisce il controllo di negazione e non raggiunge mai il motore. Sulla stessa cartella, "Family history of breast cancer" fallisce il controllo del soggetto, perché quella anamnesi appartiene a un familiare e non al paziente, e viene segnalato come rejected con il controllo fallito nominato.

Sull'intero gold set, questo verificatore ha rifiutato 7 istanze di fatto, 3 fatti errati distinti (una menzione di diabete negata, un'attribuzione di carcinoma mammario in anamnesi familiare e un farmaco allucinato piantato senza span di supporto), distribuiti su 4 dei 13 case-run valutati, tutti prima che potessero toccare un verdetto. Quando mi chiedono "come mi fido di ciò che l'agente ha estratto dalle mie note," questo pannello è l'intera risposta. Non ti chiedo di fidarti. Ti mostro cosa ha proposto e cosa è stato scartato e perché.
Poi il verdetto stesso è Python semplice che sta fuori dal framework dell'agente: un motore di logica deontica che valuta divieti, eccezioni temporali e requisiti sull'ontologia e un po' di aritmetica sulle date. Un modello non può sovrascrivere questo gate, perché il modello non è nella stanza quando il gate gira. È anche ciò che rende il motore riproducibile. Quando la logica è codice deterministico su un'ontologia fissa, rieseguire la stessa cartella produce la stessa risposta, byte per byte, ogni singola volta.
Il modello legge. Non vota. Quel singolo confine è ciò che rende una riesecuzione byte-identica.
L'unico numero a cui teneva il mio lettore di clinical-ops
Ho passato settimane a ottimizzare metriche che, alla fine ho ammesso a me stesso, non fanno perdere il sonno all'acquirente. L'accuratezza delle decisioni è un numero da classifica. Chi gestisce la fattibilità presso uno sponsor o una CRO non confronta i punteggi da classifica. Guarda una timeline di arruolamento che scivola, e ogni giorno di scivolamento è costoso. Il Tufts CSDD Impact Report (2024) colloca il costo di un ritardo di arruolamento a circa $800K al giorno in vendite di prescrizioni perse, e più alto nelle aree terapeutiche toccate da questa demo: circa $840K al giorno in oncologia e $1.4M al giorno in cardiovascolare. La complessità dei protocolli è salita del 139% nelle procedure di trial dal 2005 (IQVIA, 2026), il che significa più criteri, più clausole e più punti in cui un matcher testuale può sbagliare uno.
Così ho smesso di aprire con l'accuratezza e ho iniziato ad aprire con il numero che davvero mappa su quel dolore: i pazienti idonei che non hai perso. Su un gold set etichettato e fisso di 13 casi tratti da 7 pazienti sintetici su 2 protocolli sintetici, il mio motore perde 0 pazienti idonei. La baseline equa ne perde 3. Stesso set, stessa soglia validata in cross-validation a vantaggio della baseline.

Voglio essere esatto su cosa siano e non siano quei numeri. Sono l'output dell'harness su quel singolo set fisso di 13 casi, non una promessa open-world. Il 100% è "100% su questo gold set," mai "sempre giusto." Non ti dirò che TrialProof non sbaglia mai, perché non ho i dati per dirlo e non crederei a nessuno che lo dicesse. Ciò che posso dire è più stretto e, credo, più utile: su questo set il motore perde zero pazienti idonei, ogni decisione porta una traccia riproducibile, due decisioni si sono astenute in sicurezza con NEEDS-REVIEW quando mancava un lab o un vitale richiesto invece di indovinare, e rieseguire l'intero set è stato byte-identico, 13 su 13. Tutti i pazienti, le note e i protocolli sono fixture sintetiche, nessun record reale da nessuna parte. Puoi guardare ognuna di quelle esecuzioni su veriprajna.com/it/demos/motore-di-ragionamento-per-l-eleggibilita-agli-studi-clinici.
Il numero a cui tengo non è l'accuratezza. Sono i pazienti idonei che non ho scartato. Su questo set, sono zero persi contro i tre della baseline.
C'è anche una forma normativa in tutto questo, e la nominerò con attenzione. La guidance FDA di gennaio 2026 sul Clinical Decision Support è il framework rilevante per un ausilio di matching human-in-the-loop come questo. Ogni decisione emessa dal motore può esportarsi come un record CDISC SDTM IE, una riga per paziente e criterio, con il verdetto, la traccia di ragionamento, gli ID di concetto e l'operazione deontica. Non è un clearance e non ne sto rivendicando uno. È allineamento e direzione. Ma significa che la traccia non è una comodità di debugging. È un artefatto archiviabile, ed esiste per costruzione su ogni decisione piuttosto che come ripensamento.
A cosa continuo a tornare
Continuo a tornare al momento in cui il mio cattivo ha rifiutato di recitare la sua parte, perché ha cambiato la domanda che stavo ponendo. Per tre anni il campo ha chiesto come rendere il modello migliore nel decidere chi è idoneo. Prompt migliori, contesto più ampio, più retrieval, tutto mirato a rendere un sistema probabilistico abbastanza affidabile da decidere sull'arruolamento di un paziente. Ho passato il primo tratto di questo build dentro quella cornice anch'io, cercando di beccare un modello in un errore così da poter aggiustare il modello.
Ciò che alla fine ha fatto click è che era lo strato sbagliato. Un punteggio di similarità non può rappresentare "is-a," non può rappresentare "non," e non può rappresentare "a meno che la terapia non sia stata completata più di dodici mesi prima della randomizzazione." Nessuna quantità di prompting li aggiunge, perché non sono problemi di linguaggio. Sono problemi di logica. Quindi la mossa da senior non è rendere il modello affidabile. È rendere la fiducia non necessaria. Lascia che il modello faccia l'unica cosa in cui è bravo, leggere la prosa e proporre fatti con lo span da cui li ha letti. Poi fai sì che un verificatore scarti ciò che la nota non supporta, e che codice semplice e auditabile su un'ontologia medica calcoli il verdetto.
L'idoneità va calcolata, non predetta. Ciò che non mi aspettavo, entrando, era che il payoff non sarebbe sembrato affatto un benchmark. Sembra una ricevuta. La stessa cartella dà la stessa risposta ogni volta, la risposta nomina l'ID di concetto e l'arco del grafo che l'ha decisa, e il numero su cui un lead di fattibilità perde davvero il sonno va a zero pazienti idonei scartati.
E se preferisci guardarlo piuttosto che sentirmi descriverlo, ecco l'intera cosa in esecuzione da un capo all'altro.
Quindi ecco la domanda che non smetto di rimuginare, e mi piacerebbe davvero sapere come la rispondi tu. Quando la posta in gioco è l'occasione di una persona reale a un trial, dove vuoi che viva la tua fiducia: in un modello a cui devi credere, o in codice che puoi leggere?


