> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strattum.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Padrões e armadilhas

> A regra de ouro do er_fields, o teste de uma pergunta, os seis padrões, as armadilhas que já aconteceram de verdade e o checklist antes de ativar.

Esta página é a última antes de ativar. Ela concentra o que dá errado e como evitar.

***

## A regra de ouro

**`er_fields` é OU, nunca E.**

`[cpf, email]` significa *"acha por CPF **ou** por e-mail"*. Não existe chave composta — cada campo é uma **porta independente** para a mesma entidade, e basta uma abrir.

```text theme={null}
linha 1:  {id: CRM-001, cpf: 111.222.333-44, email: ana@x.com}
          → grava cpf 11122233344 → entidade A
          → grava email ana@x.com → entidade A

linha 3:  {id: MKT-9, cpf: 999.888.777-66, email: ana@x.com}
          → cpf não achou
          → email ACHOU a entidade A → adota
```

CPF completamente diferente, e mesmo assim virou a mesma pessoa. **Não existe votação.**

A consequência que morde: troque `ana@x.com` por `contato@empresa.com.br` — uma caixa de setor — e todo mundo que a compartilha vira uma pessoa só.

> [!WARNING]
>
> **A força da sua identidade é o campo mais FRACO da lista, não o mais forte.** Cada campo novo é mais uma maneira de errar, e o mais permissivo é quem manda.

### E se eu quiser a combinação?

Não dá na ontologia. Fabrique a coluna composta na camada clean e declare ela:

```sql theme={null}
cpf || '|' || lower(email)  AS cpf_email
```

```yaml theme={null}
er_fields: [cpf_email]
```

***

## O teste de uma pergunta

Antes de colocar um campo em `er_fields`:

> Se duas linhas tiverem o mesmo valor **neste campo**, elas são obrigatoriamente a mesma coisa no mundo real?

Se a resposta for *"não, só se o outro campo também bater"*, esse campo **não entra**.

Na dúvida, deixe de fora. Perder uma fusão é recuperável — basta declarar depois, e o lookup primário-primeiro garante que nada é re-chaveado. Colapsar mil entidades exige limpar o índice e reprocessar.

***

## Os seis padrões

1. **`id_field` é a chave que a fonte já tem e não muda** — normalmente `external_id`. Nunca um campo que o usuário edita.
2. **`er_fields` é só o que passa no teste** acima.
3. **`normalizers` quando o nome do campo não entrega a regra.** `cpf`, `cnpj`, `email`, `phone` e `telefone` são reconhecidos pelo nome; todo o resto cai em `text`.
4. **Fontes podem declarar listas diferentes** — cada uma declara só o que tem. O que não pode é o conjunto ficar desconexo.
5. **Sem elo em comum, não force.** Duas fontes sem identificador compartilhado não são uma entidade. Modele separado e resolva o dado na origem.
6. **Na aresta, `source:` é a tabela que carrega a ligação** — a que tem as duas colunas.

***

## Armadilhas que já aconteceram

### 1. Campo repetido em `er_fields`

Um gerador de teste produziu 20 000 veículos com só 9 000 placas distintas, porque o `lpad` truncava a partir de um certo índice. Resultado: **11 000 veículos fundiram em silêncio**, cada linha sobrescrevendo as propriedades da anterior, e o run reportou sucesso.

Hoje o worker avisa quando uma fonte colapsa acima de 20%, mas o modelo errado continua sendo de quem escreveu a ontologia.

### 2. Tratar `er_fields` como chave composta

Tentar casar reunião por "horário mais responsável":

```yaml theme={null}
er_fields: [meeting_at, owner_email]   # errado
```

Como é OU, isso funde **todas as reuniões do mesmo responsável numa só**.

### 3. Mesmo label, duas fontes, nenhum `er_fields`

Passa na validação e parece certo. Mas sem identificador declarado cada fonte cunha no seu próprio namespace, e você fica com **duas populações separadas usando o mesmo nome** — o card da ontologia mostra `Reunião (247)` e metade é de um conector, metade de outro, sem nenhuma aresta entre elas.

Aconteceu de verdade com `Meeting` vindo de um CRM e de uma ferramenta de gravação de calls.

### 4. Identificador sem o normalizador que precisa

Uma coluna chamada `documento` guardando CPF cai no padrão `text`, que **mantém a pontuação**. As quatro fontes escrevem quatro strings diferentes, viram quatro linhas no índice e **quatro entidades**. A busca depois acha uma delas, então o sintoma parece bug de busca.

O worker avisa:

```text theme={null}
Label Cliente: the identifier 'documento' has values that are the same once the
separators are removed but were keyed apart — '111.222.333-44' and '11122233344'.
[...] Declare the rule explicitly, e.g. normalizers: {documento: digits}
```

### 5. `digits` onde o identificador tem letra

O RG brasileiro pode terminar em **X**. Com `digits`:

```text theme={null}
12.345.678-X  →  12345678
1.234.567-8   →  12345678     ← RGs diferentes, a MESMA chave
```

Use **`alnum`** para qualquer identificador que possa conter letra: RG, placa Mercosul, chassi, códigos alfanuméricos de ERP.

### 6. Declarar `er_fields` só para "ficar pesquisável"

Se você só quer achar pelo valor do próprio `id_field`, **isso já é de graça** — e declarar tira o nó do caminho de ingestão mais rápido sem ganho nenhum. Ver [Campos pesquisáveis](https://docs.strattum.ai/admin/ontology-searchable-fields.md).

### 7. Nó vindo de uma tabela de junção

Um nó precisa vir de uma tabela com **uma linha por id**. Se o nó sai de uma tabela de junção (a que liga cliente e produto, por exemplo), as cargas incrementais perdem arestas. Modele uma tabela de dimensão para o nó e deixe a tabela de junção só como `source:` da aresta.

### 8. `updated_at` que não é a hora da gravação

A leitura incremental do grafo só vê linhas com `updated_at` novo. Uma linha inserida depois com uma data de negócio antiga fica invisível. Veja [Carga incremental](https://docs.strattum.ai/data-catalog/overview.md#carga-incremental-aba-carga).

***

## Checklist antes do apply

* [ ] cada campo de `er_fields` passa no teste da pergunta — **sozinho**
* [ ] cada campo de `er_fields` (ou a coluna do `identifier_columns`) está em `properties`
* [ ] as fontes do mesmo label formam uma cadeia conectada — nenhuma ilha
* [ ] campo com formato variável tem `normalizers` declarado
* [ ] identificador que pode ter letra usa `alnum`, não `digits`
* [ ] a tabela do `source:` de cada aresta tem **as duas** colunas de ligação
* [ ] cada nó vem de uma tabela com **uma linha por id**
* [ ] nenhum campo de visualização — se você escreveu `display_*`, apague
* [ ] depois de rodar: a queda no número de entidades é **a que você esperava**

Uma queda maior que a prevista é colapso, não fusão.

***

## Ontologias que não funcionam

| O que alguém tenta | O que acontece |
| - | - |
| duas fontes do mesmo label sem nada em comum | erro no apply — não é uma entidade, são duas |
| `er_fields` com coluna que não está em `properties` | erro no apply — a coluna não é lida |
| `normalizers` num campo que não é identificador | erro no apply — ficaria decorativo |
| `identifier_columns` apontando para coluna inexistente | erro no apply |
| `match_field` numa coluna que o outro lado não tem | a aresta não resolve; o contador `zero_match` sobe |
| `er_fields: [status]` | funde por categoria — tudo que é "aberto" vira uma entidade |

O que a plataforma **não** pega: dois identificadores legítimos que por acaso colidem, e um campo que é único hoje e deixa de ser amanhã. Isso é modelagem, e só quem conhece o negócio decide.

> **[Gerenciar a ontologia](https://docs.strattum.ai/admin/manage-ontology.md)**
>
> Salvar, validar, ativar e reverter uma versão sem derrubar o grafo.

> **[Conceitos da ontologia](https://docs.strattum.ai/admin/ontology-concepts.md)**
>
> Voltar ao começo da trilha.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.