GET /api/admin/jobs/:id/candidates calcula un score por candidato y devuelve el pool ordenado. El score se compone de razones (reasons) independientes que se suman; cada razón trae su etiqueta y sus puntos, así que la respuesta explica por qué un candidato quedó donde quedó, no solo el número final.
Esta página documenta el mecanismo tal como está implementado en
src/lib/match-score-shared.ts. Los pesos son constantes reales del código (MATCH_WEIGHTS), no una estimación.Factores y pesos
Estos valores son los de la constante
MATCH_WEIGHTS en src/lib/match-score-shared.ts:
Geografía
La ciudad y el barrio se comparan normalizados (minúsculas, sin tildes, sin espacios sobrantes) para que variaciones de escritura no rompan la coincidencia. Si el candidato no está en la misma ciudad del Job, se revisa si declaró esa ciudad entre susmunicipalities (los municipios que él mismo dijo cubrir, no una cobertura inferida por defecto). Si tampoco, la razón de geografía suma 0 puntos pero igual aparece en la respuesta (“Fuera de cobertura”).
Reputación
No se usa el promedio de calificación en bruto: se calcula una media bayesiana con un prior depriorRating: 4 y peso priorWeight: 3. La fórmula es:
bayes cae exactamente en priorRating (4 sobre 5): un camello nuevo no queda enterrado por no tener historial, pero tampoco una única reseña de 5 estrellas lo dispara al tope. Esa media bayesiana se escala contra el máximo de rating (5) y se multiplica por el peso rating (25): points = round((bayes / 5) * 25).
Carga
load son los trabajos activos del candidato. Los puntos son max(0, 20 - 7 * load): 20 puntos si está libre, 13 con un trabajo activo, 6 con dos, y 0 a partir de tres — nunca negativo.
Premium
Si el candidato espremium, se suma un bono fijo de 15 puntos como una razón adicional (kind: "premium"). Si no lo es, esa razón simplemente no aparece en la lista de motivos.
Desempate
El score es la suma de todas las razones aplicables. Cuando dos candidatos empatan en score,rankCandidates desempata en este orden: más reseñas (reviewCount) primero, luego orden alfabético del nombre (localeCompare en español), y por último el id como desempate final y determinista.
El endpoint
GET /api/admin/jobs/:id/candidates devuelve { items }: el pool de candidatos para el servicio de ese Job, ya ordenado por score, con los motivos (reasons) de cada puntaje incluidos por candidato. Acepta limit de 1 a 50 (default 20) y responde 404 si el trabajo no existe.