//Receita

Dizer se dois registros são o mesmo

Unir dois cadastros por engano é o erro caro. Por isso a decisão precisa de três saídas, e não de duas.

Duas bases descrevem o mesmo mundo com palavras diferentes. O cadastro que veio da aquisição tem “Ind. e Com. de Alimentos Silva Ltda”; o seu tem “Alimentos Silva”. São a mesma empresa? Às vezes sim, às vezes é a filial, às vezes é outra empresa da mesma família.

Uma primeira passagem barata já reduziu milhões de combinações a algumas centenas de pares que merecem olhar. O que falta é o julgamento de cada par.

Por que a solução ingênua falha

A tentação é tratar isso como problema de similaridade de texto: distância de edição, trigramas, embeddings. Funciona para erro de digitação e falha no que interessa — “Cervejaria Bamberg” e “Bamberg Cervejaria Artesanal” são parecidas e podem ser a mesma; “Brahma” e “Brahma Duplo Malte” são parecidíssimas e são produtos diferentes.

O segundo problema é mais grave e é de arquitetura: unir por engano é o erro caro. Depois de unir, cada fato de qualquer um dos dois passa a descrever o registro unido, e tudo que apontava para um dos dois vem junto. Desfazer exige descobrir de onde veio cada campo. Já deixar de unir só deixa uma duplicata no sistema.

Uma decisão com duas saídas — unir ou não unir — força a escolher entre dois erros assimétricos com um número só. Faltando a terceira saída, ou você une demais ou duplica demais.

O desenho das perguntas

A receita usa um Score cujos três níveis são as três coisas que se pode fazer com um par:

from typesafe_sdk import Noul, Score

NIVEIS = [
    "Descrevem dois produtos diferentes.",
    "Descrevem produtos muito próximos, que podem ou não ser o mesmo: "
    "uma variante, uma edição especial, ou um nome que poderia se referir a qualquer um dos dois.",
    "Descrevem um único e mesmo produto.",
]
DESTINO = {0: "deixar separado", 1: "fila do curador", 2: "registrar como o mesmo"}

PERGUNTAS = {
    "relacao": Score(
        instructions="Como as duas descrições se relacionam como produtos?",
        criteria=NIVEIS,
    ),
    "mesmo_nome": Noul(instructions="As duas entidades informam o mesmo nome de produto?"),
    "mesma_fabricante": Noul(instructions="As duas entidades são da mesma fabricante?"),
    "mesmo_estilo": Noul(instructions="As duas entidades descrevem o mesmo estilo?"),
}

def destino(nota: float) -> str:
    return DESTINO[min(int(nota + 0.5), len(NIVEIS) - 1)]

Três decisões de projeto merecem registro.

Não existe limiar para calibrar. A regra é arredondar para o nível mais próximo. Os cortes ficam em 0,5 e 1,5 por consequência da régua, não por ajuste — e você consegue escrever as três descrições antes de ver a primeira nota, o que nunca é verdade para um número calibrado em dados.

O nível do meio é o que exige redação cuidadosa. Ele é quem manda casos para gente. No experimento, ele cobre variantes, edições especiais e nomes que poderiam se referir a qualquer um dos dois.

As perguntas de campo servem ao curador, não à decisão. Elas viajam na mesma requisição e só são lidas quando a nota cai no meio — aí dizem em qual campo as duas fontes divergem. E um campo fica de fora de propósito: o teor alcoólico, que é número, e comparar números é código.

O que o experimento oficial mediu

O cookbook usa o conjunto Beer da coleção Magellan: dois catálogos de cerveja raspados de sites diferentes, já reduzidos a 450 pares candidatos. O texto foi deixado como publicado, com entidades HTML não convertidas e apóstrofos quebrados — ou seja, sujo como base real. Números publicados pela TypeSafe, com o jev-1.12, em 11 de agosto de 2026:

DestinoParesFração
Registrar como o mesmo408,9%
Fila do curador5011,1%
Deixar separado36080,0%

Quatro pares individuais mostram a régua funcionando:

  • Mesmo produto, escrito diferente: nota 1,94 com confiança 0,92. As duas fontes nomeavam a mesma cerveja com estilos escritos de formas diferentes (“American Barleywine” e “Barley Wine”).
  • Produtos diferentes: nota 0,03 com confiança 0,95. Nome, fabricante e estilo todos divergentes.
  • Estilo divergente, resto igual: nota 1,30 com confiança 0,27 — vai para o curador. As perguntas de campo explicam por quê: nome 0,95 e fabricante 0,94, mas estilo 0,35. O curador recebe o caso sabendo exatamente onde olhar.
  • Variante do mesmo produto: nota 1,10 com confiança 0,77, para uma cerveja e a sua versão com frutas. Nome 0,63: parecido, não igual. Curador.

Um detalhe que o cookbook explica e vale entender: as notas não se acomodam nos números inteiros. A maioria fica perto de 0,25, porque duas cervejas sem nada em comum ainda compartilham vocabulário de estilo, e o modelo dá alguma probabilidade ao nível do meio em vez de zero. O que decide o par é de que lado do corte ele cai — quão perto ele está de um nível não entra na conta. E os dois cortes não são igualmente disputados: 9 pares ficaram a menos de 0,1 do corte superior, que decide o que entra no grafo, contra 47 perto do corte inferior, que só decide se um curador vê o par.

O que muda em português

O conjunto é em inglês, e a adaptação brasileira tem particularidades reais:

Razão social contra nome fantasia. “Ind. e Com. de Bebidas Aurora Ltda” e “Cervejaria Aurora” são a mesma empresa com duas caras. Vale descrever isso no nível do meio ou, melhor, resolver antes com o CNPJ — que é campo, não julgamento.

Abreviação nossa. “Av.”, “R.”, “Jd.”, “Cia.”, “S/A” e o eterno “LTDA” com e sem ponto. Isso é normalização de texto: código.

Filial e matriz. No Brasil, mesma razão social com CNPJ de filial diferente é caso clássico de “relacionado, não igual” — exatamente o nível do meio.

Como a TypeSafe declara que o inglês é a língua principal de treino, teste as três descrições com os seus próprios pares antes de deixar a união rodar sozinha.

Onde isso quebra

  • Aritmética. Comparar valores, quantidades, datas de fundação: código. Está entre os limites declarados.
  • Custo por par. Uma requisição por par candidato, então o gasto acompanha o tamanho da lista que a primeira passagem entregou, não o tamanho das bases. Aperte a primeira passagem antes de aumentar o rigor aqui.
  • Registro grande no state. Mande os campos que decidem, não a ficha inteira.
  • Fila do curador sem gente. A terceira saída só vale se alguém realmente olhar. Sem isso, você tem os mesmos dois erros de antes, com uma fila no meio.

Quando a incerteza deve virar uma resposta mais genérica em vez de fila humana, a receita companheira é classificar com confiança. O índice está em receitas.

Perguntas frequentes

Por que Score e não Noul para decidir duplicata?

Porque a decisão tem três saídas e o Score deixa cada saída com um rótulo escrito: unir, deixar separado ou mandar ao curador. Um Noul chegaria lá por limiar, e um Choice perderia a ordem entre as três.

Que limiar eu preciso calibrar?

Nenhum. A decisão é arredondar a nota para o nível mais próximo, e os cortes saem da própria régua. O que você escreve são as três descrições — e pode escrevê-las antes de ver a primeira nota, o que não vale para um número calibrado.

Como o experimento distribuiu os 450 pares?

360 pares (80,0%) ficaram separados, 40 (8,9%) foram para união e 50 (11,1%) para o curador, segundo os números publicados pela TypeSafe.

Para que servem as perguntas de campo?

Para dizer ao curador onde as duas fontes divergem. Elas viajam na mesma requisição e só são lidas quando a nota cai no nível do meio: nome, cervejaria e estilo, no experimento.

Por que o teor alcoólico não tem pergunta?

Porque comparar dois números é aritmética, e aritmética fica no código. É a mesma fronteira que a documentação declara ao listar matemática entre os modos de falha do jev-1.13.

Quer dominar decisões com IA em português? O curso da comunidade está em pré-venda.

Garantir pré-venda por R$ 499,00

Pré-venda: R$ 499,00 · Após o lançamento: R$ 799,00