Battere Calderoli usando Python

In queste ultime settimane il dibattito pubblico è stato monopolizzato dalla discussione in Senato del disegno di legge che regolamenta le unioni civili tra persone dello stesso sesso e disciplina le convivenze, meglio noto come DDL Cirinnà.

Fra le varie polemiche hanno fatto notizia le migliaia di emendamenti dal contenuto frivolo presentati dai partiti dell'opposizione. Si è distinto in particolare il senatore Calderoli, autore di più di 4000 di essi.

Non è la prima volta che questo accade: nel caso del DDL Boschi il senatore Calderoli presentò decine di milioni di emendamenti generati con un programma sviluppato dal collega Crosio. Questo programma crea un numero potenzialmente illimitato di emendamenti a partire da un testo base, talvolta variando una singola cifra, talvolta aggiungendo o togliendo una frase, oppure operando altre trasformazioni che non variano nella sostanza il contenuto dell'emendamento.

Prendiamo come esempio il caso del DDL Cirinnà: l'unica differenza fra l'emendamento 1.450 e l'emendamento 1.451 è l'aggiunta della frase "e all'articolo 11 sopprimere le parole: «o da un'unione civile»". L'emendamento 1.450 è inoltre identico all'emendamento 1.452 fatto salvo il paragrafo finale, aggiunto in quest'ultimo.

La cosa può far sorridere, ma il problema è serio: se è vero che per il DDL Boschi non fu giudicato possibile discutere tutti gli emendamenti (anche votandone uno al minuto sarebbero serviti 156 anni per esaurire gli 82 milioni proposti) lo stesso non è avvenuto per il DDL Cirinnà, nel cui caso la presentazione di svariate migliaia di emendamenti ha costituito un ostacolo significativo alla sua approvazione.

Ricorderete senz'altro che i senatori dell'opposizione chiesero di cassare il superemendamento Marcucci, il cosiddetto "supercanguro", offrendo in cambio di ritirare migliaia dei propri emendamenti. Tale proposta non sarebbe stata possibile se, in primo luogo, gli emendamenti superflui fossero stati rigettati come inammissibili.

Il regolamento del Senato permette infatti al presidente di bocciare senza appello gli emendamenti inutili:

Il Presidente può stabilire, con decisione inappellabile, la inammissibilità di emendamenti privi di ogni reale portata modificativa (...) [articolo 100, comma 8 del Regolamento del Senato]

L'idea sarebbe di individuare i gruppi di emendamenti simili, in modo che soltanto il più generale di essi passi alla votazione. Per fare ciò bisognerebbe leggere con attenzione tutte le migliaia di emendamenti presentati, lavoro che ricadrebbe sui commessi del Senato. Con l'intento di semplificare la legiferazione in Senato senza rovinare la vita ai commessi, presento in questo post l'implementazione Python di un semplice algoritmo che svolge questo compito.

Un approccio pratico

Per prima cosa ci dobbiamo procurare i dati. Il Senato offre un endpoint SPARQL che forse potrebbe fare al caso nostro, ma ci scoraggiamo soltanto a leggere gli esempi di query proposti. Ce ne andiamo da quella pagina, mugugnando fra i denti che questa cosa del "web semantico" in fondo non ci aveva mai convinto.

Ci rivolgiamo allora a Scrapy, l'ottimo framework per scrivere spider. Nel giro di un paio d'ore, dopo aver abbandonato l'idea di parsare l'HTML malformato presentato dal sito del Senato, riusciamo a produrre il seguente codice, il quale scarica nella cartella data un file XML per ciascun emendamento:

Ora che abbiamo i dati possiamo passare alla parte più interessante: risolvere davvero il problema. Decidiamo di continuare in un Jupyter notebook, il quale ci consente di esplorare i dati in maniera interattiva.

I file XML che abbiamo scaricato sono strutturati secondo lo standard Akoma Ntoso, uno schema nato per descrivere documenti di natura legale. Non riusciamo a trovare alcuna libreria Python che permetta di manipolarli, quindi ci dovremo sporcare un altro po' le mani per scrivere le espressioni XPath che estraggano le parti che ci interessano: il numero identificativo dell'emendamento, i suoi autori e il suo testo.

Riconosciamo che il problema che vogliamo risolvere è un clustering non supervisionato in un numero ignoto di cluster. Il tipico approccio per risolvere questo problema è una delle varianti di Hierarchical Clustering insieme a un'euristica per tagliare il dendrogramma risultante all'altezza giusta per produrre i cluster cercati.

Per poter applicare questo algoritmo abbiamo bisogno di definire una distanza fra emendamenti, cioè una funzione che, dati due emendamenti, restituisce un numero non negativo tanto più grande quanto i due emendamenti sono diversi.

Per evitare di introdurre bias decidiamo di utilizzare soltanto il testo dell'emendamento e non i suoi autori, sebbene sia evidente che questi abbiano un alto potere predittivo sulla probabilità che due emendamenti siano simili.

Dobbiamo allora definire una distanza su due testi scritti (si spera) nella stessa lingua. Non ci viene in mente niente di meglio della Distanza di Jaccard fra i token dei due testi, dove definiamo token una sottostringa contigua di caratteri alfanumerici. L'implementazione è la seguente:

Così facendo stiamo scartando la punteggiatura, la formattazione e persino l'ordine e il numero di ripetizioni delle parole nel testo. Si potrebbe fare di meglio utilizzando degli specifici algoritmi di Natural Language Processing, ma questo algoritmo è già sufficiente per il nostro scopo.

A questo punto abbiamo tutto quello che ci serve per applicare Hierarchical Clustering ai nostri emendamenti. Usiamo l'implementazione contenuta in SciPy, piuttosto che metterci a scrivere la nostra. Applichiamo l'algoritmo solo ai primi 100 emendamenti in modo da poter ispezionare più facilmente il risultato. Ce la sbrighiamo in poche righe:

Il risultato è il seguente dendrogramma, in cui SciPy ha evidenziato con colori diversi quelli che ritiene essere cluster diversi.

Ispezioniamo l'ultimo di essi, il sottoalbero di colore verde:

77: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15. C 72: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 68: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 64: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 60: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 56: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 52: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 48: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 92: Sopprimere gli articoli 1, 2, 3, 4, S, 6, 7, 8, 9, 10, 11. Conseguentemente, 89: Sopprimere gli articoli 1, 2, 3,4, S, 6, 7, 8, 9, 10, 11, 12. Conseguentemen 84: Sopprimere gli articoli 1, 2, 3, 4, S, 6, 7, 8, 9, 10, 11, 12, 13. Conseguen 80: Sopprimere gli articoli 1, 2, 3, 4, S, 6, 7, 8, 9, 10, 11, 12, 13, 14. Conse 96: Sopprimere gli articoli 1,2, 3,4, 5, 6, 7, 8, 9, 10. Conseguentemente, sosti

Il risultato pare promettente: tutti questi emendamenti sono evidentemente generati a partire da un unico testo che elenca tutti gli articoli come da sopprimere.

Proviamo con il penultimo cluster, il sottoalbero di colore rosso:

78: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15. 73: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 69: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 65: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 61: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 57: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 53: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 49: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1 93: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11. 90: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12. 85: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13. 81: Sopprimere gli articoli 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14.

Ancora un risultato soddisfacente. Osserviamo inoltre che questo cluster differisce dal precedente soltanto per l'aggiunta di una frase finale, e infatti Hierarchical Clustering finirà per fonderli insieme.

Per ora ci fermiamo qui. Il lettore interessato può trovare tutto il codice sviluppato sul mio GitHub, distribuito con licenza assai permissiva (MIT).

Penso di aver dimostrato che battere lo stratagemma usato dal senatore Calderoli sia estremamente facile e mi auguro che in futuro questi non possa più adottarlo per ottenere un'influenza soverchiante sul processo legislativo.