Skip to content

Sobe código de limpeza de br_tse_eleicoes 1990-2024 - #1467

Closed
rdahis wants to merge 10 commits into
mainfrom
data/upload_TSE_eleicoes_code
Closed

Sobe código de limpeza de br_tse_eleicoes 1990-2024#1467
rdahis wants to merge 10 commits into
mainfrom
data/upload_TSE_eleicoes_code

Conversation

@rdahis

@rdahis rdahis commented Mar 18, 2026

Copy link
Copy Markdown
Member

Subindo para o repositório pipelines o que antes estava só em sdk (em https://github.com/basedosdados/sdk/tree/master/bases/br_tse_eleicoes/code).

Esse código é rodado na minha máquina a partir de todos os dados baixados do repositório de dados abertos do TSE. Eu cheguei a subir todos os dados raw no nosso storage. Se quisermos manter esses dados guardados como arquivo (acho que deveríamos), eu precisaria atualizá-los até 2024.

Se formos refatorar para Python, será a partir desse código.

Summary by CodeRabbit

  • New Features
    • Established a comprehensive election data processing pipeline for Brazilian electoral data spanning 1994-2024.
    • Implemented automated data cleaning and standardization for candidate information, party affiliations, and voting results across all states.
    • Added structured dataset generation for election results and voter profiles aggregated at multiple geographic and administrative levels.

@rdahis rdahis self-assigned this Mar 18, 2026
@folhesgabriel

Copy link
Copy Markdown
Collaborator

@rdahis vamos manter o código em stata no repositório SDK. Neste repo, somente em Pyhton/SQL;

@folhesgabriel

Copy link
Copy Markdown
Collaborator

Usaremos o código em stata como base para a criação dos novos em python. Tenho algumas dúvidas:

  1. O TSE atualiza dados de eleições passadas retroativamente?
  2. Você tem anotações sistematizadas sobre problemas comuns e pontos de atenção para o processo de tratamento do TSE?

@rdahis

rdahis commented Mar 18, 2026

Copy link
Copy Markdown
Member Author

@rdahis vamos manter o código em stata no repositório SDK. Neste repo, somente em Pyhton/SQL;

Stata hoje em dia nem entra nos repositórios da BD 😸

Usaremos o código em stata como base para a criação dos novos em python. Tenho algumas dúvidas:

1. O TSE atualiza dados de eleições passadas retroativamente?

Raramente. Eu já vi ser feito mas é raro.

2. Você tem anotações sistematizadas sobre problemas comuns e pontos de atenção para o processo de tratamento do TSE?

Fora o código o e guia de uso, não. Tem muita coisa comentada no código em si.

@PedroFrey

Copy link
Copy Markdown

Iniciei o mapeamento do dataset perfil_eleitorado_secao gerando todas as combinações possíveis entre Unidades Federativas (UFs) e anos eleitorais.

  • Total de UFs consideradas: 27
  • Total de anos considerados: 16
  • Total de combinações: 432
ufs = [
    "AC","AL","AP","AM",
    "BA","CE","DF","ES","GO",
    "MA","MT","MS","MG",
    "PA","PB",
    "PR","PE","PI",
    "RJ","RN","RS","RO","RR",
    "SC","SP","SE","TO"
]

anos = [
    2024, 2022,
    2020,
    2018, 2016, 2014, 2012, 2010,
    2008, 2006, 2004, 2002, 2000,
    1998, 1996, 1994
]

Embora nem todas as combinações sejam esperadas, esse mapeamento permite identificar:

  1. O que já existe em raw/br_tse_eleicoes/perfil_eleitorado_secao
  2. O que está faltando e pode precisar ser obtido

Achados atuais

  • Total de arquivos no storage: 216

  • Combinações encontradas (match): 213

  • Arquivos não correspondentes: 3

    • Referem-se a UFs placeholder:

      • perfil_eleitor_secao_2014_ZZ.zip
      • perfil_eleitor_secao_2018_ZZ.zip
      • perfil_eleitor_secao_2022_ZZ.zip

Interpretação

  • Os 213 arquivos com match já estão disponíveis no storage e não precisam de download.
  • Restam 219 combinações (432 totais menos 213 existentes) que precisam ser analisadas.

Próximos passos

  • Validar, dentro das 219 combinações ausentes, quais:

    • Não são esperadas (ex.: ausência de eleição ou de publicação de dados)
    • Não existem na fonte (TSE)
    • Existem na fonte, mas ainda não estão no storage
  • Definir estratégia para:

    • Completar ingestão dos dados faltantes, ou
    • Documentar corretamente essas lacunas.

@rdahis

rdahis commented Mar 19, 2026

Copy link
Copy Markdown
Member Author

Pessoal, estou fazendo a conversão do código para Python com Claude Code aqui. Numa boa, é o mais eficiente. Eu já tenho todo o contexto dessa base. Subirei as atualizações no PR em breve.

@rdahis rdahis mentioned this pull request Mar 24, 2026
1 task
@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A comprehensive Stata data pipeline is added for processing and normalizing Brazilian TSE (electoral commission) election datasets. The pipeline includes a main orchestration script, helper functions for data cleaning, and multiple subordinate processing scripts that handle candidates, parties, results, voter profiles, financial records, and vacancies across 1994–2024.

Changes

Cohort / File(s) Summary
Main Orchestration & Helper Functions
models/br_tse_eleicoes/code/build.do, models/br_tse_eleicoes/code/fnc/clean_string.do, models/br_tse_eleicoes/code/fnc/limpa_*.do
Master build script that orchestrates the pipeline; helper programs normalize strings (accents, case), standardize field values (education, civil status, election type, party names, results), and handle candidate-specific corrections.
Candidate & Party Data Processing
models/br_tse_eleicoes/code/sub/candidatos.do, models/br_tse_eleicoes/code/sub/partidos.do
Load, normalize, and structure candidate and party/coalition datasets by year/UF; handle format variations across election cycles, destring IDs, standardize dates and categorical fields.
Election Results (Multiple Aggregation Levels)
models/br_tse_eleicoes/code/sub/resultados_municipio_zona.do, models/br_tse_eleicoes/code/sub/resultados_secao.do, models/br_tse_eleicoes/code/sub/resultados_uf.do
Build election vote tallies for candidates and parties aggregated at municipality+zone, section, and state levels; collapse votes by relevant keys, merge municipality metadata, and apply cleanup routines.
Voter Profile & Voting Details
models/br_tse_eleicoes/code/sub/perfil_eleitorado_*.do, models/br_tse_eleicoes/code/sub/detalhes_votacao_*.do
Construct demographic elector profiles (by municipality+zone, section, voting location, and UF) and detailed voting records; normalize dates, derive percentage metrics, handle sentinel values.
Candidate Financial & Administrative Data
models/br_tse_eleicoes/code/sub/prestacao_contas.do, models/br_tse_eleicoes/code/sub/vagas.do
Process candidate assets, receipts, expenses, and election vacancies by year/UF; standardize date fields, currency formats, and municipal lookups.
Normalization, Partitioning & Aggregation
models/br_tse_eleicoes/code/sub/normalizacao_particao.do, models/br_tse_eleicoes/code/sub/agregacao.do
Unified normalization and deduplication of candidates/parties; partitioned export to year/state-level CSV files; aggregation of zone-level results to municipality/party-level summaries.
Archive / Old Processing
models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do, models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do
Legacy candidate ID creation pipelines and party affiliation dataset construction; retained for reference.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 Hop, hop, hooray! The election data now flows,
Through Stata scripts in ordered rows,
Candidates cleaned, results collated bright,
From 1994 through 2024—what a delightful sight! 🗳️
Brazilian ballots sorted with care,
A data warren beyond compare! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title does not follow the required naming pattern with keyword prefix in brackets. It lacks the required [Feature], [Data], [Bugfix], [Refactor], [Docs], [Test], [Chore], or [Deactivate] prefix. Update the title to follow the template pattern, for example: '[Feature] Sobe código de limpeza de br_tse_eleicoes 1990-2024' or '[Data] Sobe código de limpeza de br_tse_eleicoes 1990-2024'.
Description check ⚠️ Warning The PR description is incomplete and does not follow the provided template structure. It lacks key required sections including: Detalhes Técnicos, Teste e Validações, Riscos e Mitigações, and Dependências, and contains only a brief summary without structured organization. Expand the description to include all template sections: explain motivação/contexto clearly, detail principais alterações, provide testing/validation results, identify risks and rollback plans, and list any dependencies.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch data/upload_TSE_eleicoes_code

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (12)
models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do-46-46 (1)

46-46: ⚠️ Potential issue | 🟠 Major

The DF/2014 biometrics exception is currently a no-op.

This creates situacao_biometrica, but the next block keeps cd_mun_sit_biometrica and later renames it to situacao_biometria. The special-case variable is dropped before it can affect the output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do` at line 46, The
DF/2014 special-case creates situacao_biometrica but is a no-op because later
code continues to use cd_mun_sit_biometrica and then renames it to
situacao_biometria; update the DF/2014 branch so it sets the same upstream
variable used later (cd_mun_sit_biometrica) or ensure the later renaming/logic
uses situacao_biometrica instead; specifically modify the conditional that
currently does `if "`uf'" == "DF" & `ano' == 2014 gen situacao_biometrica = ""`
to assign or adjust cd_mun_sit_biometrica (or merge situacao_biometrica into
cd_mun_sit_biometrica before the subsequent block) so the DF/2014 exception
actually affects the eventual situacao_biometria output.
models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do-105-107 (1)

105-107: ⚠️ Potential issue | 🟠 Major

Filter to matched municipalities after the merge.

Dropping only _merge == 2 keeps every master row whose id_municipio_tse is not in the directory, with id_municipio missing. That lets placeholder or invalid municipality codes survive into the yearly output.

🐛 Suggested fix
-		merge m:1 id_municipio_tse using `diretorio'
-		drop if _merge == 2
-		drop _merge
+		merge m:1 id_municipio_tse using `diretorio', keep(3) nogen
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do` around lines 105
- 107, The current merge (merge m:1 id_municipio_tse using `diretorio') only
drops _merge == 2, leaving master-only rows (with missing id_municipio) in
output; change the filter to keep only matched rows by replacing "drop if _merge
== 2" with "keep if _merge == 3" (or "drop if _merge != 3"), then remove the
_merge variable as before (drop _merge) so only rows with matched
id_municipio_tse → id_municipio survive.
models/br_tse_eleicoes/code/sub/resultados_uf.do-63-75 (1)

63-75: ⚠️ Potential issue | 🟠 Major

Complex import fallback logic may load unexpected data.

Multiple cap import statements execute sequentially (lines 63-74), each potentially overwriting the previous import. If an earlier import succeeds but the later one also succeeds for a different file, the wrong data may be processed. The subsequent if blocks (lines 76-156) then apply year-specific transformations that may not match the actual loaded data.

Consider restructuring to ensure only one import succeeds per iteration, with explicit error handling.

💡 Suggested restructure
local imported = 0
cap import delimited "input/votacao_candidato_uf/votacao_candidato_uf_`ano'_br/VOTACAO_CANDIDATO_UF_`ano'_`estado'.txt", ...
if _rc == 0 local imported = 1

if `imported' == 0 {
    cap import delimited "input/votacao_candidato_uf/VOTACAO_CANDIDATO_`ano'/VOTACAO_CANDIDATO_UF_`ano'_`estado'.txt", ...
    if _rc == 0 local imported = 1
}

if `imported' == 0 {
    cap import delimited "input/votacao_candidato_uf/VOTACAO_CANDIDATO_UF_`ano'/VOTACAO_CANDIDATO_UF_`ano'_`estado'.txt", ...
    if _rc == 0 local imported = 1
}

if `imported' == 0 {
    di as error "No file found for `ano' `estado'"
    continue
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_uf.do` around lines 63 - 75, The
current sequence of cap import delimited calls (the three imports referencing
votacao_candidato_uf_`ano'_br and two VOTACAO_CANDIDATO_UF_`ano' paths) can
overwrite earlier successful imports; change the logic so only one import is
accepted: attempt each cap import in order, check _rc after each import and set
a local flag (e.g., local imported = 1) when one succeeds, and skip subsequent
imports if imported==1; if none succeed, emit an explicit error/di and continue;
retain the special-case drop in 1 for `ano' == 1933 | `ano' == 1934 but apply it
only after confirming imported==1.
models/br_tse_eleicoes/code/sub/candidatos.do-65-66 (1)

65-66: ⚠️ Potential issue | 🟠 Major

PII fields (CPF, email) in output dataset.

The dataset includes cpf and email fields, which are personally identifiable information. Ensure this aligns with privacy/compliance requirements (LGPD in Brazil). Consider whether these fields need to be anonymized or excluded from public outputs.

Also applies to: 101-102, 137-138

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/candidatos.do` around lines 65 - 66, The
output currently renames and exposes raw PII via the statements "ren v21 cpf"
and "ren v22 email" (and similar renames at the other occurrences), so either
remove these fields from the published dataset or pseudonymize them before
output; update the renames to not propagate raw CPF/email (remove those ren
v21/v22 lines or replace with masked/anonymized equivalents) and apply the same
change to the other occurrences referenced (the ren statements around lines
101-102 and 137-138) so no raw CPF or email leaves the final output.
models/br_tse_eleicoes/code/sub/detalhes_votacao_secao.do-123-125 (1)

123-125: ⚠️ Potential issue | 🟠 Major

Potential unintended data loss when handling duplicates.

The current logic tags duplicates and then drops ALL rows where dup > 0. This means if there are 2 identical records, both are dropped, not just the duplicate. If the intent is to keep one copy of each unique record, use duplicates drop instead.

💡 Suggested fix if intent is to keep one copy
-duplicates tag ano turno tipo_eleicao sigla_uf id_municipio_tse zona secao cargo, gen(dup) // idealmente usar o codigo_eleicao (v7) para identificar
-drop if dup > 0
-drop dup
+duplicates drop ano turno tipo_eleicao sigla_uf id_municipio_tse zona secao cargo, force

If the intent is genuinely to remove all ambiguous records, add a comment explaining this decision.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_secao.do` around lines 123 -
125, The current sequence using "duplicates tag ano turno tipo_eleicao sigla_uf
id_municipio_tse zona secao cargo, gen(dup)" followed by "drop if dup > 0" and
"drop dup" will remove all copies of duplicated records; if you intend to keep
one copy, replace that sequence with Stata's "duplicates drop ano turno
tipo_eleicao sigla_uf id_municipio_tse zona secao cargo" to retain a single
observation per group (or alternatively add a clear comment above the existing
"drop if dup > 0" explaining that the deliberate intent is to remove all
ambiguous records if that is desired).
models/br_tse_eleicoes/code/build.do-11-19 (1)

11-19: ⚠️ Potential issue | 🟠 Major

Hardcoded absolute paths will break for other developers.

The script uses user-specific paths (~/Downloads/dados_TSE, ~/Dropbox/BD/sdk/...) that won't work on other machines. Consider using relative paths or environment variables for portability.

Additionally, there's an inconsistency: function definitions (lines 13-19) use absolute paths while build scripts (lines 25-40) use relative paths.

💡 Suggested approach
-cd "~/Downloads/dados_TSE"
+// Set root directory via environment variable or relative path
+local root_dir = cond("$TSE_DATA_DIR" != "", "$TSE_DATA_DIR", ".")
+cd "`root_dir'"

-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/clean_string.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_tipo_eleicao.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_instrucao.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_estado_civil.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_resultado.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_partido.do"
-do "~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/limpa_candidato.do"
+do "code/fnc/clean_string.do"
+do "code/fnc/limpa_tipo_eleicao.do"
+do "code/fnc/limpa_instrucao.do"
+do "code/fnc/limpa_estado_civil.do"
+do "code/fnc/limpa_resultado.do"
+do "code/fnc/limpa_partido.do"
+do "code/fnc/limpa_candidato.do"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/build.do` around lines 11 - 19, The build script
uses hardcoded absolute paths (e.g., cd "~/Downloads/dados_TSE" and do
"~/Dropbox/BD/sdk/bases/br_tse_eleicoes/code/fnc/clean_string.do") which breaks
portability; change the script to derive those locations from a configurable
base variable (e.g., BASE_DATA_DIR or SDK_DIR) or use project-relative paths,
then reference that variable when calling the functions (clean_string.do,
limpa_tipo_eleicao.do, limpa_instrucao.do, limpa_estado_civil.do,
limpa_resultado.do, limpa_partido.do, limpa_candidato.do) so both the build and
function includes use the same indirection and no user-specific absolute paths
remain.
models/br_tse_eleicoes/code/sub/prestacao_contas.do-1107-1117 (1)

1107-1117: ⚠️ Potential issue | 🟠 Major

Persist monetary values as numeric columns.

valor_receita and valor_despesa are normalized with subinstr(..., ",", ".", .) throughout the file, but never destringed before these final saves. normalizacao_particao.do only reorders/exports these tables, so the published finance outputs keep amount fields as strings.

Suggested fix
+capture confirm variable valor_receita
+if !_rc destring valor_receita, replace force
+
+capture confirm variable valor_despesa
+if !_rc destring valor_despesa, replace force
+
 	compress
 	
 	save "output/receitas_candidato_`ano'.dta", replace
@@
+capture confirm variable valor_receita
+if !_rc destring valor_receita, replace force
+
+capture confirm variable valor_despesa
+if !_rc destring valor_despesa, replace force
+
 	compress
 	
 	save "output/despesas_candidato_`ano'.dta", replace

Also applies to: 2043-2053

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/prestacao_contas.do` around lines 1107 -
1117, The money fields valor_receita and valor_despesa are still strings (they
were normalized with subinstr but not converted); before the final compress/save
in prestacao_contas.do, use destring on these variables (e.g., destring
valor_receita, replace force and destring valor_despesa, replace force) to
convert them to numeric and handle non-numeric characters, then optionally
format or label them; apply the same change in the other similar block that
handles the other partitioned save (the block that mirrors this logic later in
the file where valor_receita/valor_despesa are produced).
models/br_tse_eleicoes/code/sub/normalizacao_particao.do-94-95 (1)

94-95: ⚠️ Potential issue | 🟠 Major

Persist the cleaned candidate lookup before downstream sections reuse it.

Step 3 writes the post-cleanup candidate table only to a tempfile. Later sections in this same script reopen output/norm_candidatos.dta, so results, bens, and finance merges use the pre-cleanup lookup while output/candidatos/ano=*/candidatos.csv uses the cleaned one.

Suggested fix
 	compress
 	
+	save "output/norm_candidatos.dta", replace
+	export delimited "output/norm_candidatos.csv", replace
+
 	tempfile candidatos
 	save `candidatos'

Also applies to: 102-160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/normalizacao_particao.do` around lines 94 -
95, The script currently writes the cleaned candidate lookup only to a tempfile
so downstream merges reopen the old pre-cleanup file; change the save/export
commands (the save "output/norm_candidatos.dta", replace and export delimited
"output/norm_candidatos.csv", replace) to persist the cleaned lookup into
output/norm_candidatos.dta and output/norm_candidatos.csv (remove tempfile
usage) so subsequent sections that reopen output/norm_candidatos.dta will use
the cleaned table; apply the same change to the duplicate blocks around lines
referenced (the subsequent 102-160 region) so all downstream merges (results,
bens, finance) consume the cleaned lookup.
models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do-217-220 (1)

217-220: ⚠️ Potential issue | 🟠 Major

Guard blank data_eleicao values before reformatting.

When data_eleicao is empty, this generates --, and the second replace does not clear it. Invalid dates will then leak into output/detalhes_votacao_municipio_zona_*.dta.

Suggested fix
 		foreach k in eleicao {
-			replace data_`k' = substr(data_`k', 7, 4) + "-" + substr(data_`k', 4, 2) + "-" + substr(data_`k', 1, 2)
+			replace data_`k' = substr(data_`k', 7, 4) + "-" + substr(data_`k', 4, 2) + "-" + substr(data_`k', 1, 2) if data_`k' != ""
 			replace data_`k' = "" if real(substr(data_`k', 1, 4)) < 1900
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do` around
lines 217 - 220, The loop over "foreach k in eleicao" should guard empty or
too-short data_eleicao values before using substr; change the transform on
data_`k' so you first set it to missing/"" if it is empty or length < 10 (or if
substr would produce non-numeric year), then only run the YYYY-MM-DD
reconstruction and the year check; in practice wrap the current replace lines in
a conditional like "if data_`k' != "" & strlen(data_`k')>=10" (or pre-emptively
replace data_`k' = "" if strlen(data_`k')<10) so data_`k' never becomes "--" and
the year check using real(substr(...)) only runs on valid strings.
models/br_tse_eleicoes/code/sub/agregacao.do-195-216 (1)

195-216: ⚠️ Potential issue | 🟠 Major

Restore the intended year range here.

This block currently exports detalhes_votacao_municipio only for 2024, so every 1994-2022 year built upstream disappears from the aggregated outputs. The inline comment suggests this was meant to be temporary.

Suggested fix
-foreach ano of numlist 2024 { // 1994(2)2024 {
+foreach ano of numlist 1994(2)2024 {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/agregacao.do` around lines 195 - 216, The
year loop is hardcoded to only 2024 (foreach ano of numlist 2024) which drops
earlier years; restore the intended range by changing the numlist to the
original sequence (e.g., 1994(2)2024 or whatever full range the pipeline
expects) so the foreach ano loop (and subsequent mkdir, local subdirs, import
delimited, collapse, gen, export delimited steps) runs for all years rather than
just 2024.
models/br_tse_eleicoes/code/sub/resultados_secao.do-164-189 (1)

164-189: ⚠️ Potential issue | 🟠 Major

Merge nominal and legend votes on the full election key.

Both temp tables are collapsed by ano id_eleicao tipo_eleicao data_eleicao turno ..., but the merge 1:1 drops tipo_eleicao and data_eleicao from the key. On legacy inputs where id_eleicao == "", that can either mismatch rows from different elections or make the merge fail on non-unique keys.

Suggested fix
-		merge 1:1 ano id_eleicao turno sigla_uf id_municipio_tse zona secao cargo numero_partido using "tmp/votos_legenda.dta", nogenerate
+		merge 1:1 ano id_eleicao tipo_eleicao data_eleicao turno sigla_uf id_municipio_tse zona secao cargo numero_partido using "tmp/votos_legenda.dta", nogenerate
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_secao.do` around lines 164 - 189,
The merge drops tipo_eleicao and data_eleicao from the merge key even though the
datasets were collapsed by them; update the merge to include those fields so the
1:1 key matches the collapse (i.e. change the merge call in resultados_secao.do
to merge 1:1 ano id_eleicao tipo_eleicao data_eleicao turno sigla_uf
id_municipio_tse zona secao cargo numero_partido using "tmp/votos_legenda.dta",
nogenerate) so legacy rows with id_eleicao == "" don’t collide or produce
non-unique matches.
models/br_tse_eleicoes/code/sub/prestacao_contas.do-1513-1521 (1)

1513-1521: ⚠️ Potential issue | 🟠 Major

Normalize 2012 expense dates with the same path used elsewhere.

This branch prefixes 20 to the whole raw string and never runs the usual despesa date reformat loop, so data_despesa is saved in a one-off malformed format only for 2012.

Suggested fix
-		replace data_despesa = "20" + data_despesa if length(data_despesa) == 8
+		replace data_despesa = substr(data_despesa, 1, 10)
+		replace data_despesa = "0" + data_despesa if length(data_despesa) == 9
+		replace data_despesa = substr(data_despesa, 7, 4) + "-" + substr(data_despesa, 4, 2) + "-" + substr(data_despesa, 1, 2) if length(data_despesa) > 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/prestacao_contas.do` around lines 1513 -
1521, The branch that handles 2012 currently does only replace data_despesa =
"20" + data_despesa (when length==8) which produces a malformed date because it
skips the existing despesa date reformat loop; change this so after prefixing
the year you run the same despesa reformat logic used elsewhere (i.e., the
loop/code that normalizes data_despesa into day/month/year format) instead of
leaving the one-off format—update the block containing data_despesa,
valor_despesa and the subsequent gen id_eleicao lines so data_despesa is passed
through the unified reformat routine rather than only prepending "20".
🟡 Minor comments (9)
models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do-71-77 (1)

71-77: ⚠️ Potential issue | 🟡 Minor

Fragile assumption: script fails if avante party file is missing.

Line 71 unconditionally loads filiacao_avante as the base dataset. If the filiados_avante.csv input file doesn't exist, the script will fail here—even though other party files may have been processed successfully.

Consider making the base dataset selection dynamic by using the first available party tempfile, or checking existence before loading.

Suggested approach for Python refactor

When refactoring to Python, use a pattern that collects all successfully processed DataFrames into a list and concatenates them at the end, avoiding dependence on any specific party being present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do` around lines 71 -
77, The code unconditionally does use `filiacao_avante' which breaks if that
tempfile/input is missing; change the logic to select a base dataset dynamically
or guard the load with an existence check: iterate `partidos', check each
`filiacao_`partido'' exists (e.g., capture confirm file or fileexists) and when
you find the first existing tempfile set it as the base (replace the `use
filiacao_avante' line), then append the rest; alternatively collect all existing
`filiacao_...'' files into a list first and loop to append them so the script
does not depend on `filiacao_avante' being present.
models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do-233-278 (1)

233-278: ⚠️ Potential issue | 🟡 Minor

Potential error when dataset is empty after filtering.

The keep command at line 276 is outside the if _N > 0 block. If tmp/para_2a_rodada.dta loads with observations but they're all filtered out by keep if N_nomes == 1 at line 267, subsequent keep and save would operate on an empty dataset, which is fine. However, if the dataset is empty from the start (_N == 0), the variables referenced in keep may not exist, causing a runtime error.

Consider wrapping the final keep and save in the same conditional or using capture to handle this edge case gracefully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do` around lines 233 -
278, The final keep (keep id_candidato_bd_str titulo_eleitoral nome) and save
("tmp/2a_rodada.dta", replace) can error when the dataset is empty because they
live outside the if _N > 0 block; wrap those two statements in the same if _N >
0 conditional (or alternatively guard with a check for variable existence or use
capture) so they only run when observations/variables created by the
id_candidato_bd_str / N_nomes logic exist (reference: the existing if _N > 0
block, the N_nomes filter, and the final keep/save calls).
models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do-6-9 (1)

6-9: ⚠️ Potential issue | 🟡 Minor

Year range mismatch between v1 and v2 variants.

The v1 variant loads data through 2022 (line 7: 2000(2)2022), while the v2 variant at line 172 loads through 2024 (2000(2)2024). This inconsistency means v1 will be missing the 2024 election data.

If both variants are intended for comparable outputs, update v1 to include 2024:

Proposed fix
 use "output/candidatos_1998.dta", clear
-foreach ano of numlist 2000(2)2022 {
+foreach ano of numlist 2000(2)2024 {
 	append using "output/candidatos_`ano'.dta"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do` around lines 6 - 9,
The foreach loop that appends year files uses the range "2000(2)2022" and
therefore omits 2024; update the numlist in the foreach statement (the foreach
ano of numlist ... loop) to match the v2 range "2000(2)2024" so the code appends
the 2024 candidato file as well.
models/br_tse_eleicoes/code/sub/perfil_eleitorado_municipio_zona.do-34-34 (1)

34-34: ⚠️ Potential issue | 🟡 Minor

instrucao field is not normalized.

The instrucao (education level) field is extracted but limpa_instrucao is never called to normalize it, unlike in candidatos.do. The function is loaded by build.do but not invoked here. Similarly, estado_civil and genero could benefit from normalization for consistency.

💡 Suggested addition after line 76
// Normalize categorical fields
limpa_instrucao
// Add other normalizations as needed:
// limpa_estado_civil

Also applies to: 52-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_municipio_zona.do` at line
34, The extracted categorical fields are not being normalized: invoke
limpa_instrucao after the instrucao extraction in
perfil_eleitorado_municipio_zona.do to normalize education levels, and likewise
call limpa_estado_civil and limpa_genero (or their implemented equivalents)
after extracting estado_civil and genero so values match the normalized forms
used elsewhere (e.g., candidatos.do); update the code to call limpa_instrucao
(and add limpa_estado_civil / limpa_genero calls) immediately after the relevant
extraction blocks to ensure consistent categorical normalization.
models/br_tse_eleicoes/code/sub/perfil_eleitorado_municipio_zona.do-17-18 (1)

17-18: ⚠️ Potential issue | 🟡 Minor

Silent failure if input file is missing.

Same issue as in vagas.do - using cap on both imports without checking _rc means no warning is raised if neither file exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_municipio_zona.do` around
lines 17 - 18, The two cap import delimited commands silently fail if files are
missing; change this by checking the result code after each import (inspect _rc)
or use fileexists() before importing: try the first import
("perfil_eleitorado_`ano'.txt"), if _rc!=0 then try the CSV import
("perfil_eleitorado_`ano'.csv"), and if that also fails emit an error message
and exit (e.g., using display and exit or error codes) so the script doesn't
continue silently; update the import lines in
perfil_eleitorado_municipio_zona.do to implement this check around the import
delimited calls.
models/br_tse_eleicoes/code/sub/vagas.do-2-4 (1)

2-4: ⚠️ Potential issue | 🟡 Minor

Incorrect comment - copy-paste error.

The header comment says "build: candidatos" but this file builds the "vagas" (vacancies) dataset.

📝 Suggested fix
 //----------------------------------------------------------------------------//
-// build: candidatos
+// build: vagas
 //----------------------------------------------------------------------------//
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/vagas.do` around lines 2 - 4, Update the
incorrect header comment at the top of the file (the comment that currently
reads "build: candidatos") to accurately reflect the dataset this file builds by
changing it to "build: vagas"; locate and edit the header comment block in
models/br_tse_eleicoes/code/sub/vagas.do (the top comment lines that start with
"// build:") so the wording matches the file purpose.
models/br_tse_eleicoes/code/sub/resultados_uf.do-2-4 (1)

2-4: ⚠️ Potential issue | 🟡 Minor

Incorrect comment - file processes UF-level results.

The header comment says "resultados municipio-zona" but this script processes UF-level election results.

📝 Suggested fix
 //----------------------------------------------------------------------------//
-// build: resultados municipio-zona
+// build: resultados uf
 //----------------------------------------------------------------------------//
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_uf.do` around lines 2 - 4, Update
the top-of-file header comment that currently reads "resultados municipio-zona"
to accurately describe the script as processing UF-level election results (for
example, "resultados UF" or "resultados por UF"); search for the exact string
"resultados municipio-zona" in the file and replace it with the corrected
UF-level description and adjust surrounding comment lines for consistency.
models/br_tse_eleicoes/code/sub/candidatos.do-170-172 (1)

170-172: ⚠️ Potential issue | 🟡 Minor

Merge result not validated.

The merge 1:1 with nogenerate suppresses the _merge variable, making it impossible to verify if all records matched. If records exist only in basico or only in complementar, they may be silently mishandled.

💡 Suggested validation
-merge 1:1 id_eleicao sequencial using `complementar', nogenerate
+merge 1:1 id_eleicao sequencial using `complementar'
+assert _merge == 3 | _merge == 1  // Allow unmatched from master
+drop _merge

Or at minimum, log a warning for unmatched records.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/candidatos.do` around lines 170 - 172, The
merge between `basico` and `complementar` uses "merge 1:1 id_eleicao sequencial
using `complementar', nogenerate" which suppresses the _merge flag so you cannot
validate matched/unmatched rows; remove the nogenerate option (or re-run the
merge without it) so the _merge variable is created, then add post-merge checks
on _merge in the same routine (e.g., count or loop over _merge==1, _merge==2,
_merge==3) and log warnings for any unmatched records (or handle them
explicitly) to ensure no rows were silently omitted.
models/br_tse_eleicoes/code/sub/vagas.do-42-43 (1)

42-43: ⚠️ Potential issue | 🟡 Minor

Silent failure if input file is missing.

Using cap on both import attempts means the script continues silently if neither .txt nor .csv exists. This could lead to confusing errors downstream when variables are missing.

💡 Suggested approach
 cap import delimited using "input/consulta_vagas/consulta_vagas_`ano'/consulta_vagas_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
-cap import delimited using "input/consulta_vagas/consulta_vagas_`ano'/consulta_vagas_`ano'_`uf'.csv", delim(";") varn(nonames) stringcols(_all) clear
+if _rc != 0 {
+    cap import delimited using "input/consulta_vagas/consulta_vagas_`ano'/consulta_vagas_`ano'_`uf'.csv", delim(";") varn(nonames) stringcols(_all) clear
+    if _rc != 0 {
+        di as error "Warning: No input file found for `ano'_`uf'"
+        continue
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/vagas.do` around lines 42 - 43, The import
block silently fails because both import delimited calls are prefixed with
capture; replace the silent-capture pattern with an explicit existence check and
a single import attempt: use
fileexists("input/consulta_vagas/consulta_vagas_`ano'/consulta_vagas_`ano'_`uf'.txt")
and fileexists(...".csv") to detect which file to load, call import delimited
(without capture) on the existing file, and if neither exists call display as
error (or error) and exit so the script fails fast; locate and update the two
import delimited calls shown and add the fileexists checks and error/exit
handling around them.
🧹 Nitpick comments (15)
models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do (3)

73-73: Remove commented debug code.

The commented fragment //"estado'" == "ac" &` appears to be leftover debug code and should be cleaned up.

-	if !("`partido'" == "avante") { //"`estado'" == "ac" & 
+	if !("`partido'" == "avante") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do` at line 73,
Remove the leftover debug comment from the conditional that checks the partido
variable: in the if statement using "`partido'" (the line starting with if
!("`partido'" == "avante") {), delete the trailing commented fragment
//"`estado'" == "ac" & so the condition remains clean and syntactically correct;
verify spacing and braces around the if ("`partido'" check) are preserved after
removal.

51-55: Date conversion assumes DD/MM/YYYY format without validation.

The substring-based date transformation assumes input is always in DD/MM/YYYY format. If the source data has inconsistent formats or invalid dates, this will produce malformed YYYY-MM-DD strings silently.

For the Python refactor, consider using proper date parsing with validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do` around lines 51 -
55, The current loop over varlist data_* uses brittle substring operations to
build YYYY-MM-DD (replace `k' = substr...), which fails on non-DD/MM/YYYY
inputs; update the refactor so each data_* field is parsed and validated (e.g.,
in Python use datetime.strptime inside a try/except or use dateutil.parser.parse
with strict format checking) and only convert valid dates to ISO format
(YYYY-MM-DD), otherwise set the field to a clear missing value (empty string or
None); ensure this replacement logic is applied for each variable in the varlist
data_* (the same loop that currently does the substr/replace) and include
logging or a counter for malformed rows so invalid dates are detectable.

44-48: Silent data handling may mask issues.

  • Line 44: force option silently converts non-numeric values in id_municipio_tse, zona, secao to missing (.)
  • Line 47: drop if _merge == 2 silently discards municipalities present in the directory but absent in the affiliation data

For the Python refactor, consider adding logging/warnings when:

  1. Values fail numeric conversion
  2. Records are dropped due to unmatched municipality IDs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do` around lines 44 -
48, The destring/merge steps silently lose information; before converting
id_municipio_tse, zona, secao to numeric, detect and log any non-numeric values
(count and a small sample) and record their row IDs so they can be inspected
(replace conversion should still proceed but with a warning), and when
performing the merge with `diretorio` detect right-only keys (records present in
`diretorio` but not in the affiliation table — the analog of _merge == 2), log
the count and a small sample of those id_municipio_tse values and DO NOT
silently drop them without a logged reason; implement these checks in the Python
refactor around the functions that perform the numeric coercion (the destring
equivalent for id_municipio_tse, zona, secao) and the merge/join step using
`diretorio` so you can trace and review conversion failures and unmatched
municipality IDs.
models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do (2)

74-89: Duplicated hard-coded name corrections.

These manual name corrections (lines 75-89) are duplicated verbatim in the v2 section (lines 242-256). Consider extracting these into a separate data file or lookup table to:

  1. Avoid duplication and potential sync issues
  2. Make corrections easier to maintain and audit
  3. Facilitate the planned Python conversion
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do` around lines 74 -
89, The duplicated hard-coded corrections for aux_primeira/aux_ultima (the
multiple replace statements like replace aux_primeira = "elves" if aux_primeira
== "elvis" & aux_ultima == "leite" and the identical block in the v2 section)
should be moved into a single lookup source (e.g., a CSV/JSON or a shared
dictionary) and both sections should load and apply that table instead of
repeating literal replace lines; update the cria_id_candidato.do logic to read
the shared corrections table and iterate applying corrections to aux_primeira
and aux_ultima (use the existing symbols aux_primeira, aux_ultima and the
replace operation) so future edits occur in one place and the same table can be
re-used by the planned Python conversion.

1-162: Dead code: v1 and v2 outputs are not consumed by the active pipeline.

Per the context from normalizacao_particao.do (lines 50-60), the merge operations using output/id_candidato_bd_v1.dta and output/id_candidato_bd_v2.dta are commented out. Only tmp/cpf_titulo_eleitoral.dta (lines 336-353) is actively used.

Given this file is in the old/ subdirectory and the PR objective mentions conversion to Python, consider:

  1. Adding a comment at the top of the file documenting that these variants are deprecated
  2. Removing the dead code if it won't be needed for the Python conversion, or
  3. Consolidating v1/v2 logic if both variants are needed for reference

Also applies to: 167-329

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do` around lines 1 -
162, This file produces id_candidato_bd_v1 (variable and file output
"output/id_candidato_bd_v1.dta") but those v1/v2 artifacts are not consumed by
the active pipeline; mark the script as deprecated and either remove or
consolidate dead branches: add a top-of-file comment stating the file is
deprecated/archival (mentioning id_candidato_bd_v1 and any id_candidato_bd_v2
references), then either delete the unused save/append blocks that write
"output/id_candidato_bd_v1.dta" and related tmp/*.dta outputs (e.g.,
tmp/1a_rodada.dta, tmp/2a_rodada.dta) if they won't be needed for the Python
conversion, or merge their logic into a single canonical path and update the
final save to a single expected artifact name; ensure any renamed variable
id_candidato_bd_v1 and the final save use the consolidated output name so
downstream scripts reference one current file.
models/br_tse_eleicoes/code/fnc/limpa_partido.do (1)

5-47: Normalize the party field inside this helper first.

The current callers in models/br_tse_eleicoes/code/sub/partidos.do:167-172, models/br_tse_eleicoes/code/sub/resultados_municipio_zona.do:201-220, and models/br_tse_eleicoes/code/sub/resultados_uf.do:168-180 do not run clean_string on sigla or sigla_partido. Because every rule here is exact-match and case/whitespace sensitive, any new variant will bypass the mapping and fragment the party dimension.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/fnc/limpa_partido.do` around lines 5 - 47, The
helper limpa_partido.do performs exact, case-sensitive replacements on `var'
causing fragmented party values; normalize the input first by applying the
project's clean_string (and/or trimming and uppercasing) to the party variable
before any replace rules (i.e., call clean_string on `var' at the top of
limpa_partido.do after local var `2' is set), then proceed with the existing
conditional replace blocks (preserving `ano' checks and the same replacement
targets) so all comparisons match normalized sigla/sigla_partido inputs.
models/br_tse_eleicoes/code/fnc/limpa_candidato.do (1)

5-8: Make these key overrides conditional.

These replaces will overwrite future raw refreshes too. Since cpf/titulo_eleitoral are padded in models/br_tse_eleicoes/code/sub/candidatos.do:220-252 and then used as dedupe/merge keys in models/br_tse_eleicoes/code/sub/normalizacao_particao.do:23-38, a later source correction would be silently forced back to the hard-coded value. Prefer matching the known-bad source value or moving these exceptions into a small audited corrections table.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/fnc/limpa_candidato.do` around lines 5 - 8, The
unconditional replace statements in limpa_candidato.do are forcing overrides
that will persist across future raw refreshes; change each replace to be
conditional on the field currently matching the known-bad source value (i.e.,
only replace cpf/titulo_eleitoral when the present value equals the incorrect
original string) or move these exceptions into a small, audited corrections
table applied during normalization (see candidatos.do padding logic and
normalizacao_particao.do dedupe/merge usage) so fixes don’t silently reapply to
corrected source data.
models/br_tse_eleicoes/code/sub/resultados_uf.do (1)

53-53: Complex year iteration pattern is hard to follow.

The numlist 1945 1947 1955(5)1965 1950(4)1990 1989 creates a non-obvious sequence. Consider adding a comment listing the actual years processed, or using explicit year lists for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_uf.do` at line 53, The foreach
loop using the numlist in the line "foreach ano of numlist 1945 1947 1955(5)1965
1950(4)1990 1989" is hard to read; update it to an explicit, easy-to-read year
list or add an inline comment enumerating the exact years produced so future
readers can immediately see the sequence. Locate the foreach that iterates the
local "ano" and either expand the numlist into a plain space-separated list of
years or append a clear comment (e.g., "// years: 1945 1947 1950 ...")
describing the full set so the iteration is unambiguous.
models/br_tse_eleicoes/code/sub/candidatos.do (1)

220-252: Complex identifier padding logic is difficult to maintain.

The CPF and titulo_eleitoral padding logic handles many edge cases with repetitive conditional statements. Consider extracting this into a reusable helper program for clarity and testability.

💡 Example helper approach
// In a separate helper file:
cap program drop pad_identifier
program pad_identifier
    args varname target_length
    
    forval i = 1/`=`target_length'-1' {
        local zeros = "0" * (`target_length' - `i')
        replace `varname' = "`zeros'" + `varname' if length(`varname') == `i'
    }
end

// Usage:
pad_identifier cpf 11
pad_identifier titulo_eleitoral 12
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/candidatos.do` around lines 220 - 252, The
current file contains repetitive, hard-to-maintain padding logic for cpf and
titulo_eleitoral; extract this into a reusable helper program (e.g.,
pad_identifier) that accepts the variable name and target length, moves the
repeated conditional/prefix logic into a loop, and then call pad_identifier for
cpf (target 11) and titulo_eleitoral (target 12); update any special-case
transformations for 12-length titulo_eleitoral (the substr-based space fixes) to
run either inside pad_identifier when detecting internal spaces or in a small
companion helper (e.g., normalize_titulo_with_internal_spaces) and replace the
inline replace statements with calls to these helpers.
models/br_tse_eleicoes/code/sub/perfil_eleitorado_local_votacao.do (2)

50-52: Inconsistent sentinel value handling.

telefone, latitude, and longitude replace -1 with empty string "", while other scripts (e.g., vagas.do, detalhes_votacao_secao.do) replace -1 with missing . for numeric fields. After destring on line 63-64, latitude and longitude become numeric, so empty strings become missing anyway, but telefone remains a string with empty values instead of missing.

Consider using a consistent approach across all scripts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_local_votacao.do` around
lines 50 - 52, The sentinel "-1" is being replaced inconsistently: telefone is
set to empty string "" while latitude and longitude are set to "" then later
destringed (lines around the destring for latitude/longitude), causing telefone
to remain a string rather than a numeric missing value as in other scripts;
change the replacements to use the numeric missing marker for consistency —
replace telefone, latitude, and longitude with . when the value equals "-1" so
that after destring (see the destring block around latitude and longitude) all
three fields are treated uniformly as missing numeric values.

54-62: Commented-out normalization code.

Multiple clean_string calls are commented out without explanation. If normalization is intentionally skipped (e.g., to preserve original formatting), add a comment explaining why. Otherwise, consider removing or enabling this code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_local_votacao.do` around
lines 54 - 62, Several normalization calls (clean_string) for fields
tipo_secao_agregada, tipo, situacao, situacao_zona, situacao_secao,
situacao_localidade, and situacao_secao_acessibilidade are commented out with no
explanation; either re-enable them or remove them and document the decision. If
normalization is required, uncomment the clean_string calls for those symbols
(or call the appropriate normalizer function used elsewhere) so fields are
normalized; if normalization is intentionally skipped to preserve original
formatting, replace the commented block with a short explanatory comment stating
why (e.g., "preserve original formatting for downstream matching") and remove
the dead code to avoid confusion. Ensure the change references the same
identifiers (clean_string and the listed field names) so reviewers can verify
the intended behavior.
models/br_tse_eleicoes/code/fnc/clean_string.do (2)

87-151: Consider removing commented-out code.

The clean_string_proper program is entirely commented out. If it's not needed, consider removing it to reduce noise. If it may be useful later, document why it's disabled or move it to a separate file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/fnc/clean_string.do` around lines 87 - 151, The
file contains a fully commented-out program clean_string_proper (using proper(),
usubinstr(), subinstr(), strtrim()) which should be removed or documented;
either delete the entire commented block to reduce noise, or move it to a
separate legacy/disabled file and add a short comment explaining why it’s
retained (e.g., "legacy proper-case handling for non-ASCII chars") and when to
re-enable, so maintainers can find clean_string_proper and related function
calls (proper/usubinstr) easily.

64-64: Duplicate double-space replacement.

The replacement of double spaces (" "" ") is performed twice (lines 64 and 76). A single pass after all other replacements would suffice.

Also applies to: 76-76

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/fnc/clean_string.do` at line 64, The duplicate
double-space replacement occurs twice for the same variable (`1') in
clean_string.do; remove the earlier occurrence and keep a single qui replace `1'
= subinstr(`1', "  ", " ", .) after all other subinstr/replace operations so the
double-space collapse is done once as a final cleanup (locate the two identical
calls and delete the first, leaving the final pass).
models/br_tse_eleicoes/code/sub/detalhes_votacao_secao.do (1)

106-119: Division by zero risk in proportion calculations.

If comparecimento is 0 or missing, the proportion calculations will produce missing or undefined values. This may be acceptable, but consider adding explicit handling or a note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_secao.do` around lines 106 -
119, The proportion calculations risk division by zero when comparecimento or
aptos are zero/missing; update each generation to explicitly guard using
conditional expressions: for proporcao_comparecimento use aptos>0 (e.g., gen
proporcao_comparecimento = cond(aptos>0, 100 * comparecimento / aptos, .)), and
for
proporcao_votos_nominais/proporcao_votos_legenda/proporcao_votos_brancos/proporcao_votos_nulos
use comparecimento>0 (e.g., gen proporcao_votos_nominais =
cond(comparecimento>0, 100 * votos_nominais / comparecimento, .)); ensure you
reference the existing variable names (proporcao_comparecimento,
proporcao_votos_nominais, proporcao_votos_legenda, proporcao_votos_brancos,
proporcao_votos_nulos, comparecimento, aptos, votos_nominais, votos_legenda,
votos_brancos, votos_nulos) and preserve the label lines (la var ...) unchanged.
models/br_tse_eleicoes/code/sub/partidos.do (1)

129-130: Fix the federation field typo before exporting it.

nome_federacacao looks like a misspelling of nome_federacao, and this file persists that name from 2022 onward. Fixing it later will be a breaking schema rename for downstream users.

Suggested rename
-			ren v20 nome_federacacao
+			ren v20 nome_federacao
@@
-		foreach k in nome_coligacao nome_federacacao sigla_federacao composicao_federacao nome_coligacao composicao_coligacao {
+		foreach k in nome_coligacao nome_federacao sigla_federacao composicao_federacao nome_coligacao composicao_coligacao {

Also applies to: 162-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/partidos.do` around lines 129 - 130, The file
renames contain a typo: change the field rename "ren v20 nome_federacacao" to
"ren v20 nome_federacao" (and similarly correct the duplicated typo at the other
occurrence around the block referenced as lines 162-163) so the exported schema
uses nome_federacao consistently; update both rename statements in partidos.do
and any nearby code that relies on v20 to ensure the correct field name is
propagated without introducing a downstream-breaking rename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3731dcd5-ce8c-4264-997e-75a959f5820f

📥 Commits

Reviewing files that changed from the base of the PR and between 1183f22 and 59de839.

📒 Files selected for processing (25)
  • models/br_tse_eleicoes/code/build.do
  • models/br_tse_eleicoes/code/fnc/clean_string.do
  • models/br_tse_eleicoes/code/fnc/limpa_candidato.do
  • models/br_tse_eleicoes/code/fnc/limpa_estado_civil.do
  • models/br_tse_eleicoes/code/fnc/limpa_instrucao.do
  • models/br_tse_eleicoes/code/fnc/limpa_partido.do
  • models/br_tse_eleicoes/code/fnc/limpa_resultado.do
  • models/br_tse_eleicoes/code/fnc/limpa_tipo_eleicao.do
  • models/br_tse_eleicoes/code/sub/agregacao.do
  • models/br_tse_eleicoes/code/sub/candidatos.do
  • models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do
  • models/br_tse_eleicoes/code/sub/detalhes_votacao_secao.do
  • models/br_tse_eleicoes/code/sub/detalhes_votacao_uf.do
  • models/br_tse_eleicoes/code/sub/normalizacao_particao.do
  • models/br_tse_eleicoes/code/sub/old/cria_id_candidato.do
  • models/br_tse_eleicoes/code/sub/old/filiacao_partidaria.do
  • models/br_tse_eleicoes/code/sub/partidos.do
  • models/br_tse_eleicoes/code/sub/perfil_eleitorado_local_votacao.do
  • models/br_tse_eleicoes/code/sub/perfil_eleitorado_municipio_zona.do
  • models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do
  • models/br_tse_eleicoes/code/sub/prestacao_contas.do
  • models/br_tse_eleicoes/code/sub/resultados_municipio_zona.do
  • models/br_tse_eleicoes/code/sub/resultados_secao.do
  • models/br_tse_eleicoes/code/sub/resultados_uf.do
  • models/br_tse_eleicoes/code/sub/vagas.do

Comment on lines +40 to +41
cap import delimited using "input/detalhe_votacao_munzona/detalhe_votacao_munzona_`ano'/detalhe_votacao_munzona_`ano'_`estado'.txt", delimiter(";") varn(nonames) stringcols(_all) clear
cap import delimited using "input/detalhe_votacao_munzona/detalhe_votacao_munzona_`ano'/detalhe_votacao_munzona_`ano'_`estado'.csv", delimiter(";") varn(nonames) stringcols(_all) clear

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the file
find . -name "detalhes_votacao_municipio_zona.do" -type f

Repository: basedosdados/pipelines

Length of output: 134


🏁 Script executed:

# Read the relevant section with context
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  cat -n "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" | sed -n '1,80p'
fi

Repository: basedosdados/pipelines

Length of output: 4179


🏁 Script executed:

# Also check lines around 217-220 mentioned in the internal scratchpad
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  cat -n "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" | sed -n '210,230p'
fi

Repository: basedosdados/pipelines

Length of output: 1064


🏁 Script executed:

# Check the full file length to understand scope
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  wc -l "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do"
fi

Repository: basedosdados/pipelines

Length of output: 136


🏁 Script executed:

# Check the end of the file to see save/append logic
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  cat -n "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" | tail -70
fi

Repository: basedosdados/pipelines

Length of output: 2773


🏁 Script executed:

# Also search for tempfile and save patterns
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  rg -n "tempfile|save|append" "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do"
fi

Repository: basedosdados/pipelines

Length of output: 314


🏁 Script executed:

# Check specifically for any data validation after the imports
if [ -f "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" ]; then
  cat -n "models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do" | sed -n '40,50p'
fi

Repository: basedosdados/pipelines

Length of output: 706


Add check to ensure import succeeded before processing.

Both imports are wrapped in cap, silently suppressing errors if neither file exists for a given state/year. The code proceeds immediately to line 42 and beyond with no validation that data was actually loaded. If both imports fail, the previous iteration's data remains in memory, gets processed, and is saved to detalhes_ano'_estado'' at line 238–239 under the wrong state's name. This contaminated dataset then gets appended (lines 248–253) into the final output, silently corrupting data with no error indication.

Add a check after line 41 such as if _N == 0 error or similar to abort if neither file exists for the current state/year combination.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_municipio_zona.do` around
lines 40 - 41, After the two cap import delimited lines for
detalhe_votacao_munzona_`ano'_`estado' check whether data was actually loaded by
testing _N (e.g., if _N == 0 error "no input for `ano' `estado'"), so you abort
instead of processing stale data; reference the import commands and the output
target detalhes_`ano'_`estado' in the error message to make failures visible and
prevent contaminated datasets from being saved or appended.

Comment on lines +38 to +39
cap import delimited using "input/detalhe_votacao_uf/DETALHE_VOTACAO_UF_`ano'/DETALHE_VOTACAO_UF_`ano'_`uf'.txt", delimiter(";") varn(nonames) stringcols(_all) clear
cap import delimited using "input/detalhe_votacao_uf/DETALHE_VOTACAO_UF_`ano'/DETALHE_VOTACAO_UF_`ano'.txt", delimiter(";") varn(nonames) stringcols(_all) clear

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Abort on missing UF inputs here as well.

These cap import delimited calls swallow file-not-found errors, but the loop immediately starts transforming whatever dataset is already in memory. If both paths fail for one ano/uf, the previous UF's data can be re-saved under the current partition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_uf.do` around lines 38 - 39,
The two lines using "cap import delimited" for DETALHE_VOTACAO_UF_`ano'_`uf'.txt
and DETALHE_VOTACAO_UF_`ano'.txt must not silently swallow missing-file errors;
either remove the leading "cap" or add an explicit file existence check before
importing (use fileexists("input/detalhe_votacao_uf/...`ano'...`uf'.txt") /
fileexists("...`ano'.txt")) and if neither file exists call display/_error and
exit to abort processing for that ano/uf; update the import logic around the
import delimited calls in detalhes_votacao_uf.do so the code only proceeds when
a valid file was found and successfully imported.

Comment on lines +141 to +144
order ano turno id_eleicao tipo_eleicao data_eleicao sigla_uf cargo ///
secoes_anuladas secoes_sem_funcionamento zonas_eleitorais juntas_apuradoras votos_anulados_apurado_separado secoes_totalizadas ///
aptos comparecimento abstencoes votos_validos votos_brancos votos_nulos votos_legenda ///
proporcao_*

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

order references columns this script never creates.

The file only keeps/renames v3-v22, so id_eleicao and data_eleicao do not exist at this point. This order will stop the build before the final save. Either keep/rename those source columns earlier or remove them from this list.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/detalhes_votacao_uf.do` around lines 141 -
144, The ORDER clause references columns that aren't created (id_eleicao,
data_eleicao) after the keep/rename of v3-v22; either add those columns into the
earlier keep/rename step (so id_eleicao and data_eleicao are produced/renamed
from their source vX fields) or remove them from the order list; locate the
ORDER statement named "order" and either (A) update the keep/rename mapping that
converts v3-v22 to include the mappings that produce id_eleicao and
data_eleicao, or (B) delete id_eleicao and data_eleicao from the order list so
only existing columns (the renamed v3-v22 names and proporcao_*) remain.

Comment on lines +763 to +779
use "output/norm_candidatos.dta", clear
keep if mod(ano, 4) == 0
keep ano tipo_eleicao sigla_uf id_municipio_tse cargo titulo_eleitoral numero
tempfile candidatos_mod0
save `candidatos_mod0'

use "output/norm_candidatos.dta", clear
keep if mod(ano, 4) == 2 & cargo != "presidente"
keep ano tipo_eleicao sigla_uf cargo titulo_eleitoral numero
tempfile candidatos_mod2_estadual
save `candidatos_mod2_estadual'

use "output/norm_candidatos.dta", clear
keep if mod(ano, 4) == 2 & cargo == "presidente"
keep ano tipo_eleicao cargo titulo_eleitoral numero
tempfile candidatos_mod2_presid
save `candidatos_mod2_presid'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file structure and read the relevant lines
cd models/br_tse_eleicoes/code/sub/ 2>/dev/null && pwd || echo "Directory not found, searching for the file"
find . -name "normalizacao_particao.do" 2>/dev/null | head -5

Repository: basedosdados/pipelines

Length of output: 143


🏁 Script executed:

# Read lines 763-779 to see the current code
sed -n '763,779p' models/br_tse_eleicoes/code/sub/normalizacao_particao.do

Repository: basedosdados/pipelines

Length of output: 677


🏁 Script executed:

# Read lines 885-901 to see the second occurrence
sed -n '885,901p' models/br_tse_eleicoes/code/sub/normalizacao_particao.do

Repository: basedosdados/pipelines

Length of output: 677


🏁 Script executed:

# Search for where these temporary files are used (candidatos_mod0, candidatos_mod2_estadual, candidatos_mod2_presid)
rg "candidatos_mod0|candidatos_mod2_estadual|candidatos_mod2_presid" models/br_tse_eleicoes/code/sub/normalizacao_particao.do -A 3 -B 1

Repository: basedosdados/pipelines

Length of output: 4339


🏁 Script executed:

# Check prestacao_contas.do for how numero_candidato is imported
find . -name "prestacao_contas.do" -type f

Repository: basedosdados/pipelines

Length of output: 119


🏁 Script executed:

# Check prestacao_contas.do for numero_candidato import with stringc
grep -n "numero_candidato\|stringc" models/br_tse_eleicoes/code/sub/prestacao_contas.do | head -20

Repository: basedosdados/pipelines

Length of output: 1144


🏁 Script executed:

# Look for the complete import section in prestacao_contas.do
sed -n '1,100p' models/br_tse_eleicoes/code/sub/prestacao_contas.do | grep -A 5 -B 5 "stringc\|numero"

Repository: basedosdados/pipelines

Length of output: 667


🏁 Script executed:

# Let's search for where the 763-779 temp files are used - look for the merge operations
sed -n '760,850p' models/br_tse_eleicoes/code/sub/normalizacao_particao.do | grep -A 10 "merge\|candidatos_mod"

Repository: basedosdados/pipelines

Length of output: 1438


🏁 Script executed:

# Check what data is being merged in from the finance files around the merge operations
sed -n '750,1050p' models/br_tse_eleicoes/code/sub/normalizacao_particao.do | grep -B 5 "merge.*candidatos_mod0" | head -30

Repository: basedosdados/pipelines

Length of output: 418


Add tostring numero, replace before saving candidate temporary files for finance merges.

The temporary files created at these locations are used in finance merge operations (receitas_candidato and despesas_candidato) where numero_candidato is imported as a string via stringc(_all) in prestacao_contas.do and renamed to numero. Without converting the candidate lookup tables to string type first, these merges will fail due to type mismatch.

Suggested fix
 use "output/norm_candidatos.dta", clear
 keep if mod(ano, 4) == 0
 keep ano tipo_eleicao sigla_uf id_municipio_tse cargo titulo_eleitoral numero
+tostring numero, replace
 tempfile candidatos_mod0
 save `candidatos_mod0'
 
 use "output/norm_candidatos.dta", clear
 keep if mod(ano, 4) == 2 & cargo != "presidente"
 keep ano tipo_eleicao sigla_uf cargo titulo_eleitoral numero
+tostring numero, replace
 tempfile candidatos_mod2_estadual
 save `candidatos_mod2_estadual'
 
 use "output/norm_candidatos.dta", clear
 keep if mod(ano, 4) == 2 & cargo == "presidente"
 keep ano tipo_eleicao cargo titulo_eleitoral numero
+tostring numero, replace
 tempfile candidatos_mod2_presid
 save `candidatos_mod2_presid'

Also applies to: 885-901

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/normalizacao_particao.do` around lines 763 -
779, Temporary candidate tempfiles are saving the variable numero as numeric
which breaks later finance merges; before each save of the tempfiles
candidatos_mod0, candidatos_mod2_estadual, and candidatos_mod2_presid (the
blocks that use "output/norm_candidatos.dta" and then save `candidatos_mod0' /
`candidatos_mod2_estadual' / `candidatos_mod2_presid') run tostring numero,
replace to convert numero to string; apply the same change in the analogous
block around the later section referenced (lines 885-901) so the temp lookup
tables match numero_candidato imported as string in prestacao_contas.do.

Comment on lines +41 to +46
cap import delimited "input/consulta_coligacao/CONSULTA_LEGENDA_`ano'/CONSULTA_LEGENDA_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/consulta_coligacao/consulta_legendas_`ano'/CONSULTA_LEGENDA_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/consulta_coligacao/consulta_legendas_`ano'/consulta_legendas_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/consulta_coligacao/consulta_legendas_`ano'/consulta_legendas_`ano'_`uf'.csv", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/consulta_coligacao/consulta_coligacao_`ano'/consulta_coligacao_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/consulta_coligacao/consulta_coligacao_`ano'/consulta_coligacao_`ano'_`uf'.csv", delim(";") varn(nonames) stringcols(_all) clear

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "partidos.do" -type f

Repository: basedosdados/pipelines

Length of output: 111


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && wc -l partidos.do

Repository: basedosdados/pipelines

Length of output: 81


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && sed -n '30,60p' partidos.do

Repository: basedosdados/pipelines

Length of output: 1554


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && sed -n '1,80p' partidos.do

Repository: basedosdados/pipelines

Length of output: 3912


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && sed -n '41,120p' partidos.do

Repository: basedosdados/pipelines

Length of output: 2529


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && sed -n '120,180p' partidos.do

Repository: basedosdados/pipelines

Length of output: 1633


🏁 Script executed:

cd models/br_tse_eleicoes/code/sub && sed -n '175,224p' partidos.do

Repository: basedosdados/pipelines

Length of output: 1408


Add an error check after the fallback imports to prevent silent data corruption.

The six cap import delimited calls suppress all errors, allowing the code to proceed even if all imports fail. When none of the paths exist for the current (ano, uf) pair, Stata silently retains the previous UF's dataset in memory. The schema-mapping block then executes on this stale data and saves it to tempfile partidos_uf'_ano'', corrupting that year's append with wrong state data.

Verify that at least one import succeeded before proceeding to schema mapping (e.g., if _N > 0 or explicit nobs check), or fail fast if all paths are missing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/partidos.do` around lines 41 - 46, After the
series of fallback cap import delimited attempts, add a check that the import
actually loaded data (e.g., verify _N>0 or use c(N) / count) before running the
schema-mapping block and saving to tempfile partidos_`uf'_`ano'; if no rows were
loaded, abort or skip this (ano, uf) with an explicit error/log message and
non-zero exit so you don't process stale memory from the previous UF. Place this
check immediately after the final cap import delimited command (the block of six
cap import delimited lines) and ensure the code branches to skip mapping/saving
when the dataset is empty.

Comment on lines +35 to +42
cap import delimited "input/perfil_eleitorado_secao/perfil_eleitor_secao_`ano'_`uf'.txt", delim(";") stringcols(_all) clear
cap import delimited "input/perfil_eleitorado_secao/perfil_eleitor_secao_`ano'_`uf'.csv", delim(";") stringcols(_all) clear

}
else {

cap import delimited "input/perfil_eleitorado_secao/perfil_eleitor_secao_`ano'_`uf'/perfil_eleitor_secao_`ano'_`uf'.txt", delim(";") stringcols(_all) clear
cap import delimited "input/perfil_eleitorado_secao/perfil_eleitor_secao_`ano'_`uf'/perfil_eleitor_secao_`ano'_`uf'.csv", delim(";") stringcols(_all) clear

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Abort the iteration when neither raw file exists.

Both imports are under cap, but the loop never checks whether one succeeded before it starts keep/rename. If both paths miss, the dataset already in memory from the previous UF is transformed and saved again under the current ano/uf.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/perfil_eleitorado_secao.do` around lines 35 -
42, The loop uses suppressed imports (cap import delimited) but never checks if
either import actually succeeded before running the subsequent keep/rename that
assume a dataset is loaded; add explicit checks after the two cap import
delimited attempts: capture the return codes (e.g., store _rc into locals like
rc1 and rc2 after each cap), and if both indicate failure (both nonzero), abort
this iteration (use continue or skip the downstream keep/rename/save logic) and
log an informative message; otherwise proceed only when at least one import
succeeded. Ensure these checks surround the block that performs keep/rename so
you never transform the previous UF’s in-memory data when both files are
missing.

Comment on lines +13 to +14
cap import delimited "input/bem_candidato/bem_candidato_`ano'/bem_candidato_`ano'_`uf'.txt", clear delim(";") varnames(nonames) stringc(_all)
cap import delimited "input/bem_candidato/bem_candidato_`ano'/bem_candidato_`ano'_`uf'.csv", clear delim(";") varnames(nonames) stringc(_all)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "prestacao_contas.do" | head -20

Repository: basedosdados/pipelines

Length of output: 119


🏁 Script executed:

cat -n ./models/br_tse_eleicoes/code/sub/prestacao_contas.do | head -100

Repository: basedosdados/pipelines

Length of output: 3472


🏁 Script executed:

cat -n ./models/br_tse_eleicoes/code/sub/prestacao_contas.do | sed -n '11,80p'

Repository: basedosdados/pipelines

Length of output: 2149


Add data validation after fallback imports to prevent stale state from previous UF iteration.

Both cap import delimited commands on lines 13–14 suppress errors, but if both fail, the previous UF's data remains in memory and gets processed and saved as the current UF's dataset at line 69. This causes data corruption when combined at lines 74–79.

The script should either explicitly clear data before imports, validate that at least one import succeeded, or fail fast on missing files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/prestacao_contas.do` around lines 13 - 14,
Before attempting the fallback imports, explicitly clear the in-memory dataset
and validate that an import succeeded: run a plain clear (to remove previous UF
data) before the two cap import delimited attempts; after each cap import check
_rc and set a flag (e.g., local imported = 1) if successful; if neither import
succeeded (imported missing or _rc != 0 for both) abort or skip processing for
that UF (display an error and continue) so you never reach the subsequent
save/processing steps that write the stale data used later in the save/combine
logic (the save at the end of the UF loop and the combine block that follows).

Comment on lines +440 to +442
if `ano' == 1998 & "`uf'" != "BRASIL" {
keep if cargo == "presidente"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

The 1998 party filter is inverted.

This is the opposite of the candidate handling at Lines 223-230. As written, non-BRASIL files keep only presidente while the BRASIL file stays unfiltered, which will either duplicate presidential rows or discard non-presidential state results.

Suggested fix
-				if `ano' == 1998 & "`uf'" != "BRASIL" {
-					keep if cargo == "presidente"
-				}
+				if `ano' == 1998 {
+					if "`uf'" == "BRASIL" {
+						keep if cargo == "presidente"
+					}
+					else {
+						drop if cargo == "presidente"
+					}
+				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_municipio_zona.do` around lines
440 - 442, The 1998 party filter is inverted: change the condition so only the
BRASIL (national) file is filtered to keep presidente; specifically, in the if
that currently reads checking `ano' == 1998 & "`uf'" != "BRASIL", flip the uf
comparison to == ("`uf'" == "BRASIL") so the block that runs keep if cargo ==
"presidente" applies only to the BRASIL case (variables: ano, uf, cargo).

Comment on lines +47 to +48
cap import delimited "input/votacao_secao/votacao_secao_`ano'_`uf'/votacao_secao_`ano'_`uf'.txt", delim(";") varn(nonames) stringcols(_all) clear
cap import delimited "input/votacao_secao/votacao_secao_`ano'_`uf'/votacao_secao_`ano'_`uf'.csv", delim(";") varn(nonames) stringcols(_all) clear

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check file existence and size
wc -l models/br_tse_eleicoes/code/sub/resultados_secao.do

Repository: basedosdados/pipelines

Length of output: 121


🏁 Script executed:

# Read the file with context around lines 47-48
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | head -70

Repository: basedosdados/pipelines

Length of output: 3648


🏁 Script executed:

# Look for the broader loop structure and any validation after these imports
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | sed -n '40,100p'

Repository: basedosdados/pipelines

Length of output: 2002


🏁 Script executed:

# Check for any validation or error checking after the imports
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | sed -n '45,120p'

Repository: basedosdados/pipelines

Length of output: 2538


🏁 Script executed:

# Search for any `scalar` or `if` statements that might check dataset state
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | grep -E "(nobs|_N|if \(c\(|error|confirm)"

Repository: basedosdados/pipelines

Length of output: 48


🏁 Script executed:

# Check the end of the loop and how data is handled
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | sed -n '115,160p'

Repository: basedosdados/pipelines

Length of output: 1643


🏁 Script executed:

# Look for where data is saved/appended in the loop
cat -n models/br_tse_eleicoes/code/sub/resultados_secao.do | tail -100

Repository: basedosdados/pipelines

Length of output: 3497


Validate that at least one section-result file was successfully imported before running transformations.

Both imports use cap to suppress errors. If neither the .txt nor .csv file exists, both import commands fail silently and the previous iteration's dataset persists in memory. The subsequent transforms (lines 50–90+) then run against this stale data, relabeling it with the current loop's ano and uf values before saving to tmp/resultados_secao_uf'.dta`. This mislabeled data is then split and appended to the final output, corrupting the results.

Add a validation check (e.g., if _N == 0) after both import attempts to halt execution when neither file exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@models/br_tse_eleicoes/code/sub/resultados_secao.do` around lines 47 - 48,
After the two cap import delimited attempts for votacao_secao_`ano'_`uf' (.txt
and .csv), add a validation that the dataset in memory is non-empty (e.g., check
_N) and abort the loop/run if no rows were imported; specifically, after the
import commands referencing votacao_secao_`ano'_`uf' check if _N == 0 and call
an appropriate stop/exit/continue to avoid running the subsequent transforms
that relabel and save stale data for the current ano and uf.

@rdahis

rdahis commented Mar 31, 2026

Copy link
Copy Markdown
Member Author

Fechando pois decidimos não subir código de Stata nesse repositório. O PR #1476 já subindo o código refatorado em Python.

@rdahis rdahis closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants