Skip to content

Repository files navigation

TallyHo

English: README.en.md

TallyHo

TallyHo ti porta a spasso nella storia elettorale italiana — dal 1946 a oggi, comune per comune, senza un solo click a mano.

L'Archivio Storico delle Elezioni del Ministero dell'Interno (DAIT) è un tesoro: 70 anni di voti, sindaci, liste, affluenze e ballottaggi. Peccato che per guardarlo devi cliccare un form JavaScript a più passi — data, area, regione, provincia, comune — e ricominciare da capo per ogni data. TallyHo fa i clic al posto tuo: gli dici "portami tutto quello che ha votato questo comune" e lui scende la gerarchia per ogni data disponibile, estrae i risultati e te li consegna in CSV e JSON, pronti per l'analisi.

Perché esiste

L'Archivio Storico delle Elezioni è la fonte ufficiale più completa in assoluto (elezioni comunali dal 1970, politiche dal 1946), ma è pensato per la consultazione visiva, non per il download: ogni comune, ogni data e ogni livello territoriale richiedono una sequenza di clic su un form JavaScript a più passi. Con oltre 160 date possibili per le sole comunali, ricostruire a mano la storia elettorale di un comune significa migliaia di interazioni ripetitive. TallyHo nasce per automatizzare quel percorso: prende ciò che il sito espone (le stesse chiamate che fa il browser) e lo trasforma in un dataset strutturato (CSV + JSON), pronto per analisi, visualizzazioni e ricerche. Uno strumento per chi studia il voto — dal singolo ricercatore al giornalista di dati — senza dover cliccare cinquant'anni di elezioni un giorno alla volta.

Funzionalità

  • Estrazione serie storica per comune (tutte le date disponibili)
  • Tipi di elezione: comunali (G), regionali (R), provinciali (P), politiche Camera/Senato (C/S), europee (E), referendum (F), Costituente (A)
  • Discesa gerarchica dinamica: regione → provincia → comune (comunali), regione con risultati a quel livello (regionali), circoscrizione → collegi plurinominali/uninominali (politiche post-2017), 5 livelli per europee (circoscrizione → regione → provincia → comune) e provinciali (regione → provincia → collegio → comune)
  • Gestione automatica delle province storiche (i comuni di una provincia istituita nel 1992 risultavano nella provincia originaria per le elezioni precedenti)
  • Formato risultati moderno (candidati + liste) e storico (sole liste, amministratori eletti dal consiglio)
  • Referendum (F): per ogni quesito, voti SI/NO, percentuali, affluenza e schede
  • Distinzione del turno: campo turno ("1° turno" / "ballottaggio") in CSV e JSON
  • Affluenza calcolata (fallback votanti/elettori*100) quando il sito non espone la percentuale
  • Export CSV (delimitatore ;, UTF-8 BOM per Excel) e JSON
  • Export aggiuntivi: --long (formato tidy per pandas/R), --xlsx (Excel, richiede openpyxl), --parquet (richiede pyarrow)
  • File di configurazione --config (TOML/YAML) per riusare le opzioni
  • Esplorazione dei valori del form (--elenca): regioni, province, comuni e date reali del sito senza ispezionare il browser
  • Valore della regione ricavato automaticamente dal nome
  • Integrazione anagrafe amministratori DAIT (--dait auto scarica da solo il file ufficiale del Ministero, con cache)
  • Pool dati ISTAT via API SDMX (--istat COMUNE): quote marginali demografiche (età, istruzione, occupazione) per comune, con cache persistente 30 giorni
  • Pool dati IRPEF comunali MEF (--reddito COMUNE): quote marginali per fascia di reddito + reddito medio, pensione_ratio, tax_ratio, con cache persistente 30 giorni
  • Pool indicatori BES benessere/fiducia ISTAT (--bes COMUNE): fiducia nelle istituzioni e partecipazione (Bes dei territori)
  • Rispetto del server: pausa configurabile tra le richieste

Nuovi qui? C'è un cheatsheet con i comandi essenziali spiegati passo passo.

Roadmap (stato)

                                TALLYHO — STATO IMPLEMENTAZIONI
   ● = fatto   ◐ = in corso   ○ = da fare

   CORE
   ●── Estrazione serie storica per comune (tutte le date)
   ●── Tipi di elezione: G R P C S E F A (comunali→costituente)
   ●── Parser referendum (SI/NO, per quesito)
   ●── Parsing regionali (candidati presidenti + liste collegate)
   ●── Flag turno (1° turno / ballottaggio)
   ●── Gerarchia dinamica fino a 5 livelli (europee, provinciali)
   ●── Province storiche (pre-1992)
   ●── Affluenza fallback (votanti/elettori*100)
   ●── Gestione errori: OK / NON_VOTATO / ERRORE + pausa uniforme

   EXPORT
   ●── CSV (UTF-8 BOM, ;) · JSON · --long (tidy pandas/R)
   ●── --xlsx (openpyxl) · --parquet (pyarrow)
   ○── Report Markdown per comune (agenti LLM / KB)   [P6]

   ROBUSTEZZA
   ●── Cache dei form in SQLite (~/.cache/tallyho)     [P2]
   ●── Retry con backoff (2/4/8 s)                     [P2]
   ◐── Catena condivisa tra comuni (~3x meno richieste) [P2]
   ●── Rilevamento cambi del sito (exit 3 se `sel_date` manca) [P3]
   ●── CI attivo (GitHub Actions — matrix py3.9-3.12, pytest+ruff) [P1]

   TERRITORIO E ANALYTICS
   ○── Dataset builder --provincia / --tutti-comuni    [P4]
   ○── Aggregazione provincia/regione (tallyho-aggrega) [P4]
   ○── Swing + volatilità Pedersen                     [P5]
   ●── Pool ISTAT demografia (--istat, quote [0,1])    [P5]
   ●── Pool IRPEF MEF (--reddito, fasce di reddito)    [P5]
   ●── Pool BES benessere/fiducia (--bes)              [P5]
   ○── Scrutini per sezione Eligendo (EI, in attesa tornata) [P5]
   ○── Clustering comuni · GIS GeoJSON · grafici       [P5]

   DISTRIBUZIONE
   ●── Zenodo + DOI 10.5281/zenodo.21979207 (v1.0.0)   [P3]
   ○── PyPI (pip install tallyho)                      [P6]
   ○── API locale FastAPI                              [P6]
   ○── Server MCP per agenti Hermes                    [P6]
   ●── README EN + Contributing                        [P3]

Dettaglio delle singole voci (priorità, stato, note operative): vedi ROADMAP.md.

Implementazioni in dettaglio

Estrazione

  • Serie storica completa: tutte le date disponibili per comune, dal 1970 (comunali) o 1946 (altri tipi) a oggi, in un solo comando.
  • Tipi di elezione: comunali (G), regionali (R), provinciali (P), politiche Camera/Senato (C/S), europee (E), referendum (F), costituente (A).
  • Referendum: per ogni quesito vengono estratti titolo, elettori, votanti e %, valide/bianche/non valide, voti SI e NO con percentuali.
  • Regionali: candidati presidenti (righe leader) e liste collegate, con esito eletto, voti, % e seggi; i totali di coalizione e le righe "LISTE CIRCOSCRIZIONALI" del TOTALE vengono ignorati.
  • Gerarchia dinamica: la discesa si adatta al numero reale di livelli della pagina (fino a 5: circoscrizione → regione → provincia → collegio → comune), quindi europee e provinciali funzionano come comunali.
  • Province storiche: i comuni di province istituite dopo il 1992 risultano nella provincia originaria per le elezioni precedenti (es. --province LECCO,COMO).
  • Turno: ogni consultazione è marcata 1° turno o ballottaggio, rilevato dagli header delle tabelle del sito ("II turno").

Export

  • CSV: delimitatore ;, BOM UTF-8 (apre correttamente in Excel), una riga per lista/candidato.
  • JSON: struttura annidata con log di navigazione (OK / NON_VOTATO / ERRORE per ogni data).
  • --long: CSV tidy con una riga per osservazione (livelli scheda/candidato/lista) pronto per pandas e R senza post-processing.
  • --xlsx e --parquet: richiedono i pacchetti opzionali (pip install "tallyho[xlsx]" / "tallyho[parquet]").
  • --dait: integra l'anagrafe amministratori DAIT (sindaci, commissari, assessori in carica con date e lista) nel JSON.

Configurazione

  • --config file.toml (o .yaml): default riusabili per tutte le opzioni; la riga di comando ha sempre la precedenza.
  • --no-cache / --cache-ttl: controllo della cache dei form.

Cache e rispetto del server

  • Cache SQLite (~/.cache/tallyho/cache.db): le pagine del form scaricate vengono riusate per 7 giorni (default), con un risparmio enorme su dataset grandi. Statistiche a fine run (cache: N/N richieste servite da cache).
  • Pausa configurabile (--sleep, default 1.2 s) applicata tra le date e sui rami di errore/NON_VOTATO: nessun burst di richieste.

Qualità e test

  • Architettura modulare: costanti / navigazione / parsing / export / cache / tallyho (CLI). API pubblica in __init__.py.
  • 84 test (offline, senza rete) + test di integrazione opzionale (-m integration) contro il sito reale; fixture HTML reali in tests/fixtures/.
  • Test di integrazione live su 5 tipi di elezione (G/R/F/E/P), eseguiti esplicitamente con pytest -m integration.
  • README EN disponibile in README.en.md per i contributori internazionali.

Installazione

git clone https://github.com/jack89-ML/tallyho
cd tallyho
python3 -m venv .venv
.venv/bin/pip install -e .

Sviluppo/test:

.venv/bin/pip install -e ".[dev]"
pytest

Uso

# Serie storica comunale (es. Roma e Milano)
tallyho --comuni ROMA,MILANO

# Equivalente via python -m
python -m tallyho --comuni ROMA,MILANO

# Solo una data (test rapido)
tallyho --comuni ROMA --data 03/10/2021

# Solo l'ultima data disponibile
tallyho --solo-ultima-data

# Regionali (i risultati sono a livello regione)
tallyho --tipo R --comuni ROMA --nome-regione LAZIO

# Integrazione anagrafe amministratori DAIT nel JSON
tallyho --comuni ROMA --dait ammcom.csv

# Output in una cartella specifica, pausa più lunga
tallyho --comuni ROMA --out ./dati --sleep 2.0

Opzioni

Opzione Default Descrizione
--comuni Comuni da cercare, separati da virgola
--elenca Esplora i valori del form (date, regioni, province, comuni) e esce
--regione (auto) Valore option della regione — opzionale, ricavato dal nome
--nome-regione LAZIO Nome della regione/circoscrizione da cercare
--province ROMA Province ammesse (per nome, virgola)
--tipo G Tipo elezione: G comunali, R regionali, P provinciali, C camera, S senato, E europee, F referendum, A costituente
--out dati_elezioni Cartella di output
--sleep 1.2 Secondi tra una data e l'altra
--data Processa solo una data (gg/mm/aaaa)
--solo-ultima-data Solo l'ultima data (test)
--dait CSV|auto Anagrafe amministratori DAIT nel JSON (auto = download automatico)
--istat COMUNE Pool dati ISTAT via API SDMX: quote marginali demografiche (età, istruzione, occupazione) per nome o codice ISTAT
--reddito COMUNE Pool dati IRPEF comunali MEF: quote marginali per fascia di reddito + indicatori (reddito medio, pensione_ratio, tax_ratio) per nome o codice ISTAT
--bes COMUNE Pool indicatori BES benessere/fiducia ISTAT (fiducia istituzioni, partecipazione) per nome o codice ISTAT
--long off Esporta anche il CSV in formato tidy (una riga per osservazione, pronto per pandas/R)
--xlsx off Esporta anche in Excel (.xlsx) — richiede openpyxl (pip install "tallyho[xlsx]")
--parquet off Esporta anche in Parquet — richiede pyarrow (pip install "tallyho[parquet]")
--config File di configurazione TOML/YAML con i default delle opzioni (la CLI ha precedenza)

Esplorare i valori senza aprire il browser

I valori di --regione e --province si scoprono da soli con l'opzione --elenca: interroga il sito e stampa le <option> reali del form (formato valore = nome).

# tutte le date disponibili per le comunali
tallyho --elenca date

# regioni che hanno votato il 14/05/2023 (valore = nome)
tallyho --elenca regioni --data 14/05/2023

# province della Toscana in quella data
tallyho --elenca province --data 14/05/2023 --nome-regione TOSCANA

# comuni della provincia di Firenze in quella data
tallyho --elenca comuni --data 14/05/2023 \
    --nome-regione TOSCANA --province FIRENZE

Output di esempio:

  09-lev19   =  TOSCANA
  12-lev112  =  LAZIO
  ...
  048-lev248 =  FIRENZE
  ...
  48017-lev348017  =  EMPOLI

In pratica non serve mai conoscere i valori a memoria: lo script ricava --regione automaticamente dal nome (--nome-regione, di default LAZIO) e --province funziona per nome. --elenca serve per verificare quali aree hanno votato in una data (o per esplorare altre regioni).

Come funziona (spiegazioni dettagliate)

Le spiegazioni approfondite di ogni funzionalità vivono in cartelle dedicate (per non appesantire la scheda principale):

Funzionalità Guida
Serie storica per comune come_funziona_exp/serie_storica.md
Anagrafe amministratori --dait come_funziona_exp/dait.md
Dati demografici --istat come_funziona_exp/istat.md
Redditi IRPEF --reddito come_funziona_exp/reddito.md
Benessere e fiducia --bes come_funziona_exp/bes.md

Come estrarre la serie storica (comandi pratici)

La serie storica di un comune è l'insieme di tutte le consultazioni in cui ha votato, dal 1970 (comunali) o dal 1946 (altri tipi) a oggi. Lo script la estrae da solo iterando su tutte le date disponibili:

# serie storica COMPLETA di uno o più comuni (tutte le date, tutti i tipi
# selezionati con --tipo; default comunali)
tallyho --comuni FIRENZE,PRATO --nome-regione TOSCANA \
    --province FIRENZE,PRATO --out dati_elezioni

# stessa cosa, ma salvando anche l'anagrafe degli amministratori nel JSON
tallyho --comuni FIRENZE,PRATO --nome-regione TOSCANA \
    --province FIRENZE,PRATO --dait auto

# anche le elezioni regionali della zona
tallyho --comuni FIRENZE --tipo R --nome-regione TOSCANA

# test rapido: solo l'ultima data (per verificare che tutto funzioni)
tallyho --comuni FIRENZE --nome-regione TOSCANA \
    --province FIRENZE --solo-ultima-data

Cosa produce (nella cartella --out):

  • elezioni_<timestamp>.csv — una riga per ogni lista/candidato di ogni consultazione in cui il comune ha votato: data, comune, provincia, elettori, votanti, affluenza, bianche, non valide, candidato, eletto, voti, %, lista, seggi;
  • elezioni_<timestamp>.json — la stessa struttura annidata + il log di navigazione (OK / NON_VOTATO / ERRORE per ogni data) +, con --dait, la sezione amministratori_dait.

I NON_VOTATO nel log sono informativi: il comune non ha avuto elezioni in quella data (mandato in corso, commissariamento, scioglimento) — la serie storica è comunque completa perché copre tutte le date possibili.

Formato output

CSV (;)

data_elezione;comune;provincia;elettori;votanti;affluenza_pct;bianche;non_valide;candidato;eletto;voti_candidato;pct_candidato;lista;voti_lista;pct_lista;seggi
03/10/2021;ROMA;ROMA;2359248;1145268;;12389;35356;GUALTIERI ROBERTO;True;299976;27.03;PARTITO DEMOCRATICO;166194;16.38;18

Nelle elezioni storiche (amministratori eletti dal consiglio) la colonna candidato è vuota e restano le liste; nelle regionali il dato è a livello regione (replicato per ogni comune richiesto).

Referendum (CSV)

Per i referendum (--tipo F) il CSV contiene una riga per quesito per ciascuna opzione SI e NO:

  • candidato = titolo del quesito (es. "Q1. Test");
  • lista = SI oppure NO;
  • voti_lista / pct_lista = voti e percentuale dell'opzione, ripetuti anche in voti_candidato / pct_candidato;
  • le colonne base (elettori, votanti, affluenza_pct, bianche, non_valide) riportano i valori del quesito;
  • eletto e seggi restano vuoti.

Il campo valide (schede valide del quesito) resta solo nel JSON: non compare nel CSV.

JSON

Struttura completa con affluenza, schede, candidati e liste per ogni consultazione, più un log di navigazione (OK / NON_VOTATO / ERRORE) e, con --dait, la sezione amministratori_dait.

Come funziona (per contributori)

Il sito usa un form con <select> pilotati da JS:

<select name="sel_sezione2" onchange="carica_pagina('index.php?tpel=G&dtel=...&tpe=R&...','ne1',this.options[this.selectedIndex].value);">
  <option value="12-lev112">LAZIO</option>

I valori delle option sono codificati e vanno decodificati:

12-lev112        ->  ne1=12&lev1=12        (regione)
58-lev258        ->  ne2=58&lev2=58        (provincia)
58091-lev558091  ->  ne3=58091&lev3=58091  (comune)
I-lev00-levsut00-msN-tpeA -> tpa=I&lev0=0&levsut0=0&ms=N&tpe=A  (area)

Lo script segue l'onchange di ogni select come farebbe il browser, mantenendo una sessione HTTP unica, e si ferma al primo livello la cui pagina contiene già la tabella dei risultati (così le regionali si fermano alla regione e le comunali arrivano al comune). La pagina dei risultati contiene tre tabelle: affluenza, schede, e candidati+liste (o sole liste per il periodo 1970-1985).

Limiti e note

  • Il server non fornisce un'API pubblica: questo strumento automatizza la navigazione del form ufficiale; usare con moderazione (--sleep).
  • Prima di un uso esteso, verifica robots.txt e i termini d'uso del sito (elezionistorico.interno.gov.it) e rispettane le eventuali limitazioni: l'uso moderato già previsto non sostituisce la conformità alle condizioni del portale.
  • Per le elezioni politiche il livello minimo pubblicato è il collegio uninominale/plurinominale, non il comune: la ricerca per comune non trova risultati (limite della fonte, non dello strumento).
  • I risultati vanno verificati sul sito ufficiale per usi istituzionali.

Struttura del progetto

tallyho/
├── src/tallyho/
│   ├── costanti.py      # costanti condivise (BASE, UA, DAIT_AMMCOM_URL, TIPO_ETICHETTE)
│   ├── navigazione.py   # decodifica opzioni/area, leggi_select/onchange/date, scendi_livello, trova_comune
│   ├── parsing.py       # estrai_tabelle/liste, parse_affluenza/schede/candidati/risultati, parse_referendum, parse_candidati_regionali
│   ├── export.py        # esporta_csv/json/long/xlsx/parquet, scarica_ammcom, integra_dait
│   ├── istat.py         # pool dati ISTAT via API SDMX: estrai_dati_istat, cache 30gg, normalizzazione quote [0,1]
│   ├── mef.py           # pool dati IRPEF comunali MEF: estrai_redditi_comune, fasce di reddito, cache 30gg
│   ├── bes.py           # pool indicatori BES benessere/fiducia ISTAT: estrai_benessere, trust_indicators
│   ├── tallyho.py       # main() e logica CLI
│   ├── __init__.py      # API pubblica
│   ├── cli.py           # entry point (console script)
│   └── __main__.py      # entry point (python -m tallyho)
├── tests/               # test unitari (decodifica, parsing, export, config — senza rete)
│   └── fixtures/        # pagine HTML reali del sito (per i test di parsing)
├── examples/            # esempi di output
├── CHEATSHEET.md        # guida rapida per chi inizia
├── pyproject.toml
└── README.md

Contributing

Contributi benvenuti. Prima di aprire una PR:

  • Segnala un bug: apri una issue descrivendo il comando eseguito, il tipo di elezione (--tipo), la data e l'output atteso vs. ottenuto. Se possibile allega una pagina HTML salvata (le fixture in tests/fixtures/ aiutano a riprodurre senza rete).
  • Proponi una miglioria: apri una issue per discuterne prima di scrivere codice; controlla la ROADMAP per le priorità.
  • Test obbligatori: ogni PR deve far girare pytest verde (i test offline girano senza rete; i test di integrazione live sono esclusi di default e richiedono pytest -m integration). Aggiungi test per il codice nuovo.
  • Niente sed/awk multilinea: le modifiche ai file devono essere patch esatte (nessuna sostituzione con regex fragili).
  • Stile: italiano per commenti, messaggi e output; inglese solo per README.en.md. Segui lo stile del codice esistente.

Le PR che non rispettano queste regole non vengono considerate.

Licenza

MIT

Citazione

I dati esposti provengono dall'Archivio Storico delle Elezioni del Ministero dell'Interno (DAIT), fonte pubblica e ufficiale: elezionistorico.interno.gov.it.

Chi utilizza TallyHo — o i dataset da esso generati — in ricerche, pubblicazioni, articoli, tesi o progetti accademici è tenuto a citare lo strumento, ad esempio come segue:

Peracchio, Jacopo (2026). TallyHo: serie storica elettorale italiana, comune per comune (Versione 1.0.0) [Software]. https://doi.org/10.5281/zenodo.21979207

In BibTeX:

@software{TallyHo,
  author = {Peracchio, Jacopo},
  title = {{TallyHo}: serie storica elettorale italiana, comune per comune},
  year = {2026},
  version = {1.0.0},
  doi = {10.5281/zenodo.21979207},
  url = {https://github.com/jack89-ML/tallyho}
}

Il file CITATION.cff nella radice del repository abilita il pulsante Cite this repository su GitHub, che genera la citazione nei formati APA e BibTeX.

Ogni release di TallyHo è inoltre archiviata su Zenodo (servizio CERN di archiviazione per la ricerca) con un DOI permanente: la versione 1.0.0 ha il DOI 10.5281/zenodo.21979207. Il DOI identifica la versione archiviata in modo stabile, anche se il link a GitHub cambiasse.

About

Serie storica elettorale italiana dal 1946: elezionistorico.interno.gov.it (Ministero dell'Interno DAIT) per comune — CSV/JSON/XLSX/Parquet. Open data, CLI, 90+ test, CI, DOI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages