diff --git a/core.py b/core.py index 82d778c..747a05a 100644 --- a/core.py +++ b/core.py @@ -527,10 +527,18 @@ async def analizza_async( def _bias_linguistico_score(bias_pats: list[Patologia]) -> float: - """1.0 - max severita patologie hedging/booster (cap a 1.0).""" + """1.0 - max severita patologie BOOSTER (cap a 1.0). + + Solo il boosting (assolutismo / petitio: «ovviamente», «indubbiamente») + erode ε: è la mossa che la critica ai dogmi punisce. L'HEDGING + («forse», «sembra», «potrebbe») è il marcatore della provvisorietà + fallibilista che l'asse ऋ⁷ *loda* — non è un bias e NON abbassa ε + (B1, 2026-07). Resta rilevato e visibile come segnale descrittivo; la + distinzione cautela↔evasività (weasel) spetta all'induttivo + (diagnosi_malafede), non al conteggio deterministico. + """ rilevanti = [p.severita for p in bias_pats - if p.tipo in {TipoPatologia.HEDGING_ECCESSIVO, - TipoPatologia.BOOSTER_ECCESSIVO}] + if p.tipo is TipoPatologia.BOOSTER_ECCESSIVO] if not rilevanti: return 1.0 return round(max(0.0, 1.0 - max(rilevanti)), 4) @@ -546,7 +554,7 @@ def _bias_linguistico_score(bias_pats: list[Patologia]) -> float: TipoPatologia.DENSITA_CRITICA}, "coerenza_tematica": {TipoPatologia.INCOERENZA_TEMATICA, TipoPatologia.DERIVA_REGISTRO}, "coesione_semantica": {TipoPatologia.INCOERENZA_LOCALE}, - "bias_linguistico": {TipoPatologia.HEDGING_ECCESSIVO, TipoPatologia.BOOSTER_ECCESSIVO}, + "bias_linguistico": {TipoPatologia.BOOSTER_ECCESSIVO}, # hedging non erode ε (B1) "credibilita_fonte": {TipoPatologia.APPELLO_AUTORITA}, "integrita_obiettivo": {TipoPatologia.OBIETTIVO_CONTRADDITTORIO, TipoPatologia.OBIETTIVO_DISPERSO}, } diff --git a/docs/analisi_bias.md b/docs/analisi_bias.md new file mode 100644 index 0000000..7db19c9 --- /dev/null +++ b/docs/analisi_bias.md @@ -0,0 +1,264 @@ +# Analisi dei bias di resh — codice e auto-coerenza + +> Un tool che diagnostica dogmi nascosti non può permettersi di nasconderne di propri. +> Questa analisi applica a resh l'arsenale che resh applica agli altri. + +L'indagine si muove su due assi, come richiesto: + +- **A — Bias nel codice**: difetti misurabili nella pipeline deterministica (`ε_ऋ`), là dove + il numero si forma. Sono correggibili con interventi locali e verificabili. +- **B — Auto-coerenza dell'agente con se stesso**: i punti in cui resh viola i principi che + esso stesso dichiara e pretende dai testi che analizza. Sono i bias più profondi, perché + toccano la coerenza tra ciò che resh *dice di essere* e ciò che *fa* quando calcola. + +Ogni rilievo cita il punto del codice che lo fonda (parità di ruolo: nessun rilievo senza +ancoraggio) e propone una direzione di correzione, senza imporla. + +--- + +## A — Bias nel codice + +### A1. Collinearità: un solo marcatore di superficie erode fino a tre componenti di ε + +`epsilon.py` calcola ε come media geometrica pesata e ne vanta l'ortogonalità: +`validita_formale` (entailment) e `assenza_fallacie` (MAFALDA) sono dichiarati «assi +ORTOGONALI» (`epsilon.py:21-24`). Ma sotto la superficie tre componenti reagiscono allo +**stesso** segnale lessicale. Una parola come «evidentemente» / «ovviamente» / «chiaramente» +innesca simultaneamente: + +1. **`bias_linguistico`** (peso 0.08) — è un booster (`lessici/booster_it.txt`) → + `BOOSTER_ECCESSIVO` → `_bias_linguistico_score` (`core.py:529`). +2. **`credibilita_fonte`** (peso 0.07) — `bias_autorita.py:166` sottrae `0.20` alla + credibilità per `booster_ratio > 0.04`. +3. **`assenza_fallacie`** (peso 0.09) — è il pattern `petitio_principii` + (`lessici/fallacy_patterns_it.json`) marcato `confermata: True` (vedi A2) → erode ε. + +La media geometrica è *zero-sensitive* per costruzione (`epsilon.py:5-8`): colpire tre +componenti con un unico segnale amplifica l'erosione in modo non lineare, e la fa apparire +come tre problemi indipendenti nella *Genesi* del report quando è uno solo. Circa il 24% del +peso totale di ε reagisce a un singolo tratto di superficie. + +**Correzione proposta.** Consolidare i marcatori di superficie in *un* canale: o togliere la +penalità di `credibilita_fonte` per hedge/boost (già coperta da `bias_linguistico`), o non +far erodere ε ai `petitio` puramente lessicali (declassarli a candidati, non confermati — vedi +A2). L'obiettivo è che ogni segnale conti una volta sola. + +### A2. `confermata: True` incondizionato — anche a confidence 0.45 + +`core.py:351` fa erodere `assenza_fallacie` **solo** alle fallacie `confermata`; è la stessa +promessa del README: «solo le patologie confermate da più segnali indipendenti sono verdetti; +le altre sono candidate». Ma `fallacie.py:125` imposta `"confermata": True` per **ogni** match +regex, con il commento «regex ad alta precisione» — incluso `petitio_principii`, che il file +lessico stesso etichetta a `confidence: 0.45` (bassa, per sua stessa ammissione). + +Il risultato: un *singolo* segnale regex a 0.45 diventa un verdetto che abbassa il numero, +mentre il README garantisce che i verdetti richiedono «più segnali indipendenti». È una +contraddizione codice↔documentazione, ed è un bias sistematico contro la prosa enfatica o +sicura di sé (che usa proprio quei marcatori). + +**Correzione proposta.** Sganciare `confermata` dal fatto di *essere* regex e legarlo a una +soglia di confidence, o alla co-occorrenza con un secondo segnale (NLI sulla stessa frase). +I pattern ad alta precisione reale (es. `ad_hominem`, conf 0.65) restano confermati; quelli a +0.4–0.45 diventano candidati visibili ma non eroditori. + +### A3. Il lessico dei booster contiene operatori logici + +`lessici/booster_it.txt` include «tutti», «nessuno», «sempre», «mai», «necessariamente». Non +sono retorica dell'assolutismo: sono i quantificatori e i modali **costitutivi** +dell'argomentazione formale valida. Un sillogismo categorico corretto — «tutti gli uomini sono +mortali; Socrate è un uomo; dunque necessariamente Socrate è mortale» — è denso di queste +parole. Su un testo breve, `booster_ratio` supera facilmente `0.04` e scatta +`BOOSTER_ECCESSIVO`. + +Ne segue una tensione *interna* a ε: `validita_formale` premia il sillogismo valido, mentre +`bias_linguistico` e `credibilita_fonte` lo puniscono per l'uso dei quantificatori che lo +rendono valido. Due componenti dello stesso numero tirano in direzioni opposte sullo stesso +tratto. + +**Correzione proposta (raffinata, Mill).** Il punto **non** è che i quantificatori siano +epistemicamente innocenti. Per J. S. Mill (*A System of Logic*) il sillogismo **è** una petitio: +«tutti gli uomini sono mortali» non è asseribile senza aver già incluso Socrate, quindi la +conclusione è già certificata nella premessa — e lo stesso vale nel ragionamento empirico. La +correzione giusta è quindi *ri-collocare* il segnale, non cancellarlo: togliere i quantificatori +dal lessico **booster** (non sono enfasi retorica) e lasciare che la loro eventuale circolarità +sia giudicata dove resh già la tratta — l'asse della circolarità (Trilemma **C₂** viziosa/ +virtuosa, `circular_reasoning`), coerente col suo stesso assioma `ऋ¹`. Nessun nuovo +penalizzatore: gli usi fallaci contestuali («tutti sanno») restano catturati dai pattern +fallacia. L'omologo inglese (`booster_en.txt`: «all», «none», «always», «never», «necessarily») +ha lo stesso difetto e la stessa correzione. + +### A4. `qualita_sintattica`: ottimi hardcoded tarati su un solo registro + +`profilo_linguistico.py:180-185` valuta la qualità sintattica con una «curva ad arco» centrata +su ottimi fissi: profondità albero ≈ 4.5, `subordination_ratio` ≈ 0.6, Gulpease ≈ 55. Sono i +valori della prosa espositiva contemporanea. La prosa filosofica classica a periodi lunghi +(Berkeley, le traduzioni di Kant — cioè esattamente il corpus di `examples/`) ha subordinazione +e profondità alte, e viene penalizzata come «barocca». + +Il README dichiara questo *epoch-bias* per `struttura_argomentativa` («risente dello stile +d'epoca», sezione Questioni aperte) ma **non** per `qualita_sintattica`, che soffre dello stesso +difetto in costanti fisse non dichiarate. + +In più: il Gulpease è una formula di leggibilità tarata **sull'italiano** (`89 - lettere/parole +*10 + frasi/parole*300`, `profilo_linguistico.py:139`), eppure `qualita_sintattica` la usa con +ottimo 55 **a prescindere dalla lingua**. Sui testi EN il `gulp_score` è quindi +sistematicamente miscalibrato — un caso concreto del «lato EN meno calibrato» che il README +menziona solo in astratto. + +**Correzione proposta.** Rendere gli ottimi funzione di lingua/registro, o escludere il Gulpease +dal ramo EN, o dichiarare `qualita_sintattica` inaffidabile sui registri classici (come già si +fa con la soglia `< 30 token → None`). + +### A5. `expertise = bool(persone)` — proxy grossolano spacciato per criterio + +`bias_autorita.py:159-161`: `expertise` diventa `True` se nel testo compare *un qualunque* nome +proprio di persona — anche il soggetto stesso del testo («Berkeley», «Socrate»), non una fonte +autorevole citata. Il fallback su capitalizzazione (`_persone_da_doc`) è ancora più rumoroso. +È un segnale debole presentato come criterio di autorità. + +**Correzione proposta.** O rimuovere `expertise`, o legarlo a un segnale reale (nome + riferimento +bibliografico / citazione), o marcarlo esplicitamente come euristica inaffidabile nel report. + +--- + +## B — Auto-coerenza dell'agente con se stesso + +### B1. Il bias centrale: ε *punisce* il dubbio che la filosofia di resh *premia* + +È la contraddizione più profonda, ed è verificabile riga per riga. + +**Ciò che resh dichiara di essere:** +- Il manifesto (README, «Perché»): resh è «un modo di **organizzare il dubbio perché serva la + vita**»; l'epigrafe è Cioran sullo scetticismo come «calmante». +- L'assioma `ऋ⁰` (`prompts_resh.md`): il dubbio è «meccanismo attivo», non «difetto da + eliminare». +- L'assioma `ऋ⁷`: la fallibilità sistemica è una virtù; le affermazioni «definitive» *senza* + clausole di revisione sono «chiusure epistemiche» da segnalare come patologia. + +**Ciò che ε fa:** +- `bias_autorita.py:111-120`: `hedging_ratio > 0.06` genera la patologia `HEDGING_ECCESSIVO`. +- `bias_autorita.py:164`: la credibilità perde `0.15` per lo stesso hedging. +- Le parole penalizzate sono `lessici/hedging_it.txt`: «forse», «sembra», «probabilmente», + «potrebbe», «si direbbe», «non è escluso che» — cioè i **marcatori linguistici della + provvisorietà fallibilista** che `ऋ⁷` loda. + +**La contraddizione.** Un testo che marca onestamente la propria incertezza viene *punito dal +numero* per la stessa umiltà epistemica che l'asse `ऋ⁷` *premia* a parole. I due lati di resh +danno verdetti opposti sul marcare il dubbio — e solo ε cambia il numero in testa al report. +Un tool che si presenta come modo di «organizzare il dubbio» scala punti a chi dubita ad alta +voce. Il codice *sa* che boosting e hedging non sono equivalenti — usa soglie diverse (0.04 vs +0.06) — ma poi penalizza comunque entrambi, anziché trarne la conseguenza. + +**Correzione proposta.** Asimmetrizzare davvero i due tratti. Il boosting va bene dov'è ora: è +petitio / assolutismo, coerente con la critica ai dogmi. L'hedging **non** è un bias di per sé: +è fallibilismo, coerente con `ऋ⁷`. Andrebbe penalizzato *solo* nella sua forma evasiva +(hedging fitto **insieme** ad assenza di impegno e a beneficio dell'agente — il «weasel», non +la «cautela»), non come tratto lessicale isolato. La distinzione cautela↔evasività è +esattamente quella che l'induttivo sa già fare (cfr. `diagnosi_malafede`): il deterministico la +appiattisce. + +### B2. resh non applica a sé il proprio `ऋ⁷`: precisione spuria e auto-esenzione dalla revisione + +`ऋ⁷` chiede a *ogni* testo: «applica la stessa fallibilità a se stesso o si esenta dalla +revisione che prescrive ad altri?». Applichiamolo a resh. + +Il lato **induttivo** è esemplare: dichiara i propri C₃ strumentali, ammette un possibile C₄ +(«affermarlo chiuso sarebbe esso stesso un C₃», `prompts_resh.md`), porta il caveat +auto-applicativo di «termine astratto» (`lessici/termini_astratti_it.json`: «"termine astratto" +è esso stesso astratto → strumento C3 strumentale»). Qui resh *è* coerente. + +Il lato **deterministico** no. Il report presenta `ε_ऋ = 0.5524` — quattro decimali — con fasce +categoriche nette (`≥0.85 alta, ≥0.65 media, ≥0.40 bassa`, README). Ma i commenti nel codice +sanno che quei numeri non reggono quella precisione: +- `epsilon.py:53-62`: i pesi sono «PROVVISORIA fino alla calibrazione su 30+ testi annotati + (TODO)». +- `obiettivo.py:169-170`, `epsilon.py:72`: soglie e pesi sono «scelta, non taratura». + +Quattro decimali e fasce secche sono una **precisione spuria**: proiettano una certezza che il +codice stesso dichiara di non avere. È, alla lettera, la «certezza operativa non dichiarata» +che `ऋ⁰` e `ऋ⁷` segnalano come patologia negli *altri* testi. Auto-coerenza parziale: +l'induttivo confessa, il numero no. + +**Correzione proposta.** Portare la provvisorietà che vive nei commenti-codice dentro il report +visibile: banda di incertezza su ε (o meno decimali), e una riga esplicita «pesi provvisori, +non ancora calibrati» accanto alle fasce. È ciò che resh pretenderebbe da qualsiasi testo che +espone un numero con quattro cifre. + +### B3. La lente non-fondazionalista è un C₃ dichiarato *dentro*, ma non *in testa al report* + +resh accusa i testi di dogmi nascosti mentre poggia su una posizione metafisica forte e assunta, +non dimostrata: `ऋ¹` (nessuna conoscenza è giustificabile in assoluto), `ऋ²` (vuoto ontologico +alla Nāgārjuna: niente essenze), `ऋ⁶` (ogni significato è contingente). Sono la lente, non un +risultato. + +I prompt lo dichiarano onestamente (fonti `SA{ऋ}` citate, `ऋ⁰⁺` meta-contingente). Ma il README +lo tratta come *rischio dell'LLM* — «i giudizi LLM possono importare un quadro filosofico non +dichiarato» (Questioni aperte) — e non come **postura costitutiva di resh stesso**. Un idealista, +o un fondazionalista, analizzato da resh viene letto con il metro dell'anti-fondazionalismo, e +il report non porta *in testa*, accanto a ε, la dichiarazione «resh legge da questo punto» con la +stessa evidenza con cui pretende che i testi dichiarino il proprio fondamento. + +Il README promette che resh «dichiara per primo su che cosa si regge lui». Lo fa a metà: la +lente è dichiarata nei prompt e nei lessici, ma non si propaga all'intestazione di ogni report +come resh esige dai testi. + +**Correzione proposta.** Una riga in testa al report, accanto a ε: «Postura di resh: analisi +non-fondazionalista; il framework `SA{ऋ}` è un C₃ strumentale dichiarato, non un fondamento». +Non cambia i numeri; rende resh soggetto alla stessa regola che impone. + +--- + +## Sintesi e priorità + +| # | Bias | Asse | Tipo | Correzione | +|---|------|------|------|------------| +| B1 | ε punisce l'hedging (dubbio) che `ऋ⁷` premia | auto-coerenza | contraddizione di principio | asimmetrizzare hedge↔boost; hedging non è bias | +| A1 | un marcatore erode 3 componenti (collinearità) | codice | amplificazione non lineare | consolidare i segnali di superficie in un canale | +| A2 | `confermata: True` per ogni regex, anche a 0.45 | codice | contraddice il README | gate su confidence / co-occorrenza NLI | +| B2 | precisione spuria di ε; auto-esenzione da `ऋ⁷` | auto-coerenza | certezza non dichiarata | banda d'incertezza + nota «pesi provvisori» nel report | +| A3 | quantificatori logici nel lessico booster | codice | tensione interna a ε | separare operatori logici da booster retorici | +| B3 | lente non-fondazionalista non dichiarata nel report | auto-coerenza | dichiarazione a metà | riga di postura in testa al report | +| A4 | ottimi sintattici hardcoded; Gulpease su EN | codice | epoch/lang bias | ottimi per lingua/registro; no Gulpease su EN | +| A5 | `expertise = bool(persone)` | codice | proxy grossolano | legare a segnale reale o marcare inaffidabile | + +**Il filo conduttore.** I bias di codice (A) e quelli di auto-coerenza (B) non sono separati: A1, +A3 e B1 sono la stessa cosa vista da angoli diversi — resh ha costruito il lato deterministico +attorno a un'idea di «buona scrittura» (sicura, asciutta, senza esitazioni, senza subordinate) +che è un *registro*, non una virtù epistemica, e che a tratti contraddice la sua stessa filosofia +del dubbio. La correzione più densa è B1: distinguere, nel numero, la cautela dall'assolutismo — +perché è lì che resh, oggi, tradisce se stesso. + +--- + +## Correzioni applicate (2026-07) + +Le correzioni sono state implementate su questo branch. Riepilogo di *cosa* è cambiato e *dove*: + +- **B1 — hedging fuori da ε.** `core.py::_bias_linguistico_score` reagisce ora solo a + `BOOSTER_ECCESSIVO`; `_COMPONENTE_PATOLOGIE["bias_linguistico"]` non mappa più l'hedging; + `bias_autorita.py` non abbassa più `credibilita` per l'hedging. L'hedging resta rilevato e + visibile (segnale descrittivo), ma non erode ε. +- **A1 — fine del doppio conteggio.** `bias_autorita.py`: `credibilita_fonte` non reagisce più + a hedge/booster (il booster resta penalizzato una sola volta, in `bias_linguistico`). *Residuo + noto:* `ad_verecundiam` è ancora rilevato sia come fallacia (`assenza_fallacie`) sia come + `APPELLO_AUTORITA` (`credibilita_fonte`) — cross-count minore lasciato invariato per non + allargare lo scope. +- **A2 — `confermata` = conferma NLI.** `fallacie.py::_conferma_via_nli`: un match regex diventa + verdetto solo se l'NLI conferma la stessa fallacia nella stessa frase; senza NLI (backend + degradato) resta candidato. La circolarità *strutturale* (mutuo-entailment) è un canale a + parte, invariato. +- **A3 — quantificatori fuori dai booster (Mill).** Rimossi `tutti/nessuno/sempre/mai/ + necessariamente` (IT) e `all/none/always/never/necessarily` (EN) dai lessici booster, con nota + Mill in testa: la loro circolarità è competenza dell'asse C₂, non del bias retorico. +- **A4 — Gulpease per lingua.** `profilo_linguistico.py::qualita_sintattica` esclude il Gulpease + (formula IT) sui testi EN e rinormalizza i pesi. Ottimi IT depth/sub non ritarati (serve gold + set) ma documentati come register-sensitive. +- **A5 — expertise reale.** `bias_autorita.py`: `expertise = bool(persone) and ha_citazione` (un + nome senza citazione/riferimento non è expertise). +- **B2 / B3 — report onesto.** `report.py`: costante `POSTURA_RESH` (lente non-fondazionalista + dichiarata in testa) e `CAVEAT_EPS` (pesi provvisori, non calibrati; register-sensitivity). + +**Effetto atteso su ε:** aumenta su testi con hedging fitto e su sillogismi/quantificatori +(dubbio e forma logica non più puniti) — è l'esito voluto. I riferimenti ε storici restano non +confrontabili coi run successivi (già dichiarato in `epsilon.py`). Le batterie ML di +calibrazione su corpus più ampio restano il passo di raffinamento successivo. diff --git a/epsilon.py b/epsilon.py index e6478be..1081dc6 100644 --- a/epsilon.py +++ b/epsilon.py @@ -15,7 +15,7 @@ coesione_semantica 0.12 (coerenza.coesione_locale) coerenza_tematica 0.08 (coerenza.coerenza_tematica_score) qualita_sintattica 0.10 (profilo_linguistico.qualita_sintattica) - bias_linguistico 0.08 (1 - max(hedge_ratio, booster_ratio)/0.1) + bias_linguistico 0.08 (1 - booster_ratio/0.1; hedging NON erode — B1) credibilita_fonte 0.07 (autorita.credibilita) NB: `validita_formale` (entailment) e `assenza_fallacie` (MAFALDA) sono assi diff --git a/gamma/bias_autorita.py b/gamma/bias_autorita.py index 1008b49..666d6ed 100644 --- a/gamma/bias_autorita.py +++ b/gamma/bias_autorita.py @@ -1,8 +1,9 @@ """resh/bias_autorita.py — NER + lessico hedging/booster + ad verecundiam. Rileva: - - hedging_ratio = #hedges / n_token → bias verso indeterminatezza - - booster_ratio = #boosters / n_token → bias verso assolutismo (petitio) + - hedging_ratio = #hedges / n_token → SEGNALE DESCRITTIVO (provvisorietà + fallibilista, non un bias): rilevato e visibile, ma NON erode ε (B1, 2026-07) + - booster_ratio = #boosters / n_token → bias verso assolutismo (petitio) → ε - pattern ad_verecundiam non-citato (NER PER + verbo dicendi + no quote) - bias multipli accumulati → flag su `AutoritaCriteri.bias_rilevati` @@ -156,16 +157,24 @@ def analizza_bias_autorita(testo: str, doc: AnnotatedDoc) -> tuple[AutoritaCrite # AutoritaCriteri legacy persone = _persone_da_doc(doc) fonte_label = persone[0] if persone else "sconosciuta" - # expertise: euristica conservativa — true solo se citata 1+ PER nominata - expertise = bool(persone) - - # credibilità: baseline neutro-positivo, penalità per bias, bonus per testo pulito + # expertise (A5, 2026-07): un nome proprio da solo NON è expertise — anche il + # soggetto del testo («Berkeley», «Socrate») è un nome. Serve un nome NOMINATO + # con un segnale di citazione (virgolette o riferimento bibliografico), non la + # sua mera presenza. Riusa i marcatori già rilevati sul testo. + ha_citazione = bool(re.search(r"[\"«»“”']", testo) or + re.search(r"\((?:cf|cfr|vedi|in)\.?\s+", testo, re.I)) + expertise = bool(persone) and ha_citazione + + # credibilità: baseline neutro-positivo, penalità SOLO per autorità non citata + # (ad_verecundiam), bonus per testo pulito. NON reagisce più a hedge/booster + # (B1+A1, 2026-07): l'hedging è fallibilismo (non bias), e il booster è già + # penalizzato una volta sola in `bias_linguistico` — qui era doppio conteggio + # su due componenti di ε. `credibilita_fonte` misura ora SOLO l'autorità/fonte, + # coerente col suo nome. credibilita = 0.65 - credibilita -= 0.15 * (hedging_ratio > 0.06) - credibilita -= 0.20 * (booster_ratio > 0.04) credibilita -= 0.20 * (1 if fonti_invocate else 0) - if not (hedging_ratio > 0.06 or booster_ratio > 0.04 or fonti_invocate): - credibilita += 0.10 # testo senza bias rilevati → premio epistemico + if not fonti_invocate: + credibilita += 0.10 # nessuna autorità non citata → premio epistemico credibilita = max(0.05, min(0.95, credibilita)) autorita = AutoritaCriteri( diff --git a/gamma/fallacie.py b/gamma/fallacie.py index 431907c..df2737d 100644 --- a/gamma/fallacie.py +++ b/gamma/fallacie.py @@ -20,6 +20,7 @@ import logging import re from pathlib import Path +from typing import Optional from . import _nli from .annotazione import AnnotatedDoc @@ -122,13 +123,65 @@ def _regex_fallacies(testo: str) -> list[Patologia]: "fallacia_l2": meta["tipo"], "match": m.group(0), "fonte": f"regex_{lang}", - "confermata": True, # regex ad alta precisione + # A2 (2026-07): un match regex NON è più un verdetto per sé. + # `confermata` viene deciso a valle da `_conferma_via_nli` + # (co-occorrenza con l'NLI). Default False finché non confermato. + "confermata": False, }, origine_modulo = "fallacie", )) return found +def _conferma_via_nli(regex_pats: list[Patologia], nli_pats: list[Patologia], + sentence_spans: list[tuple[int, int]]) -> None: + """Marca in-place `confermata` sui regex (A2, 2026-07). + + Un regex è `confermata=True` **iff** l'NLI ha rilevato la STESSA `fallacia_l2` + nella STESSA frase — il criterio è la conferma indipendente (README: «verdetti + = più segnali indipendenti»), non l'essere-regex. Era l'intento originale del + file: «i regex vengono confermate/integrate dal classifier NLI». + + Senza NLI (backend degradato / nessun candidato → `nli_pats` vuoto) nessun + regex è confermato: niente modello, niente verdetti di fallacia (coerente con + l'onestà dichiarata di resh in modalità degradata). + + `sentence_spans`: [(start_char, end_char), …] da `doc.sentences`, per mappare lo + span del match alla frase. Se il mapping fallisce (offset ignoti, es. fallback + con span 0), si ricade su un confronto doc-level per tipo. + """ + nli_by_frase: set[tuple[int, str]] = set() + nli_types: set[str] = set() + for p in nli_pats: + l2 = p.dettaglio.get("fallacia_l2") + if not l2: + continue + nli_types.add(l2) + idx = p.dettaglio.get("idx_frase") + if idx is not None: + nli_by_frase.add((int(idx), l2)) + + def _frase_di(span: Optional[tuple[int, int]]) -> Optional[int]: + if not span: + return None + start = span[0] + for i, (s, e) in enumerate(sentence_spans): + if e > s and s <= start < e: + return i + return None + + for p in regex_pats: + l2 = p.dettaglio.get("fallacia_l2") + if not l2: + p.dettaglio["confermata"] = False + continue + idx = _frase_di(p.span_char) + if idx is not None: + p.dettaglio["confermata"] = (idx, l2) in nli_by_frase + else: + p.dettaglio["confermata"] = l2 in nli_types # fallback doc-level + + def _frasi_con_marker(doc: AnnotatedDoc) -> list[tuple[int, str]]: """Ritorna [(idx_frase, text)] solo per frasi che contengono marker argomentativi — riduce drasticamente il carico NLI.""" @@ -220,9 +273,12 @@ def rileva_fallacie(doc: AnnotatedDoc, *, threshold: float = 0.55) -> list[Patol """Ritorna lista deduplicata di patologie FALLACIA_LOGICA. threshold: soglia confidence per accettare risultato NLI (default 0.55, - calibrato per deberta-v3-base-zeroshot-v2.0). Regex sono sempre inclusi - (precisione alta per costruzione). + calibrato per deberta-v3-base-zeroshot-v2.0). Regex sono sempre inclusi come + CANDIDATI; diventano `confermata` solo se l'NLI conferma la stessa fallacia + nella stessa frase (`_conferma_via_nli`, A2). """ regex_pats = _regex_fallacies(doc.text) nli_pats = _dedup_per_frase(_nli_fallacies(doc, threshold=threshold)) + sentence_spans = [(s.start_char, s.end_char) for s in doc.sentences] + _conferma_via_nli(regex_pats, nli_pats, sentence_spans) return _dedup(regex_pats + nli_pats) diff --git a/gamma/profilo_linguistico.py b/gamma/profilo_linguistico.py index 94eef5e..ca20ecc 100644 --- a/gamma/profilo_linguistico.py +++ b/gamma/profilo_linguistico.py @@ -163,6 +163,18 @@ def qualita_sintattica(profilo: dict) -> Optional[float]: Usato come componente 'qualità_sintattica' in epsilon.py. Curva ad arco: testi troppo semplici (Gulpease>80, MTLD<30) e troppo barocchi (depth>8, sub_ratio>1.5) sono entrambi penalizzati. + + REGISTER-SENSITIVE (limite noto, come `struttura_argomentativa`): gli ottimi + depth≈4.5 / sub≈0.6 sono di un registro espositivo contemporaneo; la prosa + classica a periodi lunghi (alta subordinazione/profondità) è in parte + penalizzata da artefatto di segmentazione, non da difetto reale — va letta con + gli altri componenti. Il retuning richiede un gold set di calibrazione (non si + inventano numeri a priori: il sistema si raffina nel tempo). Il report dichiara + questa provvisorietà (B2). + + LINGUA: il Gulpease è una formula tarata sull'ITALIANO — su testi EN è + miscalibrato, quindi viene ESCLUSO e i pesi rinormalizzati sui tre restanti + (A4, 2026-07). """ # Evidenza insufficiente: la stilometria (MTLD, depth, sub_ratio) non è # affidabile sotto ~30 token → ritorna None = «non misurabile», così @@ -172,15 +184,23 @@ def qualita_sintattica(profilo: dict) -> Optional[float]: if profilo.get("n_token", 0) < 30: return None + from .. import config + lang = config.LANG.get() + mtld = profilo.get("mtld", 0.0) sub = profilo.get("subordination_ratio", 0.0) depth = profilo.get("profondita_media_albero", 0.0) - gulpease = profilo.get("gulpease", 50.0) mtld_score = min(1.0, max(0.0, (mtld - 30.0) / 60.0)) # 30→0, 90→1 depth_score = 1.0 - min(1.0, abs(depth - 4.5) / 4.5) # ottimo @ ~4.5 sub_score = 1.0 - min(1.0, abs(sub - 0.6) / 0.6) # ottimo @ ~0.6 - gulp_score = 1.0 - min(1.0, abs(gulpease - 55.0) / 35.0) # ottimo @ ~55 + if lang == "en": + # Gulpease escluso (formula IT): rinormalizza 0.35/0.25/0.20 → Σ=1. + return round((0.35 * mtld_score + 0.25 * depth_score + + 0.20 * sub_score) / 0.80, 4) + + gulpease = profilo.get("gulpease", 50.0) + gulp_score = 1.0 - min(1.0, abs(gulpease - 55.0) / 35.0) # ottimo @ ~55 return round(0.35 * mtld_score + 0.25 * depth_score + 0.20 * sub_score + 0.20 * gulp_score, 4) diff --git a/lessici/booster_en.txt b/lessici/booster_en.txt index f7da242..6daa254 100644 --- a/lessici/booster_en.txt +++ b/lessici/booster_en.txt @@ -1,3 +1,9 @@ +# A3 (Mill, A System of Logic): logical quantifiers/modals — all, none, always, +# never, necessarily — do NOT belong here. They are constitutive of valid argument +# (a syllogism uses them by construction), not rhetorical emphasis. The circularity +# Mill ascribes to universal quantification (the syllogism is a petitio: the major +# premise already contains the conclusion) is the business of the circularity axis +# (Trilemma C₂ vicious/virtuous, circular_reasoning), not of rhetorical bias. obviously certainly undoubtedly @@ -20,11 +26,6 @@ incontrovertibly surely notoriously indisputably -necessarily -always -never -all -none totally completely absolutely certain diff --git a/lessici/booster_it.txt b/lessici/booster_it.txt index 45a706b..c6c8245 100644 --- a/lessici/booster_it.txt +++ b/lessici/booster_it.txt @@ -1,3 +1,9 @@ +# A3 (Mill, A System of Logic): i quantificatori/modali logici — tutti, nessuno, +# sempre, mai, necessariamente — NON stanno qui. Sono costitutivi dell'argomentazione +# valida (un sillogismo li usa per costruzione), non enfasi retorica. La circolarità +# che Mill attribuisce alla quantificazione universale (il sillogismo è petitio: la +# premessa maggiore include già la conclusione) è competenza dell'asse circolarità +# (Trilemma C₂ viziosa/virtuosa, circular_reasoning), non del bias retorico. ovviamente certamente indubbiamente @@ -21,11 +27,6 @@ sicuramente notoriamente indiscutibilmente pacificamente -necessariamente -sempre -mai -tutti -nessuno totalmente completamente assolutamente certo diff --git a/report.py b/report.py index 8cdd7ec..1a54434 100644 --- a/report.py +++ b/report.py @@ -15,6 +15,23 @@ from typing import Optional +# B3 — la lente di resh dichiarata in testa al report, come resh esige dai testi. +POSTURA_RESH = ( + "> **Postura di resh:** analisi non-fondazionalista; il framework SA{ऋ} " + "(ऋ¹ infondabilità operativa, ऋ² vuoto ontologico, ऋ⁶ contingenza dei " + "significati) è un **C₃ strumentale dichiarato**, non un fondamento — resh è " + "soggetto agli stessi assi che applica." +) + +# B2 — la provvisorietà che vive nei commenti-codice, resa visibile: niente +# precisione spuria (è la «certezza operativa non dichiarata» che resh segnala altrove). +CAVEAT_EPS = ( + "*Pesi di ε provvisori, non ancora calibrati su gold set; «qualità sintattica» " + "e «struttura argomentativa» risentono del registro d'epoca — leggere ε come " + "indicativo, non assoluto.*" +) + + _COMP_LABELS = { "trasparenza_premesse": "Trasparenza premesse", "validita_formale": "Validità formale (sequitur)", @@ -278,12 +295,15 @@ def genera_report_documento(rap_doc, *, run_uid: str = "") -> str: f"**Call LLM:** {meta.get('call_eseguite')} · {meta.get('ts')}" + (f" · **Run:** `{run_uid}`" if run_uid else ""), "", + POSTURA_RESH, + "", ] # Executive summary in testa head.append("## Riepilogo\n") fascia = _fascia_eps(eps_doc) head.append(f"**Epsilon documento: {eps_doc}** (tenuta epistemica: {fascia})") + head.append(CAVEAT_EPS) if O and "errore" not in O: head.append(f"\n**Obiettivo:** «{O.get('dichiarato', '?')}»") if sd: @@ -371,12 +391,15 @@ def genera_report(det: Optional[dict] = None, ind: Optional[dict] = None, + (f" · **Run:** `{run_uid}`" if run_uid else "") + (f" · {ts}" if ts else ""), "", + POSTURA_RESH, + "", ] # Executive summary if eps is not None: head.append("## Riepilogo\n") head.append(f"**Epsilon: {eps}** (tenuta epistemica: {fascia})") + head.append(CAVEAT_EPS) pat = (det or {}).get("patologie", []) if pat: head.append(f" · Patologie rilevate: {len(pat)}")