Ingegneria · trasparente
Metodologia
Come OpenCaseLaw trova, pondera e verifica le decisioni giudiziarie svizzere — dall'inizio alla fine, con ogni ponderazione e ogni soglia resa pubblica. Questa pagina è il riferimento che fa fede; ciò che leggete altrove dovrebbe corrispondere a quanto qui riportato — in caso contrario, prevale il codice.
1. Corpus e aggiornamento
Il corpus comprende 1’050’000+ decisioni giudiziarie svizzere pubblicate, provenienti da 118 tribunali, in tre lingue (DE 499’642; FR 459’825; IT 90’776), con un arco temporale dal 1875 al 2026. Otto giurisdizioni federali (BGer 192’049; BVGer 108’135; BGE 50’459; oltre a BStGer, BPatGer, MKG e altre), 26 giurisdizioni cantonali nonché la CEDU-Svizzera (~2’800 decisioni da traduzioni DTF, HUDOC, Camera/Grande Camera/Comitato).
Ogni decisione è ottenuta dalla sua fonte primaria — ove disponibile, dal portale di pubblicazione del tribunale stesso (tutti i 26 cantoni vengono recuperati direttamente tramite LexWork / SIL / ZH OpenData / TI RL, con ripiego PDF LexFind che integra gli atti normativi mancanti in 4 cantoni), per gli aggiornamenti federali dal back-end di ricerca ufficiale del TF. 40 dei 51 shard di entscheidsuche.ch sono rispecchiati dai nostri scraper diretti; i dati di entscheidsuche prevalgono nella deduplicazione solo laddove non disponiamo di un equivalente proprio in tempo reale. I restanti sette shard esclusivi di entscheidsuche (≈142’000 record: vd_findinfo, vd_omni, ch_vb, sg_gerichte, be_bvd, be_weitere, be_steuerrekurs) sono archivi storici congelati — i loro portali a monte sono stati dismessi, sostituiti o temporaneamente disconnessi. Rileggiamo entscheidsuche ogni settimana come salvaguardia di completezza; le ultime sei esecuzioni settimanali non hanno prodotto alcuna nuova decisione. Si veda Copertura → archivi complementari per la ripartizione completa. Il pipeline si aggiorna secondo tre cadenze:
- Ogni 15 minuti nei giorni feriali dalle 05:00 alle 16:00 UTC — il poller BGer pubblica le nuove decisioni federali in modo incrementale nel database in tempo reale, entro pochi minuti dalla loro pubblicazione da parte del tribunale.
- Quotidianamente alle 01:00 UTC — viene eseguito ogni scraper cantonale attivo; una politica di soft-fail è ancorata ai 5 scraper federali critici e a una soglia del tasso di errore del 15 % sui restanti.
- Quotidianamente alle 03:30 UTC — ricostruzione completa di FTS5, ricostruzione del grafo delle citazioni, gate di qualità, aggiornamento delle statistiche, scambio atomico senza interruzioni. I worker continuano a servire il database esistente finché quello nuovo non ha superato il controllo di integrità.
Il corpus completo è inoltre pubblicato in formato Parquet su Hugging Face (voilaj/swiss-caselaw) sotto CC0; il delta quotidiano viene aggiunto automaticamente.
2. Indice full-text
SQLite FTS5 con il tokenizer unicode61 remove_diacritics 2. La tabella delle decisioni alimenta l'indice attraverso queste colonne, con pesi BM25 per colonna calibrati empiricamente contro un golden set di 100 query:
| Colonna | Peso BM25 | Motivazione |
|---|---|---|
| title | 6.0 | Segnale più forte — conciso, deliberato. |
| regeste | 5.5 | Sintesi tematica del tribunale stesso. |
| docket_number | 2.0 | Per il recupero esatto tramite il numero di pratica. |
| full_text | 1.2 | Lungo, rumoroso — àncora, non motore. |
| court / canton / language / decision_id | 0.8 | Metadati, non contenuto. |
L'indice viene ricostruito in modo atomico: build_fts5.py scrive in decisions.db.tmp e quindi sostituisce il file tramite os.replace(). I worker che utilizzano l'URI SQLite ?immutable=1 mantengono i loro handle di file aperti sull'inode precedente finché non si riconnettono — lo scambio resta invisibile ai lettori attivi.
build_fts5.py · pesi BM25 configurati in mcp_server.py intorno alle righe 431–442 · tokenizer diacritico rispecchiato in decision_structure.db per la ricerca per paragrafo.
3. Comprensione della query
Una query in linguaggio naturale non raggiunge mai FTS5 senza elaborazione. Prima passa attraverso una sanificazione (_sanitize_fts5): apostrofi, trattini e punti privi di carattere di parola vengono ridotti a spazi; i token riservati (AND, OR, NOT, NEAR) vengono conservati solo se hanno operandi su entrambi i lati; l'unico token giuridico svizzero «OR» (abbreviazione di diritto delle obbligazioni) viene sempre forzato tra virgolette, altrimenti sarebbe interpretato come operatore booleano.
In parallelo, la query viene inoltrata a Claude Haiku 4.5 per un'analisi strutturata di 2 secondi: riferimenti normativi, termini dottrinali in DE/FR/IT, menzioni di DTF di riferimento, sinonimi e ambito giuridico vengono estratti come JSON deterministico. L'output viene memorizzato nella cache secondo la query in minuscolo per la durata della sessione.
Tre normalizzazioni rendono il vocabolario giuridico svizzero ricercabile attraverso le sue varianti stilistiche, ortografiche e interlinguistiche:
- Tokenizzazione insensibile ai diacritici. Il tokenizer FTS5
unicode61 remove_diacritics 2rimuove i diacritici sia lato indice sia lato query — così «Prüfung», «PRÜFUNG» e una query su «Prufung» raggiungono tutte la stessa lista di risultati. - Unificazione delle grafie con dieresi. La grafia senza diacritici
ae/oe/ue(diffusa nelle sentenze più antiche e in ogni ambiente pre-Unicode) viene ridotta aa/o/u, che il tokenizer unifica poi con leä/ö/üprivate dei diacritici. Così «Pruefung» trova anche «Prüfung». - Espansione sinonimica tramite LLM. L'analisi strutturata di Claude Haiku genera 2–4 termini giuridici alternativi in DE/FR/IT per query, al volo — non da una tabella statica. Così «qualité pour recourir» viene collegata a «Beschwerdebefugnis» / «Beschwerdelegitimation» / «legittimazione», anche se la vostra query ne conteneva solo uno.
4. Recupero e fusione
La query si articola in 10–12 strategie che vengono eseguite come query FTS5/grafo indipendenti. Ogni strategia ha un peso; il risultato di rango 1 di una strategia e quello di un'altra vengono fusi mediante Reciprocal Rank Fusion (costante di rango RRF = 60).
| Strategia | Peso | Effetto |
|---|---|---|
| nl_and | 1.8 | AND in linguaggio naturale sui termini. |
| raw | 1.5 | Query letterale. |
| regeste_focus | 1.4 | Limitato alla colonna del regesto. |
| nl_or | 1.2 | Ripiego OR (terminazione anticipata attenta ai costi). |
| structured_doctrine | 1.1–3.5 | Termini dottrinali dall'analisi Haiku. |
| quoted_explicit | 1.1 | Corrispondenza di frase se vengono rilevate virgolette. |
| nl_or_expanded | 1.0 | OR + espansione sinonimo / dieresi / composto. |
| title_focus | 0.95 | Corrispondenza limitata alla colonna del titolo. |
| doctrine_regeste / doctrine_title | 2.5 / 1.6 | Strategie di traduzione dei termini. |
Oltre a FTS5, lo stesso pool RRF riceve anche:
- Candidati dal grafo legislativo — decisioni collegate agli articoli di legge estratti dalla query, immessi come graduatoria parallela.
- Ricerche BGE dirette — se la query (o la sua analisi) nomina un riferimento DTF preciso, quella decisione viene fissata in cima.
- Corrispondenze di numero di pratica — stringhe come
6B_1234/2025vengono indirizzate a una ricerca esatta del numero di pratica, saltando la maggior parte del pipeline.
Il pool di candidati fuso è dimensionato dinamicamente (per impostazione predefinita ~300–400 per una query top-50) e limitato a 2’500 righe prima dell'inizio della riponderazione.
5. Riponderazione
Ogni candidato riceve un vettore di segnali; il punteggio finale è una combinazione lineare calibrata contro il golden set. Pesi dei segnali (_rerank_rows):
| Segnale | Peso | Limiti massimi / note |
|---|---|---|
| Punteggio RRF | 32.0 | Aggregato di tutti i ranghi di strategia. |
| Numero di pratica esatto / parziale | 6.0 / 2.0 | Corrispondenza a livello di stringa del numero di pratica. |
| Copertura del titolo | 3.0 | Quota dei token della richiesta nel titolo. |
| Copertura del regesto | 3.0 | Idem, contro il regesto. |
| Menzioni di legge | 3.5 · 0.5 · 2.0 | Collegamenti da decisione ad articolo. |
| Corrispondenze di citazione | 2.4 · 0.30 · 1.2 | Riscontri di citazione tra i risultati. |
| Autorità (citazioni in entrata) | 0.03 · 1.0 | Perché una decisione di principio risale. |
| Concordanza linguistica | +2.0 | Quando la lingua del risultato corrisponde a quella della query. |
| Copertura estesa | 1.5 / 0.8 | Credito per le corrispondenze sinonimo + composto. |
| Euristiche di ambito giurisdizionale | ±0.2 – +1.7 | Ponderazione asilo-BVGer + corte suprema-BGer in caso di intento corrispondente. |
Dopo lo scorer lineare, viene attivata se necessario una passata di riponderazione tramite LLM:
- Modello: Claude Haiku 4.5; Top-N: 15; timeout: 3 s; peso: 3.0 con decadimento lineare
w × max(0, 1 − rango/15). - Un gate di confidenza salta del tutto la chiamata quando il punteggio lessicale top-1 è già ≥ 2× quello top-2 (la risposta è univoca; la riponderazione è puro costo).
- La passata viene inoltre saltata per le query di numero di pratica (una corrispondenza esatta non ha bisogno di un LLM).
6. Riferimenti puntuali attivo · maggio 2026
Ciascuno dei cinque migliori risultati di ricerca e ciascuno dei tre migliori risultati di decisioni di principio porta un campo pinpoint che indica il considerando più pertinente per la query (paragrafo contenente la decisione giuridica). Il resolver opera su un indice FTS5 di paragrafi per decisione in decision_structure.db (≈8,8 mln. di paragrafi su 807’000 decisioni). Il design a due fasi:
- Passata per frase. La richiesta viene eseguita come frase FTS5 esatta — alta precisione quando la formulazione dell'utente corrisponde a quella del tribunale.
- Passata OR bag-of-words. Se la frase non restituisce nulla, gli stessi token vengono lanciati come query OR, per un recupero più ampio.
Uno scorer di confidenza (_score_pinpoint_confidence) combina tre segnali indipendenti per contrassegnare il risultato:
- Distacco BM25. Punteggio di rango 1 su quello di rango 2; un rapporto > 1,5 dà «alto», > 1,2 dà «medio».
- Forza assoluta. Per le corrispondenze a riga singola senza un rango 2 di confronto: |BM25| assoluto > 2,0 per «alto», > 1,0 per «medio». Una versione precedente di questo ramo utilizzava per le corrispondenze a riga singola un valore sentinella di 999,0, che promuoveva silenziosamente le corrispondenze deboli a «alto»; era il bug di falsa confidenza corretto a maggio 2026.
- Copertura dei token. Token univoci della richiesta (> 2 caratteri, con ~70 parole vuote generiche del discorso giuridico svizzero filtrate) che compaiono nel paragrafo trovato. Le richieste multi-token con copertura < 50 % vengono soppresse del tutto; < 70 % è limitato a «medio».
Recupero semantico infrastruttura distribuita · corpus ~33 % codificato interviene quando la passata lessicale non restituisce nulla. La richiesta viene codificata con sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (117 mln. di parametri, vettori normalizzati a 384 dimensioni, multilingue DE/FR/IT/RM più altre 47); la similarità coseno viene calcolata sui < 300 embedding di paragrafo della decisione; un risultato compare come «alto» se il coseno ≥ 0,70, come «medio» a ≥ 0,55, altrimenti nessuno. Una modalità ibrida, attivata al completamento della codifica, esegue entrambi i segnali e tratta la concordanza sullo stesso considerando come riscontro inter-segnale — la confidenza viene innalzata a «alto», source: "hybrid_agreement".
L'URL di pinpoint è costruito in modo che il browser scorra automaticamente fino al paragrafo trovato e lo evidenzi: ?highlight=<frase letterale>&e=2.3#e-2-3. Il parametro di query controlla un'evidenziazione lato server; il frammento hash attiva lo scorrimento automatico nativo del browser.
7. Grafo delle citazioni
Il testo di ogni decisione viene analizzato alla ricerca di citazioni di altre decisioni (riferimenti DTF, numeri di pratica BGer, riferimenti BVGer). Lo strato grezzo contiene 8,65 mln. di archi; il resolver ne eleva 8,09 mln. (92,9 %) a identificatori di decisione canonici. Il restante 7 % sono decisioni reali che non deteniamo ancora (DTF antichi anteriori al 1875, pubblicazioni giudiziarie ritirate, riferimenti danneggiati dall'OCR).
La risoluzione si svolge in quattro passate a cascata:
- Join standard per numero di pratica. Numero di pratica normalizzato della citazione = numero di pratica normalizzato della destinazione. Copre oltre l'80 % degli archi.
- Prefisso BGE bidirezionale. Trova le corrispondenze indipendentemente dal fatto che la citazione contenga o meno il prefisso
BGE, potendo la destinazione essere memorizzata in entrambe le forme. Questa singola correzione del marzo 2026 ha quasi triplicato la risoluzione dei BGE. - Numeri BGE nudi. Le destinazioni corrispondenti a
volume divisione paginavengono trattate come riferimenti BGE nudi e prefissate conBGE. - Risoluzione del pin-cite. Se una citazione contiene un numero di pagina assente dalle nostre destinazioni (p. es.
BGE 125 V 352, dove la prima pagina è 351), cerchiamo la prima pagina più alta ≤ 352 all'interno dello stesso (volume, divisione) ed entro 30 pagine. La confidenza viene ridotta di 0,10 per riflettere l'inferenza.
Autorità più citata (calcolata in tempo reale da reference_graph.db il 2026-05-11): BGE 125 V 351 con 85’108 citazioni in entrata, seguita da BGE 134 V 231 e BGE 122 V 157, ciascuna nell'ordine delle decine di migliaia. Questi conteggi rientrano come segnale di autorità nella riponderazione di ricerca (peso 0,03 per citazione, limitato a 1,0) — motivo per cui le decisioni di principio classiche risalgono, anche quando le loro corrispondenze linguistiche non sono più forti di quelle degli altri candidati.
8. Garanzia della qualità
Ogni pubblicazione notturna è protetta da un framework di QA a 4 livelli. L4 (il gate di pubblicazione, che esegue il sottoinsieme CRITICO di L1) è il blocco rigido: in caso di fallimento, lo scambio viene rifiutato e gli utenti conservano il corpus di ieri finché il problema non è stato esaminato. Gli altri livelli sono salvaguardie continue — L2 viene eseguito a ogni commit in CI, L3 ogni 5 minuti sul server in tempo reale.
- L1 — Controlli del dataset (63 su 20 moduli). Drift per tribunale, rilevamento di duplicati, rilevamento di testo breve / OCR, tasso di risoluzione del grafo delle citazioni, copertura dei collegamenti normativi, plausibilità data / numero di pratica, conteggio dei campi mancanti nonché verifica a campione tramite LLM su una selezione rotante.
- L2 — suite pytest (attualmente 516 superati). I test unitari coprono ogni primitiva di parsing, deduplicazione e ranking; le nuove regressioni vengono aggiunte come nuovi test secondo lo schema «incidente → test di regressione».
- L3 — smoke test (ogni 5 minuti). Sonda di salute di produzione:
/health, una pagina di decisione-àncora e il pipeline di esportazione PDF. Tre fallimenti consecutivi escalano in un allarme INVESTIGATE. - L4 — gate di pubblicazione. Tra i controlli L1, quelli contrassegnati come CRITICI devono passare prima che il nuovo corpus venga committato e pushato; se uno fallisce, la pubblicazione rifiuta lo scambio e i dati di ieri restano in linea. La dashboard su /quality.html mostra l'ultimo stato e lo storico di ogni controllo.
Oltre alla QA, i workflow LLM che citano decisioni passano attraverso un audit finale a cinque binari (attest_response) che intercetta quattro classi di allucinazione — citazione inventata, estratto inventato, testo di legge inventato, data inventata — nonché un quinto giudice di grounding facoltativo, che verifica se l'affermazione che l'LLM ha attribuito a un paragrafo citato è effettivamente sostenuta dal testo di quel paragrafo.
9. Testato e scartato
Le decisioni tecniche diventano più affidabili quando i vicoli ciechi sono visibili. Le seguenti tecniche sono state misurate sullo stesso golden set del resto e non si sono guadagnate il loro posto:
- Riponderazione tramite cross-encoder (
cross-encoder/mmarco-mMiniLMv2-L12-H384-v1come segnaposto attuale) scartato I cross-encoder multilingue addestrati su QA web generico non si trasferivano in modo pulito al vocabolario giuridico svizzero sul golden set — danneggiavano l'MRR invece di aiutare. L'helper rimane nel codice dietro_apply_cross_encoder_boostsper futuri esperimenti di fine-tuning, ma non viene chiamato in produzione. - Vettori densi BGE-M3 precostruiti scartato Il corpus codificato, l'RRF vettoriale aggiunto come quarta strategia. Nessun miglioramento rispetto a BM25 + RRF + Haiku, con qualsiasi peso vettoriale. La
vectors.dbè stata rimossa nel marzo 2026. Gli embedding semantici per paragrafo consegnati questa settimana sono un intervento distinto e più ristretto — scorano solo all'interno dei paragrafi di una singola decisione, dove BM25 ha limiti ben noti. - Top-N Haiku più ampio scartato Riponderare il top-30, il top-50 invece del top-15 non ha portato alcun guadagno di MRR e un aumento lineare dei costi. Il top-15 è il punto ottimale empirico per la riponderazione guidata dalla confidenza.
- Pool di candidati più ampio scartato Ingrandire il pool oltre ~400 candidati (per le query standard) non ha spostato il recall in modo significativo; la combinazione FTS5 + RRF fa già emergere i candidati giusti già entro le prime poche centinaia di risultati. Il limite di 2’500 rimane come tetto di sicurezza.
10. Accesso libero
Il corpus è CC0 (dedica al pubblico dominio). Il codice è MIT, ospitato su github.com/jonashertner/opencaselaw — ogni ponderazione, ogni soglia e ogni euristica di questa pagina è verificabile nel codice sorgente. Nessun account, nessun cookie; cosa registra il server, incluse le query di ricerca, e per quanto tempo, è documentato nell'informativa sulla privacy: /datenschutz/.
Accesso programmatico:
- API REST. mcp.opencaselaw.ch/docs — OpenAPI 3.0.3 con 47 endpoint (ricerca, recupero, decisioni di principio, tendenze, grafo delle citazioni, atti normativi, commentari, Botschaft, export, riferimenti puntuali, attestazione).
- Server MCP. mcp.opencaselaw.ch/sse — trasporto Server-Sent Events, che espone 38 strumenti. Collegato da Claude, ChatGPT (OpenAI MCP), Cursor, Microsoft Copilot Studio, Perplexity e altri.
- Dataset Hugging Face. voilaj/swiss-caselaw — shard Parquet, pubblicati quotidianamente in delta.
- Componente Word. opencaselaw.ch/word/ — componente aggiuntivo Office per Microsoft Word; espone lo stesso stack di ricerca + pinpoint all'interno del documento.
Questa pagina riflette il sistema in tempo reale. Se notate una discrepanza tra quanto descritto qui e quanto dice il codice sorgente, prevale il codice sorgente — aprite una issue e ne correggeremo uno dei due.