# EXPERIMENT: zero-vote sections in Brazil's 2026 presidential runoff

Written 2026-10-29 before any result was computed. Only file listings and
HTTP headers had been seen at this point. Changes made after results are
listed at the end under "Deviations", with the reason.

## The claim under test (press reports, 2026-10-28)

- 106 sections where Flávio Bolsonaro got zero votes in round 2. Lula got
  14,860 votes in them. 65 are in the North, 29 in the Northeast. About 68 are
  reportedly in indigenous villages or schools.
- 4 sections where Lula got zero: [Redacted before publication: four municipality
  names given in the press report. HMAC-SHA256 with the key in item 17:
  82db8da1e2cdb8568996ed7821d590e0a70e1d641cf585260b68766c190f2b98]. Flávio got 296 votes in them.

Neutral question: are these zero-vote sections what one would expect from how
people voted locally, for each candidate symmetrically, and compared with the
2022 and 2018 runoffs?

## Data

All files come from the TSE open-data CDN (cdn.tse.jus.br), listed on
dadosabertos.tse.jus.br. `00_download.sh` records URL, size, HTTP
last-modified, download time and SHA-256 in `sources.tsv`. Where the portal
publishes a SHA-512 file, `01_verify_hashes.py` checks it.

- Votes by section, president: `votacao_secao_<year>_BR.zip` (2026, 2022, 2018).
- Section detail (electorate, turnout, blank, null): `detalhe_votacao_secao_<year>.zip`.
- Municipal totals: `votacao_candidato_munzona_<year>.zip`.
- Polling places: `eleitorado_local_votacao_<year>.zip`.
- Ballot-box bulletins, 2026 round 2: `bweb_2t_<UF>_261020260742.zip` and `.sha512`.

## Definitions

- **Section**: one row key (state, municipality code, zone, section number)
  for round 2 (`NR_TURNO = 2`) and the office of President (`CD_CARGO = 1`).
  The universe of sections comes from the section-detail file, so a candidate
  with no row in the votes file for a section that exists counts as zero.
  Sections abroad (state code `ZZ`) are kept and shown as region "Abroad".
  Sections that were merged into another section on election day are counted
  as the TSE file reports them. No re-aggregation.
- **Valid votes (n)**: votes for the two candidates in that section. Blank and
  null votes are not valid votes.
- **Zero votes for candidate X**: X has 0 votes in a section with n >= 1. Sections
  with n = 0 (no valid vote at all) are reported separately and are not zeros
  for either side, because they say nothing about preference.
- **Small sections**: the primary count uses all sections with n >= 1. Sensitivity
  counts use n >= 10 and n >= 50. Models handle size directly because the
  probability of a zero depends on n.
- **Near zero**: (a) X's share of valid votes below 1%, and (b) X has 2 votes or
  fewer. Both with n >= 1. Zeros are included in both.
- **Candidates by year**: 2026 Lula vs Flávio Bolsonaro. 2022 Lula vs Jair
  Bolsonaro. 2018 Jair Bolsonaro vs Fernando Haddad. The comparison across
  years is by side: the PT candidate (Lula 2026, Lula 2022, Haddad 2018) and the
  opposing candidate (Flávio 2026, Bolsonaro 2022, Bolsonaro 2018). Names come
  from the data files, not from this document.
- **Region**: the five IBGE macro-regions from the state code, plus Abroad.

## Models (expected number of zeros for each candidate)

For section i with n_i valid votes, let q_i be the probability that candidate
X gets zero votes. Expected zeros E = sum of q_i. Its spread comes from the
sum of q_i(1 - q_i) (sections treated as independent). Each model is fitted and
evaluated in the same way for both candidates and all three years.

1. **Binomial, municipality leave-one-out (B-mun).** p_i = X's share of valid
   votes in the rest of the municipality, excluding section i.
   q_i = (1 - p_i)^n_i. If the rest of the municipality has no valid votes,
   the state leave-one-out share is used.
2. **Binomial, polling-place leave-one-out (B-place).** Same, with p_i taken
   from the other sections of the same polling place. Falls back to B-mun when
   the place has no other section with valid votes.
3. **Beta-binomial, municipality level (BB-mun).** The section share is drawn
   from a Beta distribution with mean equal to the municipality leave-one-out
   share and concentration phi. phi is estimated by maximum likelihood, one value
   per state per year, from all sections of that state. Then
   q_i = B(a, b + n_i) / B(a, b) with a = mu_i * phi, b = (1 - mu_i) * phi.
   Leave-one-out shares are clipped to [1e-4, 1 - 1e-4] so a and b stay positive.
   The likelihood is fitted to the counts of one candidate. The beta-binomial is
   symmetric in the two candidates, so one fit serves both.
4. **Beta-binomial, polling-place mean (BB-place).** Same as 3 with the
   polling-place leave-one-out mean. Sensitivity only.

The same machinery gives expected near-zero counts (share < 1%, votes <= 2) by
summing the model probability of those events per section.

## Questions

1. Replicate the counts of zero sections per candidate in 2026 round 2, with
   regional breakdown and the opponent's votes in those sections. Compare to
   106 / 14,860 and 4 / 296. Explain any gap (definition, abroad, data revision).
2. Describe the zero sections: electorate, turnout, valid votes, state,
   municipality, polling place name and type. Classify place names by text
   pattern into indigenous, rural or river community, school, other. This
   classification is inferred from text, not an official category. Also show
   the same section's round-1 2026 share for that candidate.
3. Observed against expected zeros and near zeros, under models 1 to 4, for each
   candidate.
4. The same for 2022 and 2018 round 2, by side.
5. Scale: share of sections and valid votes held by zero sections, national
   margin, and the extreme-case arithmetic (remove all opponent votes in those
   sections, or move them to the other side).
6. Integrity checks: bulletin vs section file per section and candidate; sum of
   sections vs published municipal totals; turnout vs electorate per section.

## What counts as "unexpected"

These were fixed before results:

- **U1 model.** Observed zeros above the 99.9% upper bound of a model (normal
  approximation to the sum of Bernoulli terms). Because real voting clusters
  more than any of these models assume, U1 is expected to fire for the binomial
  models in every year and on both sides. U1 alone is therefore not read as
  evidence of anything. It is read only together with U2.
- **U2 history.** For the same model, the 2026 observed/expected ratio for a
  candidate lies outside the range of the four baseline ratios (2018 and 2022,
  both sides) by more than a factor of 1.5.
- **U3 persistence.** Among round-2 zero sections for a candidate, the share
  where that candidate had 10% or more of the valid votes in round 1 of the same
  year in the same section. Unexpected if this share in 2026 is more than twice
  the 2022 value for the same side and involves 5 or more sections.
- **U4 integrity.** Any section where bulletin and section-file counts differ,
  any section with turnout above its electorate, or any municipality where the
  sum of sections differs from the published municipal total. Each case is
  listed.
- **U5 scale.** The votes in the zero sections are compared with the national
  margin. Reported as plain arithmetic, not as a test.

## Resources

[Redacted before publication: the name of the internal machine and its disk path. The paragraph also said: at most 8 worker processes, CPU only. HMAC-SHA256 of the original line, UTF-8, no trailing newline, with a private key (see item 17): af31be6b19a6f666535ff088be78ba6b4e6e3ad5569cfd4e8314955bc1de7d19]

## Deviations

Recorded after results, with reasons.

1. **Place-name patterns tightened before any result.** While writing the build
   script, two school patterns ("EM" and "CE") were dropped because they also match
   the Portuguese word "em" and the state code CE in addresses. A loose "T.I."
   pattern for indigenous land was dropped for the same reason. No result had
   been computed.
2. **History match with 2022 added during the build, not pre-registered.**
   `04_models.py` matches each 2026 zero section to the same section key in 2022
   and keeps only matches with the same polling-place name. Section numbers can be
   renumbered between elections, so this is approximate. It is descriptive only.
3. **National totalization report check added.** The brief asked for sums against
   the published national totals. The round-2 national report (PDF) in the
   `Relatorio_Resultado_Totalizacao_2026_BR.zip` file was parsed with pdftotext.
4. **Exploratory diagnostics added after U2 fired** (`06_diagnostics.py`). U2 fired
   for the municipality-mean beta-binomial on the non-PT side in 2026 and not for
   the polling-place models. The script splits observed and expected zeros by the
   inferred place-name flag and compares how one-sided the text-flagged indigenous
   places were in each year. It was written after seeing the U2 result, so it is
   exploratory and is labelled so in the report.
   The same script also writes two summary tables of facts already computed by
   `04_models.py`: the round-1 share in the 2026 zero sections (the U3 input) and
   the 2022 history match. They add no new test.
5. **Two extra scale fields** (votes as a share of the margin, votes needed to
   flip) were added to `scale.csv` after the first run, to state the arithmetic.

## v2 deviations (all dated 2026-10-29, after a full recheck of v1)

Everything above this heading is the v1 text, word for word, except two
redactions made before publication: the "Resources" paragraph (item 17) and the
four municipality names in the press claim (item 18). The HMAC-SHA256 of the
unredacted v1 file, with the private key of item 17, is
`68fe90b7e636dee8a433a3f80cef5f3065346e3c8e2fd54e1a00fb1a019685ba`.
The pre-registration was self-declared. It was not frozen with a hash or a commit
before the first model run, so it cannot be verified. The "Resources" paragraph
names the internal machine and disk the work ran on. Before publication that line was redacted, and a keyed HMAC-SHA256 of it is printed in its place, so anyone given the original line and the key can check it (item 17).
Errata to the text above: `01_verify_hashes.py` never existed. In v1 the hash
check was in `05_integrity.py`, and in v2 it is in `05_consistency.py`. Question 6
is renamed "Consistency checks".

Every item below was decided after the v1 results and the recheck were seen. Each
says what changed and why.

6. **BB-place dispersion fixed.** What: sections alone at their place use the
   municipality dispersion, and the place dispersion is fitted only on sections
   with place-mates. Why: v1 fitted it on all sections, which mixed municipality
   means into the place fit and inflated the spread. Pre-registered text: "same as
   3 with the polling-place leave-one-out mean", and the fallback is the
   municipality model, so the fix follows the text. The v1 code is kept as
   "BB-place (v1 code)" so the effect can be seen.
7. **Exact tails.** What: U1 uses the exact Poisson-binomial 99.9% quantile and
   P(X >= observed). The normal bound is still reported. Why: the normal
   approximation sits below the true quantile when expectations are small.
8. **Two models added.** What: EB-place and EB-r1, beta-binomial posterior
   predictives that start from the municipality mean and dispersion and update
   with the place-mates' votes (EB-place) or the same section's round-1 votes
   (EB-r1). Why: the place models give probability 1 when every place-mate is
   zero, which is circular. EB-r1 uses no round-2 outcome.
9. **Section-level checks.** What: expectation per section and where it falls,
   calibration by bins of probability, and proper scores (Bernoulli log score,
   Brier) over all sections. "Best model" means the highest log score and nothing
   else. Why: a matching total does not show that a model finds the zero sections.
10. **U2 per side.** What: each side compared with its own 2018 and 2022 ratios,
    reported next to the pooled rule written above. All 72 pre-registered checks
    are listed. Why: the pooled range is set by the side with larger ratios, so
    the rule as written cannot fire for the other side.
11. **Place-cluster bootstrap.** What: a 95% interval for each observed/expected
    ratio from resampling polling places (1,000 Poisson weights per place). Why:
    sections at one place are not independent.
12. **Classifier v2.** What: rules in `03_build.py`, the official place type,
    prison and quilombola flags, a manual check of all 110 zero sections of 2026,
    and a manual check of a random sample of 200 flagged sections. The rules were
    fixed before the sample was drawn. Why: v1 missed indigenous places, counted
    urban ones, and did not use the official type.
13. **Diagnostic redone.** What: composition reweighting, denominators, the local
    share (D3), flag components by year, and places matched across years. Why: the
    v1 diagnostic stated a cause it did not support. Note on the two matching
    keys (municipality plus place name, and place key plus place name): they give
    identical counts for indigenous-flagged places. This was checked, and it is
    not a code fault. Over all places the keys differ: 70,869 places match by
    name between 2026 and 2022 and 70,116 by key and name (87,705 and 87,224
    between 2022 and 2018). Among indigenous-flagged places, every place that
    matches by name also kept its zone and place number (350 and 422 places).
14. **Interactive files rebuilt under a privacy rule.** What: no section or place
    is named or located. Why: v1 listed small, prison and unanimous sections with
    coordinates.
15. **Reproducible package.** What: a portable downloader checked against
    `sources.lock.tsv`, pinned package versions, a configurable data folder,
    output checksums, IBGE codes from the TSE table `municipio_tse_ibge`, and
    section-level lists moved out of the published results. Why: the work is to be
    published and must run on a stranger's machine without exposing small
    communities. No number changed.
16. **External check of the place categories (step 11).** What: the place
    categories are compared with outside registers (IBGE 2022 Census indigenous and
    quilombola localities and census sectors, FUNAI indigenous lands and villages,
    INCRA quilombola territories), by each polling place's TSE coordinate. Its
    method is fixed in `EXTERNAL-CHECK-PLAN.md`, written before that check was run.
    It was later made step 11 of `run_all.sh`, its packages merged into
    `requirements.lock` and its files added to `sources.lock.tsv`. No number of
    that check changed in the merge. Why: the analyst labels read only the TSE
    text fields, so they could not show whether a place is in fact an indigenous
    or quilombola community (second recheck, items M3 and S7).
17. **"Resources" paragraph redacted.** What: the paragraph's single line was
    replaced by a redaction notice that keeps its harmless content (CPU only, at
    most 8 worker processes) and a keyed hash. Why: the line named the internal
    machine and the disk path of the raw data, which are never published. How the
    hash was computed: HMAC-SHA256 over the exact bytes of the original line as
    stored in the file, UTF-8, without the trailing newline (73 bytes), with a
    random 32-byte key. The result is
    `af31be6b19a6f666535ff088be78ba6b4e6e3ad5569cfd4e8314955bc1de7d19`. The unredacted v1 file's HMAC-SHA256 with the same key is
    `68fe90b7e636dee8a433a3f80cef5f3065346e3c8e2fd54e1a00fb1a019685ba`. Why keyed: the line is short and the notice says what it held, so a
    plain SHA-256 would let a reader confirm a guessed line. Without the key,
    a guess cannot be checked. The key is held privately by the lab and is not
    published. It can be disclosed to anyone asked to verify that the original line
    and file match these values (third recheck, item S6). Earlier drafts printed plain
    SHA-256 values. They are withdrawn.
18. **Municipality names in the press claim redacted.** What: the press report's
    four municipality names for the sections where the PT side had zero votes
    were replaced by a redaction notice with a keyed hash. Why: the text named the
    zero-section municipalities of one side only, which is asymmetric, and naming
    the municipality of a zero section breaks the lab's privacy rule. Zero sections
    are published only nationally, by region and by state. How the hash was
    computed: HMAC-SHA256, with the private key of item 17, over the exact bytes
    of the redacted span as stored in the original file, UTF-8: from the first
    letter of the first name to the closing parenthesis after the fourth, with
    the line break and the two-space indent between them (72 bytes). The
    result is `82db8da1e2cdb8568996ed7821d590e0a70e1d641cf585260b68766c190f2b98`. The key can be disclosed to anyone asked to verify it.
