|
|
"""
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
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 = ""
|
|
|
numero: str = ""
|
|
|
cargo: str = ""
|
|
|
partido_sigla: str = ""
|
|
|
partido_nome: str = ""
|
|
|
coligacao: str = ""
|
|
|
situacao: str = ""
|
|
|
|
|
|
|
|
|
uf: str = ""
|
|
|
municipio: str = ""
|
|
|
|
|
|
|
|
|
data_nascimento: str = ""
|
|
|
genero: str = ""
|
|
|
grau_instrucao: str = ""
|
|
|
ocupacao: str = ""
|
|
|
|
|
|
|
|
|
total_bens: float = 0.0
|
|
|
bens: List[Dict[str, Any]] = field(default_factory=list)
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
eleicoes = await listar_eleicoes()
|
|
|
eleicao = next((e for e in eleicoes if e.ano == ano), None)
|
|
|
|
|
|
if not eleicao:
|
|
|
|
|
|
eleicao_id = {2024: 546, 2022: 544, 2020: 426, 2018: 295}.get(ano, 546)
|
|
|
else:
|
|
|
eleicao_id = eleicao.id
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
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:
|
|
|
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()
|
|
|
}
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|