actasum
English · Español
Formerly tallylog; renamed on 11 October 2026.
An open format and a verifier for polling-station tally sheets.
After an election, each polling station records its result on a tally sheet.
The electoral authority then adds those sheets up. actasum lets anyone
repeat that addition from the station-level figures and compare it with the
published totals.
It does not handle voting, voter identity or ballots. It looks at the step between the polling station and the published result.
Status: early draft (v0.1). The format will change. The verifier has been run on published files of Spain, Colombia, Mexico and Brazil (below). Do not rely on its output for anything else yet.
Elections run
Every run compares publications of one electoral authority with each other: its station-level files against its own totals. A match means those publications are consistent: the published totals are the sum of the published station figures. It is not a verification. No run has compared the files with paper tally sheets, and a difference does not say which figure is right or why.
All runs are therefore marked run, not verified.
| Country | Election | Stations | What matches | What does not | Status | Findings |
|---|---|---|---|---|---|---|
| Spain | Congreso, March 2008 | 59,346 | 50 of 52 constituencies; 8,111 of 8,112 municipalities | Overseas rows of Barcelona and Murcia; one municipality in Soria | run, not verified | es-congreso |
| Spain | Congreso, June 2016 | 57,531 | 52 of 52 constituencies | — | run, not verified | es-congreso |
| Spain | Congreso, April 2019 | 60,038 | 52 of 52 constituencies | — | run, not verified | es-congreso |
| Spain | Congreso, November 2019 | 59,555 | 52 of 52 constituencies; 8,131 of 8,131 municipalities | — | run, not verified | es-congreso |
| Spain | Congreso, July 2023 | 60,314 | 50 of 52 constituencies; 8,131 of 8,131 municipalities | Overseas rows of Cantabria and Toledo | run, not verified | es-congreso |
| Spain | European Parliament, June 2024 | 58,556, plus 52 overseas rows | The country and all 52 provinces | — | run, not verified | es-other-elections |
| Spain | European Parliament, May 2019 | 60,076, plus 52 overseas rows | 38 of 52 provinces; 8,126 of 8,131 municipalities | The country: the three levels published do not add up to each other | run, not verified | es-other-elections |
| Spain | Local, May 2023 | 57,681 | 5,248 of 5,272 municipalities | 24 municipalities; in 5 the station votes would give other seats | run, not verified | es-other-elections |
| Spain | Local, May 2019 | 57,188 | 5,270 of 5,291 municipalities | 21 municipalities; 8 seat allocations to explain | run, not verified | es-other-elections |
| Colombia | President, 31 May 2026, Bogotá D.C. | 17,157 | All 16 vote figures of the official result (E-26); 6,410 of 6,410 changes recorded in the AGE agree with the file | Stations: 17,164 on the E-24. The file as published is the commission's shifted by 13 bytes; repaired and checked against the hash in the AGE | run, not verified | co-presidente-2026 |
| Colombia | President, 31 May 2026, Córdoba | 4,176 | All 16 vote figures of the E-26 | — (the record of changes is a scan and was not read) | run, not verified | co-presidente-2026 |
| Mexico | President, 2 June 2024 | 158,972 in the count, 170,621 in the canvass | The canvass statements equal an INE press statement in every figure it gives | Neither sum equals it: the count has unread figures and is at least 1,883 above for unregistered candidates; the standing sum includes 5,265 ballots of 16 stations whose package was not received | run, not verified | mx-presidencia-2024 |
| Brazil | President, 4 October 2026, first round | 499,206 | Every vote figure of the 28 reports of the states and abroad, and of the national report | One section abroad has no row in the file (its report gives 0 voters) | run, not verified | br-presidente-2026 |
In Brazil, 5,246 votes for number 28 are compared as null votes because the official reports declare that they count them so; the verifier says it in its result. The Brazilian reports say their result is subject to change.
How to reproduce each run
No election data is in this repository. Each run starts from files downloaded from the authority; the hash is that of the file as downloaded.
Spain. Per-station ZIP files 02YYYYMM_MESA.zip (Congreso),
07YYYYMM_MESA.zip (European Parliament) and 04YYYYMM_MESA.zip (local),
from the Ministry of the Interior's
download area.
actasum es replay --mesa 02201911_MESA.zip # per constituency
actasum es replay --mesa 02202307_MESA.zip --level municipality # per municipality
actasum es replay --mesa 07202406_MESA.zip --level province # per province
actasum es seats --mesa 04202305_MESA.zip # would a difference change a seat?
| File | SHA-256 |
|---|---|
02201911_MESA.zip |
e7b621f295d7bf79b794941d4f11e7bcf7bd971ecbd6eb21dd4269d59926e441 |
07202406_MESA.zip |
ed30e4cf11772afb0785f2992f44fa191b8671f933865335b7093ac9ccb6659b |
07201905_MESA.zip |
8199711babd4b61ee4940469351ab4a263b0cf84a84ccfbe04ab1970a4c1a474 |
04202305_MESA.zip |
20dac696cd2efaa63c9346384ddb682f3da4fda92c6af67447159a60f1ab0de3 |
04201905_MESA.zip |
b8b1c649e3841ba735383152fd3a38259a336698d1eb79f7a653cae54c02e204 |
02200803_MESA.zip, 02201606_MESA.zip, 02201904_MESA.zip, 02202307_MESA.zip |
not recorded |
Colombia. The per-station file (MMV_…csv) and the Acta General de
Escrutinio (AGE) of each department's general scrutiny commission, from the
Registraduría's scrutiny site.
The official totals were typed from the scanned E-26 and are in
docs/findings/official/.
# Bogotá: the AGE's text gives the changes and the hash of the original file
pdftotext -layout AGE.pdf AGE.txt
actasum co convert --mmv MMV_XXX_16_000_XXX_XX_XX_XXX_1009.csv --election 2026-05 \
--age-text AGE.txt --out statements.jsonl
actasum verify --statements statements.jsonl \
--official docs/findings/official/co-presidente-2026-05-bogota.official.json
# Córdoba
actasum co convert --mmv MMV_XXX_13_000_XXX_XX_XX_XXX_1007.csv --election 2026-05 \
--sha256 2799c27a52574d85753ee0d31475cebdafd7819f88f1d977fe3b718fc6d636c9 \
--out statements.jsonl
actasum verify --statements statements.jsonl \
--official docs/findings/official/co-presidente-2026-05-cordoba.official.json
| File | SHA-256 |
|---|---|
Bogotá, MMV_XXX_16_000_XXX_XX_XX_XXX_1009.csv as downloaded |
6cec1c68ac67c73407fb1cfd30a337bab08b8b850e6d679c918d98f5e4ec000e |
| Bogotá, the same file as the commission generated it, as the AGE records | dfa5612022c2763666cd72d9553bbcfd5cfcab60076fc4a95889503a34a6ce25 |
Córdoba, MMV_XXX_13_000_XXX_XX_XX_XXX_1007.csv |
2799c27a52574d85753ee0d31475cebdafd7819f88f1d977fe3b718fc6d636c9 |
Mexico. The INE's PREP and Cómputos Distritales databases of the presidential election. The address they were downloaded from was not recorded. The official totals are typed from INE press statement 368.
# PRES_2024.csv inside 20240603_2005_PREP.zip › 20240603_2005_PREP_PRES.zip, and
# inside 20240608_2030_COMPUTOS.zip › 20240608_2030_COMPUTOS_PRES.zip
actasum mx replay --prep PREP/PRES_2024.csv --computos COMPUTOS/PRES_2024.csv \
--election 2024-06 --official docs/findings/official/mx-presidencia-2024-06.official.json
| File | SHA-256 |
|---|---|
20240603_2005_PREP.zip |
dcccf23088f81a4ab7c7942a79504b04c4a9347b0a4744f6c328c59fd4d6c31d |
its PRES_2024.csv |
c9d7282e9ba0dabdc6d0378eb3383010ad3f301a26d79c3035a9f2a2fd6d5c45 |
20240608_2030_COMPUTOS.zip |
f0b0f90483720374eaac50cfb356b43425697fabfa3f963d22a5b8f2d849312f |
its PRES_2024.csv |
860c1a539ac98b5def4dff3e82e87a19be3ebba7cb435b680ae79799942d1628 |
Brazil. The TSE's 28 ballot-box report files bweb_1t_<UF>_051020261403.zip
(26 states, the Distrito Federal and ZZ, abroad), from the dataset
Resultados - 2026 - Boletim de Urna,
and the 29 presidential totalisation reports
Relatorio_Resultado_Totalizacao_2026_<UF>.zip (the same 27, plus BR),
from https://cdn.tse.jus.br/estatistica/sead/odsele/relatorio_resultado_totalizacao/.
actasum br official needs pdftotext (Poppler).
actasum br official --round 1 \
$(for f in Relatorio_Resultado_Totalizacao_2026_*.zip; do printf -- '--report %s ' "$f"; done) \
--states-out states.official.json --country-out brasil.official.json
actasum br replay \
$(for f in bweb_1t_*_051020261403.zip; do printf -- '--bu %s ' "$f"; done) \
--official docs/findings/official/br-presidente-2026-10-04-states.official.json \
--official docs/findings/official/br-presidente-2026-10-04-brasil.official.json
The TSE publishes a SHA-512 next to each ballot-box file; the 28 are in
docs/findings/br-presidente-2026-1t.sha512
(shasum -a 512 -c checks them). The SHA-256 of each report, and its issue
time, are in the two official-totals files above.
What is here
| Path | Content |
|---|---|
docs/spec/core.md |
Country-neutral format and verification procedure |
docs/spec/log.md |
Append-only log: checkpoints, inclusion and consistency proofs |
docs/spec/witness.md |
Signed checkpoints and witnesses that cosign them |
docs/spec/profile-es.md |
Spain profile: identifiers, data source, known gaps (Spanish translation) |
docs/findings/es-congreso.md |
What the first runs on real data found |
docs/findings/es-other-elections.md |
Runs on two European Parliament elections and two local elections |
docs/spec/profile-co.md |
Colombia profile (draft, one file run) |
docs/findings/co-presidente-2026.md |
Colombia, presidential election of May 2026: Bogotá and Córdoba compared with the commissions' own hash and totals |
docs/spec/profile-mx.md |
Mexico profile (draft): INE PREP and Cómputos Distritales databases |
docs/findings/mx-presidencia-2024.md |
Mexico, presidential election of June 2024: the count as captured and the canvass, station by station |
docs/spec/profile-br.md |
Brazil profile (draft): TSE ballot-box report files |
docs/findings/br-presidente-2026.md |
Brazil, presidential election of October 2026, first round: every state and abroad against the TSE's totalisation reports |
schemas/statement.schema.json |
JSON Schema for a statement |
src/actasum/ |
Reference implementation, Python; standard library only except for signatures |
docs/releasing.md |
How a release is published to PyPI |
tests/ |
Tests on synthetic data |
Quick start
Requires Python 3.10 or later. No dependencies, except cryptography for
signing and witnesses.
pip install -e .
Download the per-station (_MESA) ZIP file of a Congreso election from the
Spanish Ministry of the Interior's
download area,
then:
actasum es replay --mesa 02201911_MESA.zip
That ZIP carries both the per-station files and the province totals. Pass
--tota to read the totals from a separate file instead.
To see in which municipalities a difference arises, compare one level down:
actasum es replay --mesa 02202307_MESA.zip --level municipality
Local elections (04…_MESA.zip) and European Parliament elections
(07…_MESA.zip) are read too, with the municipality and the whole country as
constituency. Two elections of each type have been run so far; see
docs/findings/es-other-elections.md.
For a first look at a ZIP:
actasum es describe --mesa 07202406_MESA.zip
A station may have several statements: several captures of the same tally sheet, which must agree, and later acts such as a correction in the general canvass or a repeated vote, which may lawfully differ. The verifier then shows two sums, what the stations counted and what was left standing, and says which one the official totals coincide with (core specification, sections 3.3 and 5.2).
The command prints how many constituencies match and lists every difference.
It exits with 0 when everything matches, 1 when there are differences or
gaps, and 2 when the input cannot be read.
The same thing in separate steps, producing the neutral intermediate files:
actasum es convert --mesa 02201911_MESA.zip --out statements.jsonl
actasum es official --tota 02201911_MESA.zip --out official.json
actasum verify --statements statements.jsonl --official official.json
The log
Statements can be loaded into an append-only log. A checkpoint of three lines commits to every entry, and short proofs show that one polling station's statement is in the log, or that the log has only grown.
actasum es convert --mesa 02201911_MESA.zip --out statements.jsonl
actasum log build --statements statements.jsonl --out log-2019-11 > checkpoint.txt
actasum log prove --log log-2019-11 --station es/04/003/02/001/B --out proof.json
actasum log check-proof --proof proof.json --checkpoint checkpoint.txt
Anyone who builds the log from the same ZIP file obtains the same checkpoint.
See docs/spec/log.md.
Signing and witnesses
The log can sign its checkpoint, and a witness can cosign it after checking
that the log only grew. This needs the optional cryptography package
(pip install -e '.[sign]').
# the log operator
actasum key new --name actasum/es/congreso/2019-11 --out log.key # prints the log's public key
actasum log sign --log log-2019-11 --key log.key
actasum log witness-request --log log-2019-11 --old-size 0 --out request.txt
# the witness, ideally another organization on another machine
actasum witness init --dir witness --name witness.example/w1 # prints the witness's public key
actasum witness trust --dir witness --log-key '<the log public key>'
actasum witness add-checkpoint --dir witness --request request.txt --out cosignature.txt
# back at the log, then anyone
actasum log cosign --log log-2019-11 --signatures cosignature.txt
actasum log check-checkpoint --checkpoint log-2019-11/checkpoint \
--log-key '<the log public key>' --witness-key '<the witness public key>' --quorum 1
A witness refuses to cosign a log that changed or dropped an entry it had
seen. That only means something when the witness is run by someone
independent of the log operator; running both yourself shows that the
mechanism works and nothing else. Formats follow the C2SP specifications; see
docs/spec/witness.md for what is and is not done.
What a result means, and what it does not
- Matching means the published totals equal the sum of the published station-level figures. That is consistency between two publications, not verification. Nothing more.
- A difference means they do not add up. It does not say why: lawful corrections during the official canvass, transcription slips and fraud all look the same here. A difference is a reason to ask, not a conclusion.
- In this first version both inputs come from the same authority. A match shows that its two publications are consistent with each other. It does not show that either reflects the paper tally sheets. Independent captures of the sheets are the subject of later milestones.
Design in one paragraph
A country-neutral core defines the statement, the official totals and the verification rules. A profile per country maps local law and data onto the core. Spain is the first profile because its results are public down to the polling station. Later milestones add an append-only transparency log (a Merkle tree with independent witnesses, as used for web certificates), signed checkpoints, signed statements from accredited observers using W3C Verifiable Credentials, and several independent captures per station.
Roadmap
- Format and verifier on a past election, from public data. (run on elections of Spain, Colombia, Mexico and Brazil)
- Load a full election into a log and serve inclusion proofs. (first version)
- Replay the 2026 Colombian presidential runoff from the published E-14 forms.
- A witness that cosigns the log state. (mechanism done; no external witness yet)
- Credentials and a capture app, with on-device signing and offline use.
- A small real pilot together with a local election-observation organization.
Running the tests
pip install pytest
pytest
Data sources and acknowledgements
- Election results: Ministerio del Interior (Spain), Área de descargas; Registraduría Nacional del Estado Civil (Colombia), scrutiny site; Instituto Nacional Electoral (Mexico); Tribunal Superior Eleitoral (Brazil), open-data portal. No election data is included in this repository.
- The column layout of the Ministry's files was first taken from the
rOpenSpain
infoelectoralR package by Héctor Meleiro, then checked against the Ministry's own layout document. No code from that package is included here.
License
Metadata
Release files for actasum 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| actasum-0.1.0.tar.gz | 123.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| actasum-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 209.1 kB
Release files / actasum-0.1.0.tar.gz
| Download URL | actasum-0.1.0.tar.gz |
|---|---|
| Size | 123.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a6e4e93bc53abc4a43e80208f1a05a2803d630da6fa3ebf49e57df5d7bec7519
|
|
BLAKE2b-256 checksum How to use checksums |
dd6342f9e5a43bf67efae857dee141da695318be5439db39ea1504c426f9defd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / actasum-0.1.0-py3-none-any.whl
| Download URL | actasum-0.1.0-py3-none-any.whl |
|---|---|
| Size | 85.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4421ad6650bba68bc6f301809e4ba456017370b156422d0061679f4afca9304
|
|
BLAKE2b-256 checksum How to use checksums |
c30a5db14ef161ad18e0c76f6d9918b4667770a0353b6e1206a1b914f8fade25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for actasum 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for actasum-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.