← Back to projects

Civic data · 2025

Elecciones HN 2025

An independent count of the 2025 Honduras presidential election, extracted from the official actas by a local vision model and published department by department — alongside the CNE's own numbers, so anyone can compare the two.

Python Ollama ministral-3 React Vite Express pdf2image

What it claims, and what it does not

This is the disclaimer the site itself carries, and it is the point. The count adds up the votes exactly as they appear on the actas, and works only with the information contained in those documents. Whether an acta has been altered is not something this project can control — it works from public information and is not affiliated with the CNE or any political party. If there were manipulation in the actas, these results would reflect that same manipulation.

That sentence decided the architecture. A tool that cannot verify the source document has exactly one honest job: transcribe faithfully, show its work, and make the original reachable. So every number on the site is traceable back to the acta it came from, and the acta image sits one click away.

The site stopped updating on 19 December 2025, and says so in a banner rather than quietly going stale.

Coverage

The real figures from the pipeline's own report, not rounded for effect.

19,137

actas published by the CNE and pulled down.

16,895

actas read, validated and counted.

88.28%

coverage — and the shortfall is reported, not hidden.

2,245

actas left unprocessed, each one listed by id in the report.

Across 18 departments plus voto en el exterior, counting five parties: PNH, PLH, LIBRE, PINU and DC.

From a scanned acta to a number

Every stage writes its output to disk, so a bad reading can be found, corrected and re-run without repeating the whole pipeline.

Pipeline from CNE download through OCR and validation to the published count CNE portal download_actas.py PDF → image pdf2image · poppler vision model Ollama · ministral-3 JSON per acta N and L per party validation four failure categories fails actas editor human corrects, side by side re-checked, not trusted passes published count by department · vs CNE centros · mesas municipios · zonas reference data every stage writes to disk — a bad reading is found, fixed and re-run on its own

A local model, deliberately. The actas are public, but running 19,137 of them through a hosted API would have been slow, expensive and someone else's dependency.

How a bad reading is caught

An acta has arithmetic built into it, and that arithmetic is what validates the OCR. Each acta is sorted into one of five buckets.

ValidNumbers parse, and the party totals sum to the gran total. Counted.
Empty gran totalThe model read no overall total, so nothing can be checked against it.
Gran total exceeds 400A mesa cannot hold more than 400 votes. Anything above it is a misread digit, not a landslide.
Sum ≠ gran totalThe parties add up to something other than the stated total — the single most useful signal that a digit was misread.
Other errorsEverything that fails for a reason not covered above, kept separate rather than swept into one pile.
The 400 rule is the cheapest check in the project. It needs no model, no second source and no judgement — just the knowledge that a Honduran mesa is capped at 400 voters. It catches an entire class of OCR failure for free.

The correction tool

Actas that fail validation are not discarded and not guessed at. They go to a small editor built for one job: put the original PDF next to the extracted JSON and let a human fix the digit.

Filter by status

Work through one failure category at a time instead of hunting through 19,137 files.

PDF side by side

The original acta rendered next to the values, via react-pdf and pdfjs-dist.

Edit N and L

Both the numeric and written-out values an acta carries, with the total re-summed as you type.

Re-validated after

A corrected acta goes back through the same checks. A human edit is not a free pass.

The site

Screenshots go here.

Mi Conteo The independent count, with the per-department gap for each candidate. assets/hn2025-mi-conteo.webp
Conteo CNE The official numbers, shown beside the independent ones. assets/hn2025-conteo-cne.webp
Análisis a CNE Where the two counts disagree, and by how much. assets/hn2025-analisis.webp
Actas The index of scanned actas, each traceable to its mesa. assets/hn2025-actas.webp

Running the pipeline

process_actas_ocr.py
$ python3 process_actas_ocr.py --single 16-004-04-001-16498.pdf
============================================================
ACTAS OCR PROCESSING
============================================================
Found 1 PDF file(s) to process

  → 16-004-04-001-16498.pdf
    Converting PDF to image... 
    Running OCR analysis... 
    Saving JSON result... 
  ✓ 16-004-04-001-16498.pdf - Completed

Processing Summary:
  Successfully processed: 1
============================================================

Roughly two to three minutes per acta on local hardware. Multiply by 19,137 and the reason the pipeline is resumable, chunked and writes every stage to disk becomes obvious.

What the repository holds

utils/The pipeline: fetching zonas, municipios, centros and mesas from the CNE; downloading actas; converting PDFs; running OCR; comparing votes; finding inconsistencies and unprocessed files.
app/client/The correction tool's React + Vite interface, with react-pdf for the side-by-side view.
app/server/A small Express server that reads and writes the acta JSON files on disk.
actas_data_cne/The CNE's own published figures, kept separate from the extracted ones so the two can be compared rather than merged.
actas_corrections/Human corrections, stored apart from the raw model output so the original reading is never lost.
reports/validation_report.txt, unprocessed_actas_report.json and the inconsistency lists that produced the coverage figures above.
Corrections live in their own directory. Raw model output is never overwritten, so at any point you can ask what the model actually said before a human touched it — which is the only way an independent count can answer a challenge.
Elecciones HN 2025 — built by MilanDroid · frozen 19 Dec 2025 All projects