Cypher Recommender
Índice
O que é
O recommender-cypher é o serviço que implementa o MVP do sistema de recomendação do sistema. O serviço calcula a compatibilidade entre um currículo e as oportunidades disponíveis no grafo de conhecimento utilizando o Índice de Jaccard, retornando uma lista ranqueada de oportunidades compatíveis com o perfil do usuário.
O serviço foi projetado para ser substituível. Caso a abordagem de recomendação evolua para modelos mais sofisticados (como GNN), a arquitetura permite que os serviços rodem de forma independente e em paralelo, sendo possível, futuramente, rodar o cypher e o GNN em paralelo e comparar os resultados.
Como Funciona
Eventos processados pelo serviço:
| Direção | Evento | Campos |
|---|---|---|
| Consumidor | RecommendationRequested | resume_id, request_id, top_n |
| Produtor | RecommendationProduced | resume_id, request_id, top_n, total_candidates, recommendations[] |
Escolhas de Implementação
G0 sem traversal hierárquico da ESCO: o recomendador opera exclusivamente sobre relacionamentos diretos (Resume)-[:HAS_SKILL]->(Skill)<-[:REQUIRES_SKILL]-(Opportunity), sem utilizar relações [:BROADER] ou [:NARROWER]. Um currículo com "React" não realiza correspondência com uma vaga que requer "JavaScript", ainda que React implique JavaScript na ontologia da ESCO. Esta limitação é definida no MVP para manter a implementação simplificada e auditável. Entretando aproveitamos essas relações no Personalized PageRank.
Query única Cypher via inclusão-exclusão: o cálculo de interseção, união e percentual é executado em uma única consulta, sem o uso de plugins externos (como APOC). A aplicação do princípio da inclusão-exclusão () evita operações explícitas de conjunto e permite a execução nativa no Neo4j.
Query Cypher
O cálculo de interseção, união e percentual é executado em uma única consulta Cypher, sem APOC e sem processamento em memória no serviço executor:
MATCH (r:Resume {id: $resume_id})-[:HAS_SKILL]->(s:Skill)
WITH r, COLLECT(DISTINCT s) AS resumeSkills
UNWIND resumeSkills AS rs
MATCH (o:Opportunity)-[:REQUIRES_SKILL]->(rs)
WITH r, o, resumeSkills, COLLECT(DISTINCT rs) AS sharedSkills
MATCH (o)-[:REQUIRES_SKILL]->(os:Skill)
WITH o, resumeSkills, sharedSkills, COLLECT(DISTINCT os) AS oppSkills
WITH o,
SIZE(sharedSkills) AS intersectionSize,
SIZE(resumeSkills) + SIZE(oppSkills) - SIZE(sharedSkills) AS unionSize,
sharedSkills,
oppSkills,
resumeSkills
WHERE unionSize > 0
RETURN o.id AS opportunity_id,
o.title AS title,
o.company AS company,
toFloat(intersectionSize) / unionSize * 100 AS match_percentage,
[sk IN sharedSkills | sk.name] AS shared_skills,
[sk IN oppSkills | sk.name] AS required_skills,
[sk IN resumeSkills | sk.name] AS resume_skills,
SIZE(sharedSkills) AS matched_count,
SIZE(oppSkills) AS required_count
ORDER BY match_percentage DESC
LIMIT $top_n
O cálculo da união utiliza o princípio da inclusão-exclusão:
Isso evita a necessidade de operações explícitas de conjunto.
Estrutura da Recomendação
Cada item da lista recommendations[] no evento RecommendationProduced contém:
| Campo | Tipo | Descrição |
|---|---|---|
opportunity_id | string | Identificador da vaga no sistema |
position | string | Título do cargo |
company | string | Nome da empresa |
match_percentage | float | Compatibilidade (0–100) |
reasoning.message | string | Explicação sobre como foi calculada a compatibilidade para exibição na interface |
reasoning.matched_skills | int | Número de habilidades em comum (intersecção) |
reasoning.total_skills | int | Total de habilidades únicas consideradas (união) |
A estrutura reasoning é agnóstica ao modelo matemático. Dessa forma, futuras evoluções no algoritmo não exigirão alterações na interface do usuário.
ADR's Relacionadas
| ADR | Data | Decisão |
|---|---|---|
| ADR-030 | Mar 2026 | Implementação do MVP com Jaccard via Cypher (substituição do algoritmo Java por soma de pesos) |