English: README.en.md
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.
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.
- 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, richiedeopenpyxl),--parquet(richiedepyarrow) - 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 autoscarica da solo il file ufficiale del Ministero, con cache) - Rispetto del server: pausa configurabile tra le richieste
Nuovi qui? C'è un cheatsheet con i comandi essenziali spiegati passo passo.
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]
○── Join ISTAT popolazione (affluenza normalizzata) [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.
- 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° turnooballottaggio, rilevato dagli header delle tabelle del sito ("II turno").
- 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.--xlsxe--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.
--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 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.
- 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 intests/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.
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# 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| 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) |
--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) |
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 FIRENZEOutput 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).
Lo script non conosce a priori le date di elezione di un comune: le legge
dal sito. Il primo <select> del form (sel_date) contiene tutte le date
disponibili per il tipo di elezione scelto (per le comunali sono oltre 160,
dal 1970 a oggi). Per ogni data lo script:
- seleziona la data nel form;
- scende la gerarchia (regione → provincia → comune) con le stesse chiamate che fa il browser quando clicchi i menu a tendina;
- se il comune compare nell'elenco del livello finale, scarica la pagina
dei risultati e la parsa; altrimenti registra l'esito
NON_VOTATO(il comune non ha avuto elezioni in quella data: mandato in corso, commissariamento, scioglimento) e passa alla data successiva.
Alla fine il CSV contiene una riga per ogni lista/candidato di ogni
consultazione in cui il comune ha votato: è la serie storica completa.
Il log nel JSON distingue OK, NON_VOTATO ed eventuali ERRORE.
L'anagrafe degli amministratori locali
è un open data del Ministero dell'Interno: il file ammcom.csv (aggiornato
con cadenza periodica) elenca, per ogni comune, gli amministratori in
carica con nome, cognome, carica (sindaco, assessore, consigliere,
commissario…), date di elezione e di entrata in carica, lista.
Due modi di usarlo:
# 1) automatico: scarica il file ufficiale dal portale del Ministero
# (cache in ~/.cache/tallyho/, ~30 MB una tantum)
tallyho --comuni ROMA --dait auto
# 2) manuale: passi un CSV già scaricato (anche filtrato per i comuni)
tallyho --comuni ROMA --dait ammcom.csvA quel punto integra_dait:
- legge il CSV (salta le prime righe di titolo e data di aggiornamento);
- filtra le righe per
denominazione_comunetra i comuni richiesti; - aggiunge al JSON di output una sezione
amministratori_daitcon, per ogni comune, l'elenco degli amministratori in carica (carica, nominativo, date, lista).
È utile per incrociare i risultati elettorali con chi governa: es. capire se il sindaco uscente è stato riconfermato o se il comune è sotto commissariamento.
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-dataCosa 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 sezioneamministratori_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.
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).
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=SIoppureNO;voti_lista/pct_lista= voti e percentuale dell'opzione, ripetuti anche invoti_candidato/pct_candidato;- le colonne base (
elettori,votanti,affluenza_pct,bianche,non_valide) riportano i valori del quesito; elettoeseggirestano vuoti.
Il campo valide (schede valide del quesito) resta solo nel JSON: non
compare nel CSV.
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.
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).
- 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.txte 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.
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
│ ├── 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
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 intests/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
pytestverde (i test offline girano senza rete; i test di integrazione live sono esclusi di default e richiedonopytest -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.
MIT
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.
