Schema do banco de dados
O corpus do IRIS Core é SQL plano (sem ORM): as migrações em
../db/migrations/ são a única fonte da verdade. Esta página é uma
referência legível por pessoas gerada a partir dessas migrações. O corpus é neutro — zero campos RMC
ou referências cruzadas (imposto por pnpm neutrality).
- Engine: PostgreSQL 16 +
pgvector (halfvec + HNSW), pg_trgm, fuzzystrmatch, unaccent,
busca full-text em espanhol.
- Ferramenta de migração:
dbmate (cada arquivo tem -- migrate:up / -- migrate:down).
Diagrama entidade–relacionamento
erDiagram
instancia ||--o{ instancia_clase : "classes"
instancia ||--o{ instancia_persona : "owners/agents"
instancia ||--o{ anotacion : "events"
instancia |o--o| instancia : "renewal chain"
persona ||--o{ instancia_persona : "role"
clase ||--o{ instancia_clase : ""
estado ||--o{ instancia : "status"
tipo_signo ||--o{ instancia : "sign type"
tipo_anotacion ||--o{ anotacion : "type"
pais ||--o{ persona : "country"
raw_source ||--o{ dead_letter : "re-derive handle"
consumer ||--o{ api_key : "owns"
Corpus (entidades neutras)
instancia — a solicitação de marca
A entidade central. Chave natural nro_solicitud (D-01); denominacion é um atributo, nunca uma
chave (D-02). Enriquecida de forma autoritativa pelo Buscador; semeada (somente nulls) pelo Sheets.
| Coluna |
Tipo |
Notas |
nro_solicitud |
text |
PK — chave natural |
nro_registro |
text |
aparece somente na concessão, nullable |
denominacion |
text |
NOT NULL — o texto da marca |
tipo_signo |
text |
→ tipo_signo(codigo) |
estado |
text |
→ estado(codigo) |
tipo_nombre, subtipo_nombre |
text |
tipo/subtipo do INAPI (D-14) |
fecha_presentacion, fecha_publicacion, fecha_registro, fecha_vencimiento |
date |
as quatro datas explícitas (D-14) |
traduccion, descripcion_etiqueta, protection_description, imagen_url |
text |
atributos de detalhe (D-14) |
renovada_de, renovada_por |
text |
auto-FK → instancia(nro_solicitud) — cadeia de renovação (D-17) |
embedding |
halfvec(1024) |
vetor semântico, índice HNSW |
search_vector |
tsvector |
gerada — FTS em espanhol sobre unaccent(denominacion) |
content_hash |
text |
idempotência (INGEST-05) |
created_at, updated_at |
timestamptz |
|
persona — titulares e agentes
| Coluna |
Tipo |
Notas |
id |
bigint |
PK (identity) |
pais |
text |
→ pais(codigo) |
identificador |
text |
RUT (Chile) ou id estrangeiro, nullable |
nombre, apellido |
text |
|
region, comuna |
text |
opcional (D-16) |
search_vector |
tsvector |
gerada, FTS |
created_at |
timestamptz |
|
Chave única uq_persona_pais_identificador (pais, identificador) onde identificador IS NOT NULL;
personas sem id são reconciliadas por melhor esforço via (pais, lower(nombre)).
instancia_persona — ponte de papéis
| Coluna |
Tipo |
Notas |
instancia_nro_solicitud |
text |
PK, → instancia |
persona_id |
bigint |
PK, → persona |
rol |
text |
titular | representante | ambas |
instancia_clase — classes de Nice
| Coluna |
Tipo |
Notas |
instancia_nro_solicitud |
text |
PK, → instancia |
clase_numero |
int |
PK, → clase (1..45) |
descripcion, estado |
text |
descrição/status por classe |
anotacion — eventos do Estado-Diário (append-only)
| Coluna |
Tipo |
Notas |
id |
bigint |
PK (identity) |
instancia_nro_solicitud |
text |
→ instancia (um pai ausente é convertido em stub/colocado em quarentena) |
tipo |
text |
→ tipo_anotacion(codigo) — M1..M14 |
fecha, fecha_vencimiento |
date |
|
observacion, seccion, seccion_nombre |
text |
|
created_at |
timestamptz |
|
Chave de dedup uq_anotacion_event (instancia_nro_solicitud, tipo, fecha, md5(coalesce(observacion,'')))
— um evento repetido é um DO NOTHING limpo (eventos nunca sofrem mutação).
Catálogos
| Tabela |
PK |
Outros |
estado |
codigo |
nombre — conjunto controlado de status |
tipo_signo |
codigo |
nombre |
tipo_anotacion |
codigo |
seccion_nombre — M1..M14 |
pais |
codigo |
nombre — conjunto ISO completo + códigos descontinuados |
clase |
numero |
nombre — classes de Nice, CHECK (numero BETWEEN 1 AND 45) |
Um valor de catálogo desconhecido é aceito como null + registrado em log (D-09) — um novo valor do INAPI nunca interrompe
a ingestão.
Ingestão / operações
| Tabela |
Chave |
Finalidade |
sync_state |
source (PK) |
cursor de retomada por fonte (cursor jsonb) |
movement_log |
id |
feed de mudanças append-only (entity_type, entity_id, change_type, source, occurred_at, content_hash) — alimenta a lista de trabalho do Buscador |
raw_source |
id |
bytes brutos retidos (storage_path sob RAW_STORAGE_DIR, content_type, size) — re-derivabilidade (D-10) |
dead_letter |
id |
quarentena (source, reason, raw_source_id → raw_source, payload jsonb) — não abortante (D-07) |
sync_run |
id |
histórico de execução (source, started_at, finished_at, status ok\|failed\|running, processed, changed, quarantined, error) — OBS-03 |
Controle de acesso
| Tabela |
Chave |
Finalidade |
consumer |
id |
org/tenant (name UNIQUE) |
api_key |
id |
consumer_id → consumer; key_hash (sha256, UNIQUE — nunca em texto puro), prefix, scopes text[], monthly_quota int (>= 0), expires_at, rotated_at, revoked_at |
Escopos: brands:read (/v1/brands, MCP search_brands/get_brand_detail), insights:read
(/v1/freshness, /v1/sync-runs). Escopos vazios = irrestrito. Os contadores de cota vivem no Redis
(compartilhado REST ↔ MCP), não no Postgres, para que o caminho de leitura permaneça SELECT-only.
Índices principais
- FTS: GIN em
instancia.search_vector e persona.search_vector (espanhol, insensível a acentos
via unaccent).
- Fuzzy:
pg_trgm GIN/GiST sobre a denominação para busca tolerante a erros de digitação.
- Vetor: HNSW em
instancia.embedding (halfvec, cosseno) para similaridade semântica.
- Reconciliação: parcial
idx_persona_noident_reconcile (pais, lower(nombre)) WHERE identificador IS NULL.
- Chaves naturais / dedup:
uq_anotacion_event, uq_persona_pais_identificador, além das PKs de tabela acima.
As migrações são aplicadas via dbmate up; o DDL completo (índices, colunas geradas, ordenação de FK,
config de FTS em espanhol) vive em ../db/migrations/.