feat(avaliacao): adicionar nome da comissão nas avaliações

- Adiciona campo nome_comissao em AvaliacaoComissao (entity, DTO, schema)
- Extrai nome da comissão do Elasticsearch no repository
- Propaga campo pelo job de processamento e use case
- Exibe nome da comissão no ConsultorCard (frontend)
- Remove link do manual PDF do footer
This commit is contained in:
Frederico Castro
2025-12-17 14:15:43 -03:00
parent 0f61b55944
commit 15570be9c9
12 changed files with 39 additions and 3 deletions

View File

@@ -47,6 +47,7 @@ class AvaliacaoComissaoDTO:
premio: str premio: str
ano: int ano: int
comissao_tipo: str comissao_tipo: str
nome_comissao: str
@dataclass @dataclass

View File

@@ -149,7 +149,8 @@ class ProcessarRankingJob:
"tipo": a.tipo, "tipo": a.tipo,
"premio": a.premio, "premio": a.premio,
"ano": a.ano, "ano": a.ano,
"comissao_tipo": a.comissao_tipo "comissao_tipo": a.comissao_tipo,
"nome_comissao": a.nome_comissao,
} }
for a in consultor.avaliacoes_comissao for a in consultor.avaliacoes_comissao
], ],

View File

@@ -109,6 +109,7 @@ class ObterRankingUseCase:
premio=a.premio, premio=a.premio,
ano=a.ano, ano=a.ano,
comissao_tipo=a.comissao_tipo, comissao_tipo=a.comissao_tipo,
nome_comissao=a.nome_comissao,
) )
for a in consultor.avaliacoes_comissao for a in consultor.avaliacoes_comissao
], ],

View File

@@ -44,6 +44,7 @@ class AvaliacaoComissao:
premio: str premio: str
ano: int ano: int
comissao_tipo: str = "" comissao_tipo: str = ""
nome_comissao: str = ""
@dataclass @dataclass

View File

@@ -253,6 +253,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
comissao = dados.get("comissao", {}) or {} comissao = dados.get("comissao", {}) or {}
comissao_tipo = comissao.get("tipo", "") if isinstance(comissao, dict) else "" comissao_tipo = comissao.get("tipo", "") if isinstance(comissao, dict) else ""
nome_comissao = comissao.get("nome", "") if isinstance(comissao, dict) else ""
is_grande_premio = "grande" in nome_premio.lower() is_grande_premio = "grande" in nome_premio.lower()
is_coordenador = "coordenador" in tipo_part.lower() or "presidente" in tipo_part.lower() is_coordenador = "coordenador" in tipo_part.lower() or "presidente" in tipo_part.lower()
@@ -268,6 +269,7 @@ class ConsultorRepositoryImpl(ConsultorRepository):
premio=nome_premio, premio=nome_premio,
ano=ano, ano=ano,
comissao_tipo=comissao_tipo, comissao_tipo=comissao_tipo,
nome_comissao=nome_comissao,
)) ))
return avaliacoes return avaliacoes

View File

@@ -1,9 +1,11 @@
import logging import logging
import json import json
import asyncio import asyncio
from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from .routes import router from .routes import router
@@ -147,6 +149,10 @@ app.add_middleware(
app.include_router(router) app.include_router(router)
static_dir = Path(__file__).parent.parent.parent.parent / "static"
if static_dir.exists():
app.mount("/api/static", StaticFiles(directory=str(static_dir)), name="static")
@app.get("/") @app.get("/")
async def root(): async def root():

View File

@@ -42,6 +42,7 @@ class AvaliacaoComissaoSchema(BaseModel):
premio: str premio: str
ano: int ano: int
comissao_tipo: str comissao_tipo: str
nome_comissao: str = ""
class PremiacaoSchema(BaseModel): class PremiacaoSchema(BaseModel):

Binary file not shown.

View File

@@ -22,6 +22,7 @@ services:
volumes: volumes:
- ./backend/src:/app/src - ./backend/src:/app/src
- ./backend/scripts:/app/scripts - ./backend/scripts:/app/scripts
- ./backend/static:/app/static
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
networks: networks:
- shared_network - shared_network

View File

@@ -194,6 +194,28 @@ footer p + p {
font-size: 0.8rem; font-size: 0.8rem;
} }
.manual-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
padding: 0.6rem 1.2rem;
background: linear-gradient(145deg, var(--accent), var(--accent-2));
color: white;
text-decoration: none;
border-radius: 8px;
font-weight: 600;
font-size: 0.9rem;
transition: all 200ms ease;
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
.manual-link:hover {
filter: brightness(1.1);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(79, 70, 229, 0.4);
}
.selecao-flutuante { .selecao-flutuante {
position: fixed; position: fixed;
bottom: 2rem; bottom: 2rem;

View File

@@ -262,7 +262,7 @@ function App() {
<footer> <footer>
<p>Dados: ATUACAPES (Elasticsearch) + Oracle</p> <p>Dados: ATUACAPES (Elasticsearch) + Oracle</p>
<p>Critérios: Minuta Técnica - Ranking AtuaCAPES | Clique em qualquer consultor para ver detalhes</p> <p>Clique em qualquer consultor para ver detalhes</p>
</footer> </footer>
</div> </div>
); );

View File

@@ -388,7 +388,7 @@ const ConsultorCard = memo(({ consultor, highlight, selecionado, onToggleSelecio
<div key={idx} className="list-item"> <div key={idx} className="list-item">
<span className="badge">{aval.codigo}</span> <span className="badge">{aval.codigo}</span>
<span className="pontos">{PONTOS_BASE[aval.codigo] || 0} pts</span> <span className="pontos">{PONTOS_BASE[aval.codigo] || 0} pts</span>
<span>{aval.premio}</span> <span>{aval.nome_comissao || aval.premio}</span>
<span className="muted">{aval.ano}</span> <span className="muted">{aval.ano}</span>
</div> </div>
))} ))}