16 KiB
Esquema físico do banco dfimoveis.sqlite3
Atualização de 22/07/2026: as seções históricas deste documento descrevem o schema v1. A migração aditiva v2 descrita na seção final prevalece para consultas atuais.
1. Escopo e identificação do snapshot
Este documento descreve o esquema observado e revalidado em 21 de julho de 2026. Ele não altera o modelo conceitual de docs/DATA_MODEL.md e não atribui significado não comprovado às colunas.
| Item | Valor |
|---|---|
| Caminho | dfimoveis_data/dfimoveis.sqlite3 |
| Tamanho | 9.478.144 bytes |
| SHA-256 | bfbbdd09e88f76afd917ebd981b848b9321aeb5fc0602f712645d33e1a5ea76c |
| Data de referência | 2026-07-21 |
| Codificação | UTF-8 |
journal_mode persistido |
WAL |
| Páginas | 2.314 páginas de 4.096 bytes; freelist_count = 2 |
user_version / application_id |
0 / 0 |
| Método de abertura | URI mode=ro, PRAGMA query_only = ON e transação de leitura |
Como este arquivo é atualizado pelo scraper, não foi usado immutable=1. PRAGMA quick_check retornou ok e PRAGMA foreign_key_check não retornou violações. Nenhum sidecar foi removido.
2. Visão geral
O banco possui cinco tabelas da aplicação e a tabela interna sqlite_sequence. Não há views nem triggers. Os quatro índices existentes são autoíndices criados por chaves primárias ou restrições UNIQUE.
erDiagram
listings ||--|{ listing_history : "listing_id"
listings ||--|{ listing_searches : "listing_id"
listings ||--|{ photos : "listing_id"
crawl_runs {
INTEGER id PK
TEXT started_at
TEXT finished_at
TEXT status
TEXT stats_json
}
listings {
TEXT listing_id PK
TEXT url
REAL price_brl
REAL area_m2
TEXT first_seen_at
TEXT last_seen_at
TEXT content_hash
}
listing_history {
INTEGER id PK
TEXT listing_id FK
TEXT captured_at
TEXT content_hash
TEXT data_json
}
listing_searches {
TEXT listing_id PK,FK
TEXT search_url PK
TEXT first_seen_at
TEXT last_seen_at
}
photos {
TEXT listing_id PK,FK
TEXT source_url PK
INTEGER ordinal
TEXT sha256
TEXT local_path
}
crawl_runs não possui relacionamento físico com anúncios ou snapshots. Portanto, a execução que gerou uma linha só pode ser inferida pelos horários, não recuperada por uma chave.
Contagens e cardinalidades observadas
| Tabela | Linhas | Interpretação física |
|---|---|---|
crawl_runs |
20 | Execuções do coletor |
listings |
161 | Identidades de anúncios e seu estado corrente |
listing_history |
562 | Estados distintos dos anúncios por hash |
listing_searches |
161 | Vínculos entre anúncio e URL de busca |
photos |
7.363 | Mídias associadas diretamente aos anúncios |
sqlite_sequence |
2 | Sequências internas de tabelas AUTOINCREMENT |
No snapshot avaliado:
- cada anúncio está ligado a exatamente uma URL de busca;
- cada anúncio possui de 1 a 4 registros em
listing_history; - cada anúncio possui de 5 a 79 linhas em
photos; - não há órfãos nas três relações com
listings; - o hash corrente de cada anúncio possui registro correspondente no histórico;
- grande parte dos snapshots adicionais reflete instabilidade de campos contaminados, sobretudo
creci, e não mudança imobiliária real.
3. Dicionário de tabelas
3.1 crawl_runs
Representa execuções técnicas do coletor. Não registra fonte, versão do scraper, configuração, texto de erro ou relacionamento direto com os itens coletados.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
id |
INTEGER | Não | PK, AUTOINCREMENT |
Identificador da execução; a PK inteira atribui um valor quando omitido |
started_at |
TEXT | Não | — | Início em ISO 8601, observado em UTC |
finished_at |
TEXT | Sim | — | Término em ISO 8601 |
status |
TEXT | Não | — | Estado textual da execução; ok e failed foram observados |
stats_json |
TEXT | Não | '{}' |
Contadores técnicos em JSON |
As estruturas JSON observadas são válidas. Além das chaves originais, execuções recentes também registram photos_skipped.
3.2 listings
Representa a identidade do anúncio no portal e uma cópia mutável de seu estado mais recente. Não representa um imóvel físico deduplicado.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação e ressalvas |
|---|---|---|---|---|
listing_id |
TEXT | Sim no DDL; não observado | PK | Identificador do anúncio no portal; todos os valores atuais têm 6–7 dígitos. Por peculiaridade do SQLite, PK textual sem NOT NULL explícito merece validação na aplicação |
url |
TEXT | Não | — | URL do anúncio |
title |
TEXT | Sim | — | Título extraído |
description |
TEXT | Sim | — | Descrição curta extraída; não é o HTML bruto completo |
transaction_type |
TEXT | Sim | — | Tipo da transação; somente venda no snapshot |
property_type |
TEXT | Sim | — | Tipo anunciado; apartamento ou casa |
price_brl |
REAL | Sim | — | Preço anunciado em reais; sujeito a erros de escala |
condominium_brl |
REAL | Sim | — | Condomínio anunciado; periodicidade não está formalizada |
iptu_brl |
REAL | Sim | — | IPTU anunciado; periodicidade não está formalizada |
area_m2 |
REAL | Sim | — | Uma única área em m², sem indicar se é útil, privativa, construída, total ou terreno |
bedrooms |
INTEGER | Sim | — | Quartos anunciados, não a planta original comprovada |
suites |
INTEGER | Sim | — | Suítes anunciadas |
parking_spaces |
INTEGER | Sim | — | Vagas anunciadas |
address |
TEXT | Sim | — | Texto de endereço; fortemente contaminado pela página na coleta atual |
neighborhood |
TEXT | Sim | — | Bairro; fortemente contaminado pela página na coleta atual |
city |
TEXT | Sim | — | Cidade; 100% nula atualmente |
state |
TEXT | Sim | — | UF; 100% nula atualmente |
advertiser_name |
TEXT | Sim | — | Nome do anunciante; 100% nulo atualmente |
advertiser_code |
TEXT | Sim | — | Código do anunciante; parte relevante dos valores está contaminada |
creci |
TEXT | Sim | — | Registro do anunciante; parte relevante dos valores está contaminada |
latitude |
REAL | Sim | — | Latitude; 100% nula atualmente |
longitude |
REAL | Sim | — | Longitude; 100% nula atualmente |
published_at |
TEXT | Sim | — | Data publicada pelo portal; 100% nula atualmente |
first_seen_at |
TEXT | Não | — | Primeira observação do anúncio |
last_seen_at |
TEXT | Não | — | Última observação do anúncio |
inactive_at |
TEXT | Sim | — | Momento de inativação inferida pelo coletor; 100% nulo atualmente |
content_hash |
TEXT | Sim | — | SHA-256 do conteúdo normalizado usado para detectar mudança |
raw_html_path |
TEXT | Sim | — | Caminho relativo para o HTML bruto do anúncio |
extra_json |
TEXT | Não | '{}' |
JSON adicional; contém apenas json_ld, um array vazio nos 156 anúncios |
Exemplo não sensível de identidade: o anúncio 1026522 aponta para uma URL sob www.dfimoveis.com.br e para um HTML em raw/listing/1026522/...html.gz.
3.3 listing_history
Representa estados distintos de um anúncio, não cada tentativa ou observação do coletor.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
id |
INTEGER | Não | PK, AUTOINCREMENT |
Identificador do estado histórico; a PK inteira atribui um valor quando omitido |
listing_id |
TEXT | Não | FK | Anúncio ao qual o estado pertence |
captured_at |
TEXT | Não | — | Momento em que o estado foi capturado |
content_hash |
TEXT | Não | UNIQUE com listing_id |
Hash que impede repetir um estado idêntico para o anúncio |
data_json |
TEXT | Não | — | Snapshot JSON dos principais campos extraídos |
data_json é JSON válido em todas as 156 linhas. Ele contém os campos correntes de conteúdo, mas não contém first_seen_at, last_seen_at, inactive_at, raw_html_path ou uma referência à execução. Como a restrição é UNIQUE(listing_id, content_hash), observações repetidas sem mudança não geram novos snapshots.
3.4 listing_searches
Tabela de associação entre anúncio e consulta de busca que o encontrou.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
listing_id |
TEXT | Não | PK parcial, FK | Anúncio |
search_url |
TEXT | Não | PK parcial | URL/configuração da busca |
first_seen_at |
TEXT | Não | — | Primeira observação nessa busca |
last_seen_at |
TEXT | Não | — | Última observação nessa busca |
A chave primária composta é (listing_id, search_url). Há duas URLs distintas no banco, uma para apartamentos de três quartos no Cruzeiro Novo e outra para casas anunciadas com três ou quatro quartos no Jardins Mangueiral.
3.5 photos
Representa arquivos de mídia associados diretamente ao anúncio. A tabela não preserva associação por snapshot.
| Coluna | Tipo | Nulo? | Chave/default | Interpretação |
|---|---|---|---|---|
listing_id |
TEXT | Não | PK parcial, FK | Anúncio |
ordinal |
INTEGER | Não | — | Ordem da imagem no anúncio |
source_url |
TEXT | Não | PK parcial | URL de origem da imagem |
sha256 |
TEXT | Sim | — | Hash do arquivo baixado |
mime_type |
TEXT | Sim | — | Tipo MIME detectado |
bytes |
INTEGER | Sim | — | Tamanho do arquivo |
local_path |
TEXT | Sim | — | Caminho relativo sob dfimoveis_data/ |
first_seen_at |
TEXT | Não | — | Primeira observação da mídia |
last_seen_at |
TEXT | Não | — | Última observação da mídia |
A chave primária é (listing_id, source_url), e não (listing_id, ordinal). Não há ordinais repetidos dentro de um anúncio na amostra atual. Exemplo de caminho: photos/1026522/001_9d7f11205f0a4a3f.jpg.
3.6 sqlite_sequence
Tabela interna do SQLite que mantém as sequências AUTOINCREMENT de crawl_runs e listing_history. Não é entidade do domínio.
4. Chaves, índices e integridade referencial
| Índice | Tabela | Origem | Colunas lógicas |
|---|---|---|---|
sqlite_autoindex_listings_1 |
listings |
PK | listing_id |
sqlite_autoindex_listing_history_1 |
listing_history |
UNIQUE |
listing_id, content_hash |
sqlite_autoindex_listing_searches_1 |
listing_searches |
PK | listing_id, search_url |
sqlite_autoindex_photos_1 |
photos |
PK | listing_id, source_url |
As FKs de listing_history, listing_searches e photos apontam para listings(listing_id), com NO ACTION para atualização e exclusão. Não há índices explícitos por data, preço, região ou hash de foto. Com o volume atual isso não é um problema operacional; se a base crescer, índices analíticos devem ser criados apenas em uma camada derivada ou recomendados para uma migração aprovada, nunca adicionados ao banco original durante análise.
5. Correspondência com o modelo conceitual
| Conceito desejado | Implementação física | Avaliação |
|---|---|---|
| Fonte | Ausente; DF Imóveis é implícito nas URLs | Não permite múltiplas fontes com identidade segura |
| Execução de coleta | crawl_runs |
Parcial; faltam fonte, configuração, versão e relação com itens |
| Anúncio | listings |
Presente; estado corrente e identidade estão na mesma linha |
| Observação histórica | listing_history |
Parcial; guarda somente conteúdo alterado, sem run_id |
| Imóvel físico candidato | Ausente | Nenhuma deduplicação física é persistida |
| Localização normalizada | Colunas em listings |
Insuficiente e atualmente contaminada |
| Mídia | photos |
Parcial; vinculada ao anúncio, não ao snapshot |
| Origem por consulta | listing_searches |
Presente como relação anúncio–URL de busca |
6. Decisões de interpretação e ambiguidades
listing_ididentifica um anúncio no portal, não um imóvel físico.- Uma linha de
listing_historyé um estado de conteúdo distinto, não prova de que o anúncio foi observado somente uma vez. Observações idênticas são condensadas emlast_seen_at. - O banco ainda não contém histórico suficiente para liquidez: há quatro dias efetivos de observação e a maioria das transições é ruído de campos contaminados.
- Anúncios diferentes com preço, área ou fotos semelhantes permanecem anúncios distintos. Não há chave de endereço/unidade nem tabela de candidatos físicos.
area_m2não deve ser usada para preço por m² até que o conceito de área seja validado por tipologia e fonte.bedroomsé a quantidade anunciada. Ela não comprova a planta original de três quartos exigida para Jardins Mangueiral.- Datas com sufixo
+00:00são tratadas como UTC. A cobertura observada vai de 13 a 21/07/2026. - Os campos textuais contaminados devem ser reextraídos do HTML bruto em uma camada derivada; os valores atuais não devem ser silenciosamente normalizados como se fossem corretos.
7. Problemas conhecidos do snapshot
- 150 de 161 endereços têm mais de 300 caracteres; 159 de 161 bairros têm mais de 100 caracteres.
city,state,latitude,longitude,published_ateadvertiser_nameestão integralmente ausentes.- 99 códigos de anunciante e 157 valores de CRECI são longos demais para o significado esperado, indicando extração contaminada.
- Há um preço de R$ 570.000.000 e uma área de 0,11 m², ambos candidatos fortes a erro de extração/escala.
- O JSON-LD reservado em
extra_jsoné um array vazio em todos os 161 anúncios e não oferece uma fonte estruturada alternativa no snapshot. - As 7.363 linhas de foto têm hash e caminho local preenchidos; isso não garante que toda imagem pertença ao imóvel.
- Hashes de foto idênticos aparecem em muitos anúncios; isso demonstra a presença de ativos genéricos e impede usar igualdade de hash como prova isolada de imóvel duplicado.
- Das 401 transições históricas, 398 alteram
creci; somente três anúncios apresentam mudança real de preço. O histórico precisa de normalização antes de análises temporais. - Não há como ligar formalmente anúncio/snapshot a
crawl_runs, nem distinguir por chave a fonte se uma segunda fonte for incorporada.
12. Schema v2 — 22/07/2026
A migração foi autorizada explicitamente, executada após backup consistente e não removeu linhas ou colunas antigas.
Contagens preservadas na migração:
| Tabela legada | Linhas |
|---|---|
listings |
162 |
listing_searches |
162 |
listing_history |
637 |
photos |
7.457 |
crawl_runs |
21 |
Novas colunas:
listings:address_normalized,neighborhood_normalized,creci_normalized,raw_content_hash,stable_content_hash;photos:discovery_source,is_primary_gallery,review_status;crawl_runs:searches_json,config_json,scope_complete.
Novas tabelas:
availability_observations: evidência datada e sua fonte, sem converter selo visual em prova registral;data_quality_issues: quarentenas reversíveis, com severidade, evidência e estado;listing_change_events: eventos materiais produzidos pelo hash estável nas futuras coletas.
Novas visões:
v_primary_photos;v_open_data_quality_issues;v_listing_current_availability.
Auditoria inicial:
- 3.521 fotos pertencem à galeria do próprio anúncio;
- 3.286 registros legados apontam para
/fotos/<outro_listing_id>/e vieram de recomendações do portal; - 650 registros restantes são ativos visuais do site ou URLs sem proprietário de anúncio;
- os 3.936 registros não primários foram preservados e marcados, não apagados;
- 15 anúncios foram normalizados para o CRECI
26903; PRAGMA quick_checkretornouokapós a migração.