Madras1 commited on
Commit
7223f8f
·
verified ·
1 Parent(s): f76a41f

Upload 58 files

Browse files
app/api/routes/dados_publicos.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Public Data API Routes - IBGE and TSE data access
3
+ """
4
+ from fastapi import APIRouter, HTTPException, Query
5
+ from pydantic import BaseModel, Field
6
+ from typing import Optional, List, Dict, Any
7
+
8
+ from app.services.ibge_api import (
9
+ listar_estados,
10
+ listar_municipios,
11
+ buscar_municipio,
12
+ enriquecer_localizacao
13
+ )
14
+ from app.services.tse_api import (
15
+ listar_eleicoes,
16
+ buscar_candidatos,
17
+ obter_candidato_detalhes,
18
+ buscar_politico
19
+ )
20
+
21
+
22
+ router = APIRouter(prefix="/dados", tags=["Public Data"])
23
+
24
+
25
+ # ========== IBGE Endpoints ==========
26
+
27
+ class EstadoResponse(BaseModel):
28
+ id: int
29
+ sigla: str
30
+ nome: str
31
+ regiao: str
32
+
33
+
34
+ class MunicipioResponse(BaseModel):
35
+ id: int
36
+ nome: str
37
+ estado_sigla: str
38
+ estado_nome: str
39
+ regiao: str
40
+
41
+
42
+ @router.get("/ibge/estados", response_model=List[EstadoResponse])
43
+ async def get_estados():
44
+ """List all Brazilian states"""
45
+ estados = await listar_estados()
46
+ return [EstadoResponse(**e.__dict__) for e in estados]
47
+
48
+
49
+ @router.get("/ibge/municipios/{uf}", response_model=List[MunicipioResponse])
50
+ async def get_municipios(uf: str):
51
+ """List municipalities in a state"""
52
+ municipios = await listar_municipios(uf)
53
+ return [MunicipioResponse(**m.__dict__) for m in municipios]
54
+
55
+
56
+ @router.get("/ibge/buscar")
57
+ async def buscar_cidade(
58
+ nome: str = Query(..., min_length=2),
59
+ uf: Optional[str] = None
60
+ ):
61
+ """Search for a municipality by name"""
62
+ municipios = await buscar_municipio(nome, uf)
63
+ return [MunicipioResponse(**m.__dict__) for m in municipios]
64
+
65
+
66
+ @router.get("/ibge/enriquecer")
67
+ async def enriquecer_cidade(
68
+ cidade: str = Query(..., min_length=2),
69
+ uf: Optional[str] = None
70
+ ):
71
+ """Enrich a location name with IBGE data"""
72
+ return await enriquecer_localizacao(cidade, uf)
73
+
74
+
75
+ # ========== TSE Endpoints ==========
76
+
77
+ class EleicaoResponse(BaseModel):
78
+ id: int
79
+ ano: int
80
+ descricao: str
81
+ turno: int
82
+
83
+
84
+ class CandidatoResponse(BaseModel):
85
+ id: int
86
+ nome: str
87
+ nome_urna: str
88
+ numero: str
89
+ cargo: str
90
+ partido_sigla: str
91
+ uf: str
92
+ municipio: str
93
+ situacao: str
94
+ total_bens: float
95
+
96
+
97
+ class CandidatoDetalhadoResponse(BaseModel):
98
+ id: int
99
+ nome: str
100
+ nome_urna: str
101
+ numero: str
102
+ cargo: str
103
+ partido_sigla: str
104
+ partido_nome: str
105
+ uf: str
106
+ municipio: str
107
+ situacao: str
108
+ data_nascimento: str
109
+ genero: str
110
+ grau_instrucao: str
111
+ ocupacao: str
112
+ total_bens: float
113
+ bens: List[Dict[str, Any]]
114
+
115
+
116
+ @router.get("/tse/eleicoes", response_model=List[EleicaoResponse])
117
+ async def get_eleicoes():
118
+ """List available elections"""
119
+ eleicoes = await listar_eleicoes()
120
+ return [EleicaoResponse(**e.__dict__) for e in eleicoes]
121
+
122
+
123
+ @router.get("/tse/candidatos")
124
+ async def get_candidatos(
125
+ nome: str = Query(..., min_length=3),
126
+ ano: int = Query(default=2024),
127
+ uf: Optional[str] = None,
128
+ cargo: Optional[str] = None
129
+ ):
130
+ """Search for candidates by name"""
131
+ candidatos = await buscar_candidatos(nome, ano=ano, uf=uf, cargo=cargo)
132
+ return [CandidatoResponse(**c.__dict__) for c in candidatos]
133
+
134
+
135
+ @router.get("/tse/candidato/{id_candidato}")
136
+ async def get_candidato_detalhes(
137
+ id_candidato: int,
138
+ ano: int = Query(default=2024)
139
+ ):
140
+ """Get detailed candidate information including assets"""
141
+ candidato = await obter_candidato_detalhes(id_candidato, ano=ano)
142
+
143
+ if not candidato:
144
+ raise HTTPException(status_code=404, detail="Candidato não encontrado")
145
+
146
+ return CandidatoDetalhadoResponse(**candidato.__dict__)
147
+
148
+
149
+ @router.get("/tse/politico")
150
+ async def pesquisar_politico(nome: str = Query(..., min_length=3)):
151
+ """
152
+ Search for a politician across multiple elections.
153
+ Returns consolidated career information.
154
+ """
155
+ return await buscar_politico(nome)
app/main.py CHANGED
@@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
8
 
9
  from app.config import settings
10
  from app.core.database import init_db
11
- from app.api.routes import entities, relationships, events, search, ingest, analyze, graph, research, chat, investigate
12
 
13
 
14
  @asynccontextmanager
@@ -60,6 +60,7 @@ app.include_router(graph.router, prefix="/api/v1")
60
  app.include_router(research.router, prefix="/api/v1")
61
  app.include_router(chat.router, prefix="/api/v1")
62
  app.include_router(investigate.router, prefix="/api/v1")
 
63
 
64
 
65
  @app.get("/")
 
8
 
9
  from app.config import settings
10
  from app.core.database import init_db
11
+ from app.api.routes import entities, relationships, events, search, ingest, analyze, graph, research, chat, investigate, dados_publicos
12
 
13
 
14
  @asynccontextmanager
 
60
  app.include_router(research.router, prefix="/api/v1")
61
  app.include_router(chat.router, prefix="/api/v1")
62
  app.include_router(investigate.router, prefix="/api/v1")
63
+ app.include_router(dados_publicos.router, prefix="/api/v1")
64
 
65
 
66
  @app.get("/")
app/services/ibge_api.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ IBGE API Service
3
+ Access to Brazilian geographic and demographic data
4
+ """
5
+ import httpx
6
+ from typing import Optional, Dict, Any, List
7
+ from dataclasses import dataclass
8
+
9
+
10
+ IBGE_BASE_URL = "https://servicodados.ibge.gov.br/api/v1"
11
+
12
+
13
+ @dataclass
14
+ class Estado:
15
+ """Brazilian state data"""
16
+ id: int
17
+ sigla: str
18
+ nome: str
19
+ regiao: str
20
+
21
+
22
+ @dataclass
23
+ class Municipio:
24
+ """Brazilian municipality data"""
25
+ id: int
26
+ nome: str
27
+ estado_sigla: str
28
+ estado_nome: str
29
+ regiao: str
30
+ # Optional enriched data
31
+ populacao: Optional[int] = None
32
+ area_km2: Optional[float] = None
33
+
34
+
35
+ async def listar_estados() -> List[Estado]:
36
+ """List all Brazilian states"""
37
+ try:
38
+ async with httpx.AsyncClient(timeout=15.0) as client:
39
+ response = await client.get(f"{IBGE_BASE_URL}/localidades/estados")
40
+
41
+ if response.status_code != 200:
42
+ return []
43
+
44
+ data = response.json()
45
+ estados = []
46
+
47
+ for item in data:
48
+ estados.append(Estado(
49
+ id=item["id"],
50
+ sigla=item["sigla"],
51
+ nome=item["nome"],
52
+ regiao=item.get("regiao", {}).get("nome", "")
53
+ ))
54
+
55
+ return sorted(estados, key=lambda x: x.nome)
56
+
57
+ except Exception as e:
58
+ print(f"IBGE estados error: {e}")
59
+ return []
60
+
61
+
62
+ async def listar_municipios(uf: str) -> List[Municipio]:
63
+ """List all municipalities in a state"""
64
+ try:
65
+ async with httpx.AsyncClient(timeout=15.0) as client:
66
+ response = await client.get(
67
+ f"{IBGE_BASE_URL}/localidades/estados/{uf}/municipios"
68
+ )
69
+
70
+ if response.status_code != 200:
71
+ return []
72
+
73
+ data = response.json()
74
+ municipios = []
75
+
76
+ for item in data:
77
+ municipios.append(Municipio(
78
+ id=item["id"],
79
+ nome=item["nome"],
80
+ estado_sigla=uf.upper(),
81
+ estado_nome=item.get("microrregiao", {}).get("mesorregiao", {}).get("UF", {}).get("nome", ""),
82
+ regiao=item.get("microrregiao", {}).get("mesorregiao", {}).get("UF", {}).get("regiao", {}).get("nome", "")
83
+ ))
84
+
85
+ return sorted(municipios, key=lambda x: x.nome)
86
+
87
+ except Exception as e:
88
+ print(f"IBGE municipios error: {e}")
89
+ return []
90
+
91
+
92
+ async def buscar_municipio(nome: str, uf: Optional[str] = None) -> List[Municipio]:
93
+ """Search for municipalities by name"""
94
+ try:
95
+ # If UF provided, search only that state
96
+ if uf:
97
+ municipios = await listar_municipios(uf)
98
+ return [m for m in municipios if nome.lower() in m.nome.lower()]
99
+
100
+ # Otherwise search all states (slower)
101
+ async with httpx.AsyncClient(timeout=30.0) as client:
102
+ response = await client.get(f"{IBGE_BASE_URL}/localidades/municipios")
103
+
104
+ if response.status_code != 200:
105
+ return []
106
+
107
+ data = response.json()
108
+ results = []
109
+
110
+ for item in data:
111
+ if nome.lower() in item["nome"].lower():
112
+ uf_info = item.get("microrregiao", {}).get("mesorregiao", {}).get("UF", {})
113
+ results.append(Municipio(
114
+ id=item["id"],
115
+ nome=item["nome"],
116
+ estado_sigla=uf_info.get("sigla", ""),
117
+ estado_nome=uf_info.get("nome", ""),
118
+ regiao=uf_info.get("regiao", {}).get("nome", "")
119
+ ))
120
+
121
+ return results[:20] # Limit results
122
+
123
+ except Exception as e:
124
+ print(f"IBGE search error: {e}")
125
+ return []
126
+
127
+
128
+ async def obter_municipio_por_id(id_municipio: int) -> Optional[Municipio]:
129
+ """Get municipality by IBGE code"""
130
+ try:
131
+ async with httpx.AsyncClient(timeout=15.0) as client:
132
+ response = await client.get(
133
+ f"{IBGE_BASE_URL}/localidades/municipios/{id_municipio}"
134
+ )
135
+
136
+ if response.status_code != 200:
137
+ return None
138
+
139
+ item = response.json()
140
+ uf_info = item.get("microrregiao", {}).get("mesorregiao", {}).get("UF", {})
141
+
142
+ return Municipio(
143
+ id=item["id"],
144
+ nome=item["nome"],
145
+ estado_sigla=uf_info.get("sigla", ""),
146
+ estado_nome=uf_info.get("nome", ""),
147
+ regiao=uf_info.get("regiao", {}).get("nome", "")
148
+ )
149
+
150
+ except Exception as e:
151
+ print(f"IBGE municipio error: {e}")
152
+ return None
153
+
154
+
155
+ async def enriquecer_localizacao(cidade: str, uf: Optional[str] = None) -> Dict[str, Any]:
156
+ """
157
+ Enrich a location name with IBGE data.
158
+ Useful for adding context to extracted locations.
159
+ """
160
+ resultado = {
161
+ "cidade_original": cidade,
162
+ "encontrado": False,
163
+ "ibge_codigo": None,
164
+ "cidade": None,
165
+ "estado": None,
166
+ "estado_sigla": None,
167
+ "regiao": None
168
+ }
169
+
170
+ municipios = await buscar_municipio(cidade, uf)
171
+
172
+ if municipios:
173
+ # Take best match (exact or first)
174
+ melhor = None
175
+ for m in municipios:
176
+ if m.nome.lower() == cidade.lower():
177
+ melhor = m
178
+ break
179
+
180
+ if not melhor:
181
+ melhor = municipios[0]
182
+
183
+ resultado.update({
184
+ "encontrado": True,
185
+ "ibge_codigo": melhor.id,
186
+ "cidade": melhor.nome,
187
+ "estado": melhor.estado_nome,
188
+ "estado_sigla": melhor.estado_sigla,
189
+ "regiao": melhor.regiao
190
+ })
191
+
192
+ return resultado
app/services/tse_api.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ TSE (Tribunal Superior Eleitoral) API Service
3
+ Access to Brazilian electoral data - candidates, assets, donations
4
+ """
5
+ import httpx
6
+ from typing import Optional, Dict, Any, List
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ # DivulgaCand API (unofficial but functional)
11
+ TSE_DIVULGACAND_URL = "https://divulgacandcontas.tse.jus.br/divulga/rest/v1"
12
+
13
+
14
+ @dataclass
15
+ class Candidato:
16
+ """Electoral candidate data"""
17
+ id: int
18
+ nome: str
19
+ nome_urna: str
20
+ cpf_parcial: str = "" # TSE only shows partial
21
+ numero: str = ""
22
+ cargo: str = ""
23
+ partido_sigla: str = ""
24
+ partido_nome: str = ""
25
+ coligacao: str = ""
26
+ situacao: str = ""
27
+
28
+ # Location
29
+ uf: str = ""
30
+ municipio: str = ""
31
+
32
+ # Personal
33
+ data_nascimento: str = ""
34
+ genero: str = ""
35
+ grau_instrucao: str = ""
36
+ ocupacao: str = ""
37
+
38
+ # Assets
39
+ total_bens: float = 0.0
40
+ bens: List[Dict[str, Any]] = field(default_factory=list)
41
+
42
+ # Campaign
43
+ total_receitas: float = 0.0
44
+ total_despesas: float = 0.0
45
+
46
+
47
+ @dataclass
48
+ class Eleicao:
49
+ """Election metadata"""
50
+ id: int
51
+ ano: int
52
+ descricao: str
53
+ turno: int = 1
54
+
55
+
56
+ async def listar_eleicoes() -> List[Eleicao]:
57
+ """List available elections"""
58
+ try:
59
+ async with httpx.AsyncClient(timeout=15.0) as client:
60
+ response = await client.get(f"{TSE_DIVULGACAND_URL}/eleicao/ordinarias")
61
+
62
+ if response.status_code != 200:
63
+ return []
64
+
65
+ data = response.json()
66
+ eleicoes = []
67
+
68
+ for item in data:
69
+ eleicoes.append(Eleicao(
70
+ id=item.get("id", 0),
71
+ ano=item.get("ano", 0),
72
+ descricao=item.get("descricaoEleicao", ""),
73
+ turno=item.get("turno", 1)
74
+ ))
75
+
76
+ return sorted(eleicoes, key=lambda x: x.ano, reverse=True)
77
+
78
+ except Exception as e:
79
+ print(f"TSE eleicoes error: {e}")
80
+ return []
81
+
82
+
83
+ async def buscar_candidatos(
84
+ nome: str,
85
+ ano: int = 2024,
86
+ uf: Optional[str] = None,
87
+ cargo: Optional[str] = None
88
+ ) -> List[Candidato]:
89
+ """
90
+ Search for candidates by name.
91
+
92
+ Args:
93
+ nome: Candidate name to search
94
+ ano: Election year (default 2024)
95
+ uf: State filter (optional)
96
+ cargo: Position filter (optional)
97
+ """
98
+ try:
99
+ # First get the election ID for the year
100
+ eleicoes = await listar_eleicoes()
101
+ eleicao = next((e for e in eleicoes if e.ano == ano), None)
102
+
103
+ if not eleicao:
104
+ # Try common election IDs
105
+ eleicao_id = {2024: 546, 2022: 544, 2020: 426, 2018: 295}.get(ano, 546)
106
+ else:
107
+ eleicao_id = eleicao.id
108
+
109
+ # Build search URL
110
+ base_url = f"{TSE_DIVULGACAND_URL}/candidatura/listar/{ano}/{eleicao_id}"
111
+
112
+ params = {"nomeCompleto": nome}
113
+ if uf:
114
+ params["uf"] = uf.upper()
115
+ if cargo:
116
+ params["cargo"] = cargo
117
+
118
+ async with httpx.AsyncClient(timeout=30.0) as client:
119
+ response = await client.get(base_url, params=params)
120
+
121
+ if response.status_code != 200:
122
+ return []
123
+
124
+ data = response.json()
125
+ candidatos_data = data.get("candidatos", [])
126
+
127
+ candidatos = []
128
+ for item in candidatos_data:
129
+ candidatos.append(Candidato(
130
+ id=item.get("id", 0),
131
+ nome=item.get("nomeCompleto", ""),
132
+ nome_urna=item.get("nomeUrna", ""),
133
+ cpf_parcial=item.get("cpf", "")[:3] + ".***.***-**" if item.get("cpf") else "",
134
+ numero=str(item.get("numero", "")),
135
+ cargo=item.get("cargo", {}).get("nome", "") if isinstance(item.get("cargo"), dict) else str(item.get("cargo", "")),
136
+ partido_sigla=item.get("partido", {}).get("sigla", "") if isinstance(item.get("partido"), dict) else "",
137
+ partido_nome=item.get("partido", {}).get("nome", "") if isinstance(item.get("partido"), dict) else "",
138
+ uf=item.get("ufSigla", "") or item.get("uf", ""),
139
+ municipio=item.get("municipio", {}).get("nome", "") if isinstance(item.get("municipio"), dict) else "",
140
+ situacao=item.get("situacao", ""),
141
+ total_bens=float(item.get("totalDeBens", 0) or 0)
142
+ ))
143
+
144
+ return candidatos
145
+
146
+ except Exception as e:
147
+ print(f"TSE search error: {e}")
148
+ return []
149
+
150
+
151
+ async def obter_candidato_detalhes(
152
+ id_candidato: int,
153
+ ano: int = 2024,
154
+ eleicao_id: Optional[int] = None
155
+ ) -> Optional[Candidato]:
156
+ """Get detailed candidate information including assets"""
157
+ try:
158
+ if not eleicao_id:
159
+ eleicao_id = {2024: 546, 2022: 544, 2020: 426, 2018: 295}.get(ano, 546)
160
+
161
+ async with httpx.AsyncClient(timeout=30.0) as client:
162
+ # Get candidate details
163
+ response = await client.get(
164
+ f"{TSE_DIVULGACAND_URL}/candidatura/buscar/{ano}/{eleicao_id}/candidato/{id_candidato}"
165
+ )
166
+
167
+ if response.status_code != 200:
168
+ return None
169
+
170
+ item = response.json()
171
+
172
+ candidato = Candidato(
173
+ id=item.get("id", 0),
174
+ nome=item.get("nomeCompleto", ""),
175
+ nome_urna=item.get("nomeUrna", ""),
176
+ numero=str(item.get("numero", "")),
177
+ cargo=item.get("cargo", {}).get("nome", "") if isinstance(item.get("cargo"), dict) else "",
178
+ partido_sigla=item.get("partido", {}).get("sigla", "") if isinstance(item.get("partido"), dict) else "",
179
+ partido_nome=item.get("partido", {}).get("nome", "") if isinstance(item.get("partido"), dict) else "",
180
+ uf=item.get("ufSigla", ""),
181
+ municipio=item.get("localCandidatura", ""),
182
+ situacao=item.get("situacao", ""),
183
+ data_nascimento=item.get("dataNascimento", ""),
184
+ genero=item.get("genero", ""),
185
+ grau_instrucao=item.get("grauInstrucao", ""),
186
+ ocupacao=item.get("ocupacao", ""),
187
+ total_bens=float(item.get("totalDeBens", 0) or 0)
188
+ )
189
+
190
+ # Try to get assets (bens)
191
+ try:
192
+ bens_response = await client.get(
193
+ f"{TSE_DIVULGACAND_URL}/candidatura/buscar/{ano}/{eleicao_id}/candidato/{id_candidato}/bens"
194
+ )
195
+ if bens_response.status_code == 200:
196
+ bens_data = bens_response.json()
197
+ candidato.bens = [
198
+ {
199
+ "tipo": b.get("tipoBem", ""),
200
+ "descricao": b.get("descricao", ""),
201
+ "valor": float(b.get("valor", 0) or 0)
202
+ }
203
+ for b in bens_data
204
+ ]
205
+ except:
206
+ pass
207
+
208
+ return candidato
209
+
210
+ except Exception as e:
211
+ print(f"TSE details error: {e}")
212
+ return None
213
+
214
+
215
+ async def buscar_politico(nome: str) -> Dict[str, Any]:
216
+ """
217
+ Search for a politician across multiple elections.
218
+ Returns consolidated information.
219
+ """
220
+ resultado = {
221
+ "nome": nome,
222
+ "encontrado": False,
223
+ "candidaturas": [],
224
+ "ultimo_cargo": None,
225
+ "total_patrimonio": 0.0,
226
+ "partidos": set(),
227
+ "ufs": set()
228
+ }
229
+
230
+ # Search in recent elections
231
+ for ano in [2024, 2022, 2020, 2018]:
232
+ candidatos = await buscar_candidatos(nome, ano=ano)
233
+
234
+ for c in candidatos:
235
+ if nome.lower() in c.nome.lower():
236
+ resultado["encontrado"] = True
237
+ resultado["candidaturas"].append({
238
+ "ano": ano,
239
+ "cargo": c.cargo,
240
+ "partido": c.partido_sigla,
241
+ "uf": c.uf,
242
+ "situacao": c.situacao,
243
+ "patrimonio": c.total_bens
244
+ })
245
+
246
+ if c.partido_sigla:
247
+ resultado["partidos"].add(c.partido_sigla)
248
+ if c.uf:
249
+ resultado["ufs"].add(c.uf)
250
+
251
+ if c.total_bens > resultado["total_patrimonio"]:
252
+ resultado["total_patrimonio"] = c.total_bens
253
+
254
+ if not resultado["ultimo_cargo"]:
255
+ resultado["ultimo_cargo"] = f"{c.cargo} ({ano})"
256
+
257
+ if resultado["encontrado"]:
258
+ break # Found in most recent election
259
+
260
+ # Convert sets to lists for JSON
261
+ resultado["partidos"] = list(resultado["partidos"])
262
+ resultado["ufs"] = list(resultado["ufs"])
263
+
264
+ return resultado