Modelo de dados
Modelo relacional (PostgreSQL 16 + PostGIS), normalizado (3FN) e com nomes em português. O schema é versionado por Flyway no backend Java.
Diagrama: veja o esquema visual em Diagramas → Esquema do banco de dados.
Convenções e padrões
- Nomenclatura:
snake_case, tabelas e colunas em português, nomes no singular, sem palavras reservadas (ex.:usuario, nuncauser). - Chave primária:
idbigint GENERATED ALWAYS AS IDENTITY(padrão SQL). Semprebigint, mesmo em tabelas pequenas — ointestoura em ~2,1 bi e trocar depois é migração cara (ADR-0012). - Chave estrangeira:
<tabela_referenciada>_id(ex.:imovel_id). - Constraints nomeadas:
pk_<tabela>,fk_<tabela>_<ref>,uq_<tabela>_<col>,ck_<tabela>_<regra>,ix_<tabela>_<col>. - Tempo:
timestamptzem UTC;criado_em/atualizado_emcom defaultnow(). - Dinheiro:
numeric(14,2). Percentual:numeric(6,2). - Geo:
geography(Point,4326)(PostGIS) com índice GiST. - Vocabulário controlado: tabelas de referência (
tipo_imovel,modalidade_venda) ou tipos enumerados (status_imovel,status_enriquecimento), evitando texto livre repetido. - Migrations imutáveis e incrementais (
V1__init.sql,V2__...).
Auditoria e exclusão lógica (padrão em todas as tabelas)
Toda tabela (exceto as de referência, que são imutáveis) carrega estas colunas — por isso elas não são repetidas em cada tabela abaixo:
| Coluna | Tipo | Regra |
|---|---|---|
criado_em | timestamptz NOT NULL DEFAULT now() | Data/hora de criação do registro. |
atualizado_em | timestamptz NOT NULL DEFAULT now() | Atualizada em todo UPDATE via trigger set_atualizado_em. |
excluido_em | timestamptz NULL | Exclusão lógica (soft delete). NULL = ativo; preenchido = excluído. |
- Nunca fazer
DELETEfísico no fluxo da aplicação — sempre marcarexcluido_em = now(). - Toda leitura filtra
WHERE excluido_em IS NULL(encapsular numa viewvw_<tabela>_ativoou repositório base). UNIQUE de negócio vira índice parcial (... WHERE excluido_em IS NULL) para permitir recriar um registro após exclusão lógica. - Trigger padrão (uma função reutilizada por todas as tabelas):
CREATE OR REPLACE FUNCTION set_atualizado_em() RETURNS trigger AS $$
BEGIN NEW.atualizado_em = now(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
-- por tabela:
CREATE TRIGGER tg_<tabela>_atualizado_em BEFORE UPDATE ON <tabela>
FOR EACH ROW EXECUTE FUNCTION set_atualizado_em();Tipos enumerados
status_imovel:disponivel | vendido | suspenso | removido.status_enriquecimento:pendente | ok | parcial | falha.canal_alerta:email | push | webhook.
Tabelas de referência
municipio
id (PK), codigo_ibge (uq), nome, uf char(2).
tipo_imovel
id (PK), nome (uq) — Casa, Apartamento, Terreno, Comercial...
modalidade_venda
id (PK), nome (uq) — "Leilão SFI", "Venda Direta Online", "Licitação Aberta"...
Tabelas principais
imovel
Núcleo (dados do CSV). Colunas: id (PK), codigo (N° do imóvel, uq por fonte), fonte, url, titulo, tipo_imovel_id (FK), modalidade_venda_id (FK), municipio_id (FK), bairro, endereco, localizacao geography(Point,4326), area_total, area_privativa, area_terreno, quartos, valor_avaliacao, valor_minimo, desconto_pct, status (status_imovel)
imovel_detalhe (enriquecimento — ADR-0010)
1:1 com imovel (imovel_id FK uq). Dados raspados da página de detalhe: valor_primeiro_leilao, valor_segundo_leilao, data_primeiro_leilao, data_segundo_leilao, leiloeiro, edital, numero_item, matricula, comarca, oficio, inscricao_imobiliaria, cep, endereco_completo, aceita_fgts, aceita_financiamento, despesas_condominio_comprador, despesas_tributos_comprador, situacao_ocupacao, descricao_completa, edital_url, matricula_url, enriquecido_em, status_enriquecimento (status_enriquecimento).
imovel_foto
id (PK), imovel_id (FK), url, ordem. uq(imovel_id, ordem).
historico_preco
id (PK), imovel_id (FK), valor, desconto_pct, status (status_imovel), capturado_em (histórico de preço/status — ver RN-02).
analise_custo
1:1 com imovel (imovel_id FK uq), recalculada quando o imóvel muda/é enriquecido: itbi, custas, registro, dividas, reforma_estimada, custo_total, desconto_real_pct, score smallint (0–100), confianca numeric(4,3) (0–1), versao_score (ex.: v0-heuristico), fatores jsonb (array de {nome, rotulo, contribuicao, direcao} — explicabilidade, ver score-v0-spec), parametro_custo_versao (versão dos parâmetros usados), status_confianca (status_enriquecimento), calculado_em.
analise_juridica (IA — ADR-0014, evolução)
1:1 com imovel (imovel_id FK uq). Resultado da análise jurídica com IA: resumo text (parecer em linguagem simples — RF-10), flags jsonb (array de {tipo, presente, confianca, trecho_citado, origem} — grounding), nivel_risco (baixo|medio|alto), modelo (ex.: gpt-4o-mini), versao_prompt, fonte (edital|matricula), analisado_em, status (ok|parcial|falha). Não substitui parecer jurídico humano (disclaimer obrigatório). Guarda os trechos citados que embasam cada flag (nunca "caixa-preta").
coleta_bruta
id (PK), fonte (caixa = CSV, caixa-detalhe = HTML), uf, codigo, payload_brutobytea (comprimido), compressao (zstd|gzip|none), payload_hash char(64) (SHA-256), tamanho_bytes, coletado_em. Permite reprocessar sem re-baixar (inclusive re-parsear o detalhe se o parser mudar).
- Compressão, não base64: o payload (HTML/CSV cru) é comprimido com zstd e guardado como
bytea. Base64 é proibido aqui — codifica sem comprimir e infla ~33%. Ver ADR-0012. payload_hashdá integridade e dedup (não regravar coleta idêntica).- Retenção: ~90 dias em banco; depois descartar ou arquivar frio (object storage). É o dado que mais pesa — ver Backup & DR. Já
historico_precotem retenção longa (é pequeno e é parte do produto).
Parâmetros de custo (versionados)
Alimentam o motor de custos. São versionados por vigência: o analise_custo grava a parametro_custo_versao usada, para o cálculo ser auditável e reprodutível. Defaults e fontes em Parâmetros de custo.
aliquota_itbi
Alíquota de ITBI (varia por município). id (PK), municipio_id (FK, null = default por UF), uf char(2) (usado no default por UF), aliquota_pct numeric(6,2), vigencia_inicio date, vigencia_fim date NULL, fonte (URL/lei), versao. uq parcial por (municipio_id, vigencia_inicio) WHERE excluido_em IS NULL.
parametro_custo
Demais parâmetros configuráveis (custas, registro, R$/m² de reforma). id (PK), chave (custas_pct | registro_faixa | reforma_m2), escopo (nacional | uf | municipio), ufchar(2) NULL, municipio_id (FK NULL), tipo_imovel_id (FK NULL, p/ reforma por tipo), valor numeric(14,2) NULL, percentual numeric(6,2) NULL, faixa_min/faixa_maxnumeric(14,2) NULL (p/ registro por faixa), vigencia_inicio date, vigencia_fim date NULL, fonte, versao. Resolução: município → UF → nacional (mais específico vence).
Usuários e engajamento
usuario
id (PK), email (uq), nome, avatar_url, provedor (google|github), provedor_id (sub/id no provedor). uq(provedor, provedor_id). Sem senha_hash — login social via OIDC (ADR-0013); só e-mail verificado.
token_atualizacao (refresh token)
id (PK), usuario_id (FK), token_hash char(64) (SHA-256 do refresh, uq), familia_id uuid (agrupa a cadeia de rotação), expira_em, revogado_em, substituido_por (FK self, rastreia a rotação), user_agent, ip. Guardado hasheado; habilita revogação e detecção de reúso (ADR-0013). Retenção: podar expirados/revogados.
favorito
id (PK), usuario_id (FK), imovel_id (FK). uq(usuario_id, imovel_id).
alerta
id (PK), usuario_id (FK), filtro jsonb, canal (canal_alerta), ativo boolean.
Tabelas de suporte (infra)
Sustentam decisões arquiteturais aceitas; não são entidades de negócio.
geocode_cache (ADR-0016)
id (PK), chave_hash char(64) (uq, SHA-256 de endereco_normalizado+cep), lat, lng, qualidade (exato|aproximado|centroide_bairro|centroide_cidade), provedor, resolvido_em. Evita reconsultar o geocoder; TTL longo.
outbox (ADR-0018 / padrão Transactional Outbox)
id (PK), agregado (ex.: imovel, alerta), agregado_id, tipo_evento, payload jsonb, disponivel_em, publicado_em NULL, tentativas. Gravada na mesma transação da mudança de estado; um relay publica no RabbitMQ (at-least-once, sem perder evento).
notificacao_enviada (ADR-0018 / idempotência)
id (PK), usuario_id (FK), tipo_evento, imovel_id (FK NULL), janela (ex.: dia), canal (canal_alerta), enviado_em, status (enviado|falha|bounce|complaint). uq(usuario_id, tipo_evento, imovel_id, janela) → dedup de envio.
Índices e busca
- Filtros:
ix_imovel_municipio,ix_imovel_tipo,ix_imovel_modalidade,ix_imovel_statuse índice emdesconto_pct/desconto_real_pct(viaanalise_custo). - Espacial (GiST) em
imovel.localizacaopara busca por raio/região (PostGIS). uq_imovel_codigo_fonte(codigoúnico porfonte) — deduplicação (RN-01); como índice parcialWHERE excluido_em IS NULL(permite recriar após exclusão lógica).
Ver Backend (Java) e Cálculo de custos.