Numidium / app /services /tse_api.py
Madras1's picture
Upload 58 files
f4c2765 verified
"""
TSE (Tribunal Superior Eleitoral) API Service
Access to Brazilian electoral data - candidates, assets, donations
"""
import httpx
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, field
# DivulgaCand API (unofficial but functional)
TSE_DIVULGACAND_URL = "https://divulgacandcontas.tse.jus.br/divulga/rest/v1"
@dataclass
class Candidato:
"""Electoral candidate data"""
id: int
nome: str
nome_urna: str
cpf_parcial: str = "" # TSE only shows partial
numero: str = ""
cargo: str = ""
partido_sigla: str = ""
partido_nome: str = ""
coligacao: str = ""
situacao: str = ""
# Location
uf: str = ""
municipio: str = ""
# Personal
data_nascimento: str = ""
genero: str = ""
grau_instrucao: str = ""
ocupacao: str = ""
# Assets
total_bens: float = 0.0
bens: List[Dict[str, Any]] = field(default_factory=list)
# Campaign
total_receitas: float = 0.0
total_despesas: float = 0.0
@dataclass
class Eleicao:
"""Election metadata"""
id: int
ano: int
descricao: str
turno: int = 1
async def listar_eleicoes() -> List[Eleicao]:
"""List available elections"""
try:
async with httpx.AsyncClient(timeout=15.0) as client:
response = await client.get(f"{TSE_DIVULGACAND_URL}/eleicao/ordinarias")
if response.status_code != 200:
return []
data = response.json()
eleicoes = []
for item in data:
eleicoes.append(Eleicao(
id=item.get("id", 0),
ano=item.get("ano", 0),
descricao=item.get("descricaoEleicao", ""),
turno=item.get("turno", 1)
))
return sorted(eleicoes, key=lambda x: x.ano, reverse=True)
except Exception as e:
print(f"TSE eleicoes error: {e}")
return []
async def buscar_candidatos(
nome: str,
ano: int = 2024,
uf: Optional[str] = None,
cargo: Optional[str] = None
) -> List[Candidato]:
"""
Search for candidates by name.
Args:
nome: Candidate name to search
ano: Election year (default 2024)
uf: State filter (optional)
cargo: Position filter (optional)
"""
try:
# First get the election ID for the year
eleicoes = await listar_eleicoes()
eleicao = next((e for e in eleicoes if e.ano == ano), None)
if not eleicao:
# Try common election IDs
eleicao_id = {2024: 546, 2022: 544, 2020: 426, 2018: 295}.get(ano, 546)
else:
eleicao_id = eleicao.id
# Build search URL
base_url = f"{TSE_DIVULGACAND_URL}/candidatura/listar/{ano}/{eleicao_id}"
params = {"nomeCompleto": nome}
if uf:
params["uf"] = uf.upper()
if cargo:
params["cargo"] = cargo
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(base_url, params=params)
if response.status_code != 200:
return []
data = response.json()
candidatos_data = data.get("candidatos", [])
candidatos = []
for item in candidatos_data:
candidatos.append(Candidato(
id=item.get("id", 0),
nome=item.get("nomeCompleto", ""),
nome_urna=item.get("nomeUrna", ""),
cpf_parcial=item.get("cpf", "")[:3] + ".***.***-**" if item.get("cpf") else "",
numero=str(item.get("numero", "")),
cargo=item.get("cargo", {}).get("nome", "") if isinstance(item.get("cargo"), dict) else str(item.get("cargo", "")),
partido_sigla=item.get("partido", {}).get("sigla", "") if isinstance(item.get("partido"), dict) else "",
partido_nome=item.get("partido", {}).get("nome", "") if isinstance(item.get("partido"), dict) else "",
uf=item.get("ufSigla", "") or item.get("uf", ""),
municipio=item.get("municipio", {}).get("nome", "") if isinstance(item.get("municipio"), dict) else "",
situacao=item.get("situacao", ""),
total_bens=float(item.get("totalDeBens", 0) or 0)
))
return candidatos
except Exception as e:
print(f"TSE search error: {e}")
return []
async def obter_candidato_detalhes(
id_candidato: int,
ano: int = 2024,
eleicao_id: Optional[int] = None
) -> Optional[Candidato]:
"""Get detailed candidate information including assets"""
try:
if not eleicao_id:
eleicao_id = {2024: 546, 2022: 544, 2020: 426, 2018: 295}.get(ano, 546)
async with httpx.AsyncClient(timeout=30.0) as client:
# Get candidate details
response = await client.get(
f"{TSE_DIVULGACAND_URL}/candidatura/buscar/{ano}/{eleicao_id}/candidato/{id_candidato}"
)
if response.status_code != 200:
return None
item = response.json()
candidato = Candidato(
id=item.get("id", 0),
nome=item.get("nomeCompleto", ""),
nome_urna=item.get("nomeUrna", ""),
numero=str(item.get("numero", "")),
cargo=item.get("cargo", {}).get("nome", "") if isinstance(item.get("cargo"), dict) else "",
partido_sigla=item.get("partido", {}).get("sigla", "") if isinstance(item.get("partido"), dict) else "",
partido_nome=item.get("partido", {}).get("nome", "") if isinstance(item.get("partido"), dict) else "",
uf=item.get("ufSigla", ""),
municipio=item.get("localCandidatura", ""),
situacao=item.get("situacao", ""),
data_nascimento=item.get("dataNascimento", ""),
genero=item.get("genero", ""),
grau_instrucao=item.get("grauInstrucao", ""),
ocupacao=item.get("ocupacao", ""),
total_bens=float(item.get("totalDeBens", 0) or 0)
)
# Try to get assets (bens)
try:
bens_response = await client.get(
f"{TSE_DIVULGACAND_URL}/candidatura/buscar/{ano}/{eleicao_id}/candidato/{id_candidato}/bens"
)
if bens_response.status_code == 200:
bens_data = bens_response.json()
candidato.bens = [
{
"tipo": b.get("tipoBem", ""),
"descricao": b.get("descricao", ""),
"valor": float(b.get("valor", 0) or 0)
}
for b in bens_data
]
except:
pass
return candidato
except Exception as e:
print(f"TSE details error: {e}")
return None
async def buscar_politico(nome: str) -> Dict[str, Any]:
"""
Search for a politician across multiple elections.
Returns consolidated information.
"""
resultado = {
"nome": nome,
"encontrado": False,
"candidaturas": [],
"ultimo_cargo": None,
"total_patrimonio": 0.0,
"partidos": set(),
"ufs": set()
}
# Search in recent elections - continue through ALL years
for ano in [2024, 2022, 2020, 2018]:
try:
candidatos = await buscar_candidatos(nome, ano=ano)
print(f"TSE: Buscando '{nome}' em {ano} - encontrados: {len(candidatos)}")
for c in candidatos:
# Match if nome is in the candidate's full name
if nome.lower() in c.nome.lower() or nome.lower() in c.nome_urna.lower():
resultado["encontrado"] = True
resultado["candidaturas"].append({
"ano": ano,
"cargo": c.cargo,
"partido": c.partido_sigla,
"uf": c.uf,
"situacao": c.situacao,
"patrimonio": c.total_bens
})
if c.partido_sigla:
resultado["partidos"].add(c.partido_sigla)
if c.uf:
resultado["ufs"].add(c.uf)
if c.total_bens > resultado["total_patrimonio"]:
resultado["total_patrimonio"] = c.total_bens
if not resultado["ultimo_cargo"]:
resultado["ultimo_cargo"] = f"{c.cargo} ({ano})"
except Exception as e:
print(f"TSE search {ano} error: {e}")
continue
# Convert sets to lists for JSON
resultado["partidos"] = list(resultado["partidos"])
resultado["ufs"] = list(resultado["ufs"])
print(f"TSE resultado para '{nome}': encontrado={resultado['encontrado']}, candidaturas={len(resultado['candidaturas'])}")
return resultado