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çãoEventoCampos
ConsumidorRecommendationRequestedresume_id, request_id, top_n
ProdutorRecommendationProducedresume_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 (AB=A+BAB|A \cup B| = |A| + |B| - |A \cap B|) 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:

AB=A+BAB|A \cup B| = |A| + |B| - |A \cap B|

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:

CampoTipoDescrição
opportunity_idstringIdentificador da vaga no sistema
positionstringTítulo do cargo
companystringNome da empresa
match_percentagefloatCompatibilidade (0–100)
reasoning.messagestringExplicação sobre como foi calculada a compatibilidade para exibição na interface
reasoning.matched_skillsintNúmero de habilidades em comum (intersecção)
reasoning.total_skillsintTotal 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

ADRDataDecisão
ADR-030Mar 2026Implementação do MVP com Jaccard via Cypher (substituição do algoritmo Java por soma de pesos)