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

# Escrevendo uma ontologia

> A estrutura do arquivo YAML — nós, arestas, normalizadores e identificadores com nome próprio — campo a campo, com um exemplo completo que roda.

O arquivo tem três blocos: `nodes`, `edges` e, opcionalmente, `timeline`. Esta página cobre os dois primeiros, que é onde está toda a modelagem.

Antes de escrever, leia [Conceitos da ontologia](/admin/ontology-concepts) — sobretudo a diferença entre `id_field` e `er_fields`.

***

## Um nó

```yaml theme={null}
nodes:
  - label: Cliente                 # o tipo. PascalCase, singular.
    source: clean/crm_contatos     # a tabela da clean. Sempre começa com clean/
    connector: postgres            # de qual conector veio (ícone na UI)
    connector_instance: crm_matriz # QUAL instância — dois CRMs não se misturam

    id_field: external_id          # cunha o id
    er_fields: [cpf, email]        # acha quem já existe
    normalizers: {matricula: text} # opcional — a regra de formato por campo
    identifier_columns: {}         # opcional — quando a coluna tem outro nome

    properties:                    # as colunas que viram propriedade do nó
      - external_id
      - nome
      - cpf
      - email
```

### Campo a campo

| Campo                              | Obrigatório | O que faz                                                                                                                                                                      |
| ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `label`                            | sim         | o tipo da entidade                                                                                                                                                             |
| `source`                           | sim         | a tabela da clean                                                                                                                                                              |
| `id_field`                         | sim         | a chave que cunha o id. Use a chave que o sistema já tem e que **não muda** — normalmente `external_id`. Nunca um campo que o usuário edita.                                   |
| `er_fields`                        | não         | os identificadores. Sem eles, o nó é local à sua fonte, e fica no caminho de ingestão mais rápido.                                                                             |
| `properties`                       | sim         | tudo que você quer ver no nó. Precisa conter o `id_field` e as colunas dos `er_fields`.                                                                                        |
| `indexed_fields`                   | não         | as propriedades pelas quais se **filtra** (`cor`, `status`, `ano`). Cria índice no grafo. Não identificam nada — ver [Campos pesquisáveis](/admin/ontology-searchable-fields). |
| `normalizers`                      | não         | a regra de normalização por campo                                                                                                                                              |
| `identifier_columns`               | não         | quando esta fonte chama o identificador de outra coisa                                                                                                                         |
| `connector` e `connector_instance` | recomendado | procedência; a instância é o que impede dois sistemas iguais de se fundirem                                                                                                    |

***

## Normalizadores

A regra sai do **nome do campo**. `cpf` vira só dígitos porque se chama `cpf`.

```yaml theme={null}
er_fields: [cpf, matricula_imovel, placa]
normalizers:
  matricula_imovel: text    # "M-123456", " m-123456 " → "m-123456"
```

| Nome              | O que faz                       | Exemplo                              |
| ----------------- | ------------------------------- | ------------------------------------ |
| `cpf`, `cnpj`     | só dígitos                      | `111.222.333-44` → `11122233344`     |
| `email`           | minúscula, tira o `+alias`      | `Ana+x@X.COM` → `ana@x.com`          |
| `phone`           | E.164                           | `(11) 99999-9999` → `+5511999999999` |
| `digits`          | só dígitos, qualquer campo      | `RG 12.345.678-9` → `123456789`      |
| `alnum`           | letras e dígitos, minúscula     | `M-123.456` → `m123456`              |
| `upper` e `title` | caixa alta, capitalizado        | `ana souza` → `Ana Souza`            |
| `text`            | **o padrão** — trim e minúscula | ` M-123456` → `m-123456`             |
| `verbatim`        | só trim                         | preserva a caixa                     |

<Warning>
  O padrão `text` **mantém a pontuação**. Uma coluna chamada `documento` guardando CPF cai no padrão, e `111.222.333-44` vira uma entidade diferente de `11122233344`.

  Declare a regra: `normalizers: {documento: cpf}`. O worker avisa no log quando detecta isso, mas é melhor declarar de cara.
</Warning>

***

## Quando a coluna tem outro nome

Uma fonte estrangeira chama o CPF de `ID_BRAZIL`. Sem dizer nada, ela chaveia como `id_brazil` e **nunca encontra** quem chama de `cpf`:

```yaml theme={null}
- label: Cliente
  source: clean/erp_estrangeiro
  id_field: external_id
  er_fields: [cpf]                       # o NOME é o que faz convergir
  identifier_columns: {cpf: ID_BRAZIL}   # a COLUNA é local desta fonte
  properties: [external_id, full_name, ID_BRAZIL]
```

A chave é o nome do identificador; o valor é a coluna desta fonte. O normalizador continua seguindo o **nome** — `ID_BRAZIL` é tratado como CPF porque é um CPF.

***

## Várias fontes, um label

É o caso normal: o mesmo conceito visto por vários sistemas. Repita o label, mude o `source`.

```yaml theme={null}
- label: Cliente
  source: clean/crm
  id_field: external_id
  er_fields: [cpf, email]        # o CRM tem e-mail
  properties: [external_id, nome, cpf, email]

- label: Cliente
  source: clean/erp
  id_field: external_id          # outra chave, outro namespace — tudo bem
  er_fields: [cpf, telefone]     # lista DIFERENTE — basta compartilhar o cpf
  properties: [external_id, nome, cpf, telefone]

- label: Cliente
  source: clean/rh
  id_field: external_id
  er_fields: [cpf, matricula]
  properties: [external_id, nome, cpf, matricula]
```

As listas **não precisam ser iguais**. Precisam formar uma **cadeia**: folha de pagamento e mailing podem não ter nada em comum, desde que o ERP conheça os dois.

O que é recusado é o conjunto **desconexo** — duas ilhas sem caminho entre si não são uma entidade, são duas.

***

## Uma aresta

```yaml theme={null}
edges:
  - type: POSSUI_VEICULO        # VERBO, maiúsculas com underscore
    source: clean/detran        # a tabela que tem AS DUAS colunas
    from:
      label: Cliente
      match_field: cpf
      source_column: cpf_proprietario
    to:
      label: Carro
      match_field: placa
      source_column: placa
```

### Por vários campos

Quando metade das linhas tem um identificador e a outra metade tem outro:

```yaml theme={null}
    from:
      label: Cliente
      match_field:   [cpf, email]
      source_column: [titular_cpf, titular_email]
```

Tenta na ordem; vence o primeiro que **realmente resolve**.

### `join_key` em vez de `match_field`

Quando a coluna aponta direto para o `id_field` do outro lado:

```yaml theme={null}
    to:
      label: Contrato
      join_key: id
```

***

## Exemplo completo que roda

Uma pessoa vista por três sistemas, com o carro dela:

```yaml theme={null}
version: "1"

nodes:
  - label: Cliente
    source: clean/sim_crm
    connector: postgres
    connector_instance: sim_crm
    id_field: external_id
    er_fields: [cpf, email]
    properties: [external_id, nome, cpf, email]

  - label: Cliente
    source: clean/sim_rh
    connector: postgres
    connector_instance: sim_rh
    id_field: external_id
    er_fields: [cpf, matricula]
    normalizers: {matricula: text}
    properties: [external_id, nome, cpf, matricula]

  - label: Carro
    source: clean/sim_detran
    connector: postgres
    connector_instance: sim_detran
    id_field: external_id
    er_fields: [placa, chassi, renavam]
    properties: [external_id, placa, chassi, renavam, modelo, cpf_proprietario]

  - label: Carro
    source: clean/sim_seguradora
    connector: postgres
    connector_instance: sim_seguradora
    id_field: external_id
    er_fields: [placa]              # a seguradora só tem a placa — e basta
    properties: [external_id, placa, cor, ano]

edges:
  - type: POSSUI_VEICULO
    source: clean/sim_detran
    from: {label: Cliente, match_field: cpf,   source_column: cpf_proprietario}
    to:   {label: Carro,   match_field: placa, source_column: placa}
```

Resultado: o CPF em três formatos vira **uma** pessoa; a placa em duas caixas vira **um** carro, com o chassi do DETRAN e a cor da seguradora no mesmo nó.

***

## Aplicando

```bash theme={null}
POST /v1/ontology            # salva uma versão nova (não ativa)
POST /v1/ontology/validate   # confere sem salvar
POST /v1/ontology/apply      # ativa  { "id": 12, "reset": false }
```

<Warning>
  `reset: true` **apaga o grafo inteiro** antes de aplicar. Não é o caminho normal.
</Warning>

Para o dado que já existe ficar achável pelos identificadores novos, é preciso um re-scan: `POST /v1/memory/full-refresh`.

O procedimento completo, com os estados de versão e como reverter, está em [Gerenciar a ontologia](/admin/manage-ontology).

***

## O que não vai aqui

**Nada de visualização.** Não existe `display_field`, e `display_tier` e `display_color` estão depreciados — aceitos e ignorados. O rótulo do nó é derivado (um campo de nome, senão o primeiro `er_field`, senão o `id_field`) e a cor sai do label. Duas pessoas lendo a mesma ontologia podem querer coisas diferentes na tela; isso não é do negócio.

**Nada de transformação.** Limpar, converter, juntar colunas — camada clean, em dbt. Se você precisa de um campo composto, crie a coluna lá e declare ela aqui.

<CardGroup cols={2}>
  <Card title="Campos pesquisáveis" icon="magnifying-glass" href="/admin/ontology-searchable-fields">
    Por que você não acha uma coisa, e o que declarar para achar.
  </Card>

  <Card title="Padrões e armadilhas" icon="triangle-exclamation" href="/admin/ontology-patterns">
    A regra de ouro, os erros que já aconteceram e o checklist antes do apply.
  </Card>
</CardGroup>
