Índice de Jaccard

Índice


O que é

O Índice de Jaccard mede a similaridade entre dois conjuntos como a razão entre sua interseção e sua união:

J(A,B)=ABABJ(A, B) = \frac{|A \cap B|}{|A \cup B|}
  • J = 0 → nenhuma habilidade em comum
  • J = 1 → conjuntos idênticos (compatibilidade perfeita)

No Kairos, A representa o conjunto de habilidades de um currículo e B o conjunto de habilidades requeridas por uma oportunidade. O resultado é expresso como match_percentage (0–100) na interface do sistema.

Propriedades do Jaccard

O Jaccard é simétrico (J(A, B) = J(B, A)) e naturalmente normalizado. Não exige treino de modelo, é interpretável em linguagem natural ("você possui 70% das habilidades desta vaga") e é penalizado tanto por habilidades faltantes no currículo quanto por excesso de requisitos na vaga.


Como Funciona

Por que Jaccard para Recomendação de Vagas

A escolha do Índice de Jaccard como métrica de compatibilidade para o MVP do sistema de recomendação se baseia em quatro fatores:

1. Simplicidade,Interpretabilidade O Jaccard é uma métrica intuitiva, se adequanto ao objetivo de alinhamento de competências entre currículos e vagas, além de ser facilmente interpretável. Ela servirá de baseline para o desenvolvimento de uma abordagem baseada em GNN, permitindo comparações diretas de performance e interpretabilidade entre o modelo simples e o modelo avançado.

2. Natureza Conjuntual das Skills Habilidades são conjuntos por definição: não têm ordenação relevante, não se repetem e a pertinência (Python ∈ skills_do_currículo) é o que importa. O Jaccard modela exatamente essa semântica.

3. Grafo Já Estruturado para Isso A pipeline NLP do Kairos popula o Neo4j com os relacionamentos [:HAS_SKILL] (currículo possui habilidade) e [:REQUIRES_SKILL] (vaga requer habilidade), ambos apontando para nós (:Skill) da ESCO. A interseção A ∩ B é simplesmente o conjunto de nós (:Skill) apontados por ambos — calculável com uma única query Cypher.

4. Implementação Sem Dependências Externas O Jaccard é computável via Cypher puro usando o princípio da inclusão-exclusão para calcular a união sem precisar de plugins como APOC:

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

Implementação no recommender-cypher

O Subgrafo Utilizado (G0)

O recommender MVP opera exclusivamente sobre relacionamentos diretos entre currículos, habilidades e oportunidades — sem traversal hierárquico da ESCO (sem [:BROADER]/[:NARROWER]):

A Query Cypher

A query usa a fórmula de inclusão-exclusão em uma única passagem sobre o grafo:

MATCH (r:Resume)
WHERE r.raw_data_hash = $hash

MATCH (o:Opportunity)

OPTIONAL MATCH (r)-[:HAS_SKILL]->(s:Skill)<-[:REQUIRES_SKILL]-(o)
WITH r, o, count(s) AS intersection

WITH r, o, intersection,
     size([(r)-[:HAS_SKILL]->() | 1]) AS resume_skills,
     size([(o)-[:REQUIRES_SKILL]->() | 1]) AS opp_skills

WITH r, o, intersection,
     (resume_skills + opp_skills - intersection) AS union_size

WHERE union_size > 0

RETURN
  o.entity_id AS opportunity_id,
  o.position   AS position,
  o.company    AS company,
  round(toFloat(intersection) / union_size * 100, 2) AS match_percentage,
  intersection AS matched_skills,
  union_size   AS total_skills

ORDER BY match_percentage DESC
Single Query

Todo o cálculo de interseção, união e percentual acontece dentro de uma única query Cypher — sem loops N+1, sem APOC e sem processamento em memória na aplicação host.

Estrutura do Resultado

Cada recomendação retornada pelo recommender-cypher contém:

CampoTipoDescrição
opportunity_idstringIdentificador da vaga no sistema
match_percentagefloatCompatibilidade Jaccard em percentual (0–100)
reasoning.messagestringMensagem legível para exibição direta na UI
reasoning.matched_skillsintNúmero de habilidades em comum
reasoning.total_skillsintTamanho da união (total de habilidades únicas consideradas)

A estrutura reasoning foi projetada para ser agnóstica ao modelo matemático: quando o algoritmo de recomendação evoluir para abordagens mais sofisticadas (ex: ReMR baseado em RL), o front-end não precisará de adaptações.


Escopo por raw_data_hash

Cada execução da pipeline de processamento de currículo gera um identificador imutável chamado raw_data_hash — um hash SHA-256 calculado sobre os campos estáveis do currículo (excluindo timestamps).

Todas as queries de recomendação Jaccard são escopadas por esse hash, garantindo que o cálculo opere sempre sobre o snapshot mais recente e consistente do currículo:

WHERE r.raw_data_hash = $hash
Por que o hash importa

Se um usuário resubmeter o currículo (atualização de habilidades), o hash muda e um novo snapshot é criado. Isso evita que habilidades de processamentos anteriores contaminem as recomendações atuais — cada cálculo Jaccard reflete o estado exato das habilidades vinculadas naquele processamento.


Demonstração / Exemplos

Cálculo Passo a Passo

Contexto: currículo de um desenvolvedor sendo comparado a duas vagas distintas.

Habilidades do currículo (conjunto A): {Python, Docker, SQL, Git, React}

Vaga 1 — Alta Compatibilidade

Requisitos da vaga (conjunto B₁): {Python, SQL, JavaScript, Docker, Node.js}

A ∩ B₁ = {Python, SQL, Docker}                  → |∩| = 3
A ∪ B₁ = {Python, Docker, SQL, Git, React, JavaScript, Node.js} → |∪| = 7

J(A, B₁) = 3 / 7 ≈ 0.4286 → match_percentage = 42.86%
{
  "opportunity_id": "vaga-001",
  "match_percentage": 42.86,
  "reasoning": {
    "message": "Você possui 3 das 7 habilidades únicas desta vaga.",
    "matched_skills": 3,
    "total_skills": 7
  }
}

Vaga 2 — Baixa Compatibilidade

Requisitos da vaga (conjunto B₂): {Java, Spring Boot, Kubernetes, Terraform, AWS}

A ∩ B₂ = {}                                      → |∩| = 0
A ∪ B₂ = {Python, Docker, SQL, Git, React, Java, Spring Boot, Kubernetes, Terraform, AWS} → |∪| = 10

J(A, B₂) = 0 / 10 = 0.0 → match_percentage = 0.00%

O currículo não possui nenhuma skill em comum com a vaga 2, resultando em match_percentage = 0% — a vaga não aparecerá no topo das recomendações.

Efeito da Simetria

O Jaccard é simétrico: adicionar requisitos irrelevantes à vaga penaliza o score mesmo que o currículo seja forte nas skills relevantes.

Currículo: {Python, SQL}
Vaga A:    {Python, SQL}                          → J = 2/2 = 1.00 (100%)
Vaga B:    {Python, SQL, Java, Kotlin, Go, Rust}  → J = 2/6 = 0.33 (33%)

Ambos os currículos têm todas as skills que importam para a Vaga B, mas o Jaccard penaliza porque a union cresce com os requisitos não cobertos — comportamento intencional que desfavorece vagas com requisitos genéricos.


ADR's Relacionadas

ADRDataDecisão
ADR-030Mar 2026Adoção do Índice de Jaccard como métrica de compatibilidade do MVP; algoritmo anterior baseado em soma de pesos descartado por ineficiência em larga escala
ADR-0402026Estabelecimento do Jaccard Médio Global como indicador de estabilidade da pipeline de processamento