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.
Disponibilidad
Si el Job no tienescheduledStart, esta razón no aparece y el score queda igual que sin agenda.
Si la tiene, se cruza con CamelloAvailability en hora de Colombia. La cita dura 60 minutos y tiene que caber entera dentro de una franja del mismo día de la semana:
- franja que cubre la hora y el hueco está libre: 10 puntos, «Disponible en la cita»;
- franja que no cubre esa hora: 0 puntos, «Fuera de horario»;
- el camello no publicó franjas: 0 puntos, «Sin horario»;
- otro trabajo distinto de
CANCELADOsolapa el intervalo: 0 puntos, «Ocupado en la cita».
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. Cada ítem trae también city, activeJobs y categories (las categorías de los servicios que ofrece). La tarjeta del admin recorta esa lista ya rankeada por categoría, ciudad o carga; el tope y el orden no cambian. Acepta limit de 1 a 50 (default 20) y responde 404 si el trabajo no existe.