From c5e95a609583743243f56fb962e0ccd712d4fa32 Mon Sep 17 00:00:00 2001 From: Yutsuo Date: Thu, 23 Jul 2026 07:58:58 -0300 Subject: [PATCH] many updates. many. --- .gitignore | 26 +- AGENTS.md | 257 ++++---- README.md | 24 +- __pycache__/dfimoveis_scraper.cpython-314.pyc | Bin 72591 -> 100843 bytes dfimoveis_data/dfimoveis.sqlite3-shm | Bin 0 -> 32768 bytes dfimoveis_data/dfimoveis.sqlite3-wal | 0 dfimoveis_scraper.py | 240 ++++++- docs/ACTUAL_SCHEMA.md | 97 ++- docs/ANALYSIS_RULES.md | 610 ++++++------------ docs/CONTEXT_CHANGELOG.md | 90 +++ docs/CURRENT_SHORTLIST.md | 53 ++ docs/CURRENT_STATE.md | 145 +++++ docs/DATA_MODEL.md | 537 +++++---------- docs/FINANCIAL_STRATEGY.md | 195 ++++++ docs/LOCATION_CRITERIA.md | 138 ++++ docs/PROJECT_CONTEXT.md | 308 +++------ docs/PROPERTY_CRITERIA.md | 536 ++++++--------- docs/SCRAPER_OPERATIONS.md | 206 ++++++ reports/creci_26903_mangueiral/README.md | 55 ++ reports/creci_26903_mangueiral/query.sql | 15 + reports/creci_26903_mangueiral/results.csv | 16 + reports/cruzeiro_top10/README.md | 172 ----- .../__pycache__/analysis.cpython-314.pyc | Bin 6342 -> 0 bytes .../photo_similarity.cpython-314.pyc | Bin 4667 -> 0 bytes reports/cruzeiro_top10/parameters.json | 22 - reports/cruzeiro_top10/results.csv | 11 - reports/cruzeiro_top15/README.md | 64 ++ .../analysis.py | 2 +- reports/cruzeiro_top15/parameters.json | 20 + .../photo_similarity.py | 6 +- .../query.sql | 1 + reports/cruzeiro_top15/results.csv | 16 + reports/cruzeiro_vs_mangueiral/README.md | 179 ++--- .../cruzeiro_vs_mangueiral/parameters.json | 46 +- reports/cruzeiro_vs_mangueiral/results.csv | 52 +- reports/database_assessment.md | 358 ++++------ reports/mangueiral_top10/README.md | 184 ------ .../__pycache__/analysis.cpython-312.pyc | Bin 5417 -> 0 bytes .../__pycache__/analysis.cpython-314.pyc | Bin 6208 -> 0 bytes .../photo_similarity.cpython-312.pyc | Bin 4120 -> 0 bytes .../photo_similarity.cpython-314.pyc | Bin 4680 -> 0 bytes reports/mangueiral_top10/parameters.json | 29 - reports/mangueiral_top10/results.csv | 11 - reports/mangueiral_top15/README.md | 70 ++ .../analysis.py | 2 +- reports/mangueiral_top15/parameters.json | 38 ++ .../photo_similarity.py | 6 +- .../query.sql | 3 +- reports/mangueiral_top15/results.csv | 16 + requirements-dfimoveis.txt | 2 +- run_scraper.ps1 | 5 + scripts/migrate_dfimoveis_schema_v2.py | 169 +++++ search_list.txt | 3 +- tests/test_dfimoveis_schema_v2.py | 64 ++ 54 files changed, 2710 insertions(+), 2389 deletions(-) create mode 100644 dfimoveis_data/dfimoveis.sqlite3-shm create mode 100644 dfimoveis_data/dfimoveis.sqlite3-wal create mode 100644 docs/CONTEXT_CHANGELOG.md create mode 100644 docs/CURRENT_SHORTLIST.md create mode 100644 docs/CURRENT_STATE.md create mode 100644 docs/FINANCIAL_STRATEGY.md create mode 100644 docs/LOCATION_CRITERIA.md create mode 100644 docs/SCRAPER_OPERATIONS.md create mode 100644 reports/creci_26903_mangueiral/README.md create mode 100644 reports/creci_26903_mangueiral/query.sql create mode 100644 reports/creci_26903_mangueiral/results.csv delete mode 100644 reports/cruzeiro_top10/README.md delete mode 100644 reports/cruzeiro_top10/__pycache__/analysis.cpython-314.pyc delete mode 100644 reports/cruzeiro_top10/__pycache__/photo_similarity.cpython-314.pyc delete mode 100644 reports/cruzeiro_top10/parameters.json delete mode 100644 reports/cruzeiro_top10/results.csv create mode 100644 reports/cruzeiro_top15/README.md rename reports/{cruzeiro_top10 => cruzeiro_top15}/analysis.py (97%) create mode 100644 reports/cruzeiro_top15/parameters.json rename reports/{cruzeiro_top10 => cruzeiro_top15}/photo_similarity.py (92%) rename reports/{cruzeiro_top10 => cruzeiro_top15}/query.sql (97%) create mode 100644 reports/cruzeiro_top15/results.csv delete mode 100644 reports/mangueiral_top10/README.md delete mode 100644 reports/mangueiral_top10/__pycache__/analysis.cpython-312.pyc delete mode 100644 reports/mangueiral_top10/__pycache__/analysis.cpython-314.pyc delete mode 100644 reports/mangueiral_top10/__pycache__/photo_similarity.cpython-312.pyc delete mode 100644 reports/mangueiral_top10/__pycache__/photo_similarity.cpython-314.pyc delete mode 100644 reports/mangueiral_top10/parameters.json delete mode 100644 reports/mangueiral_top10/results.csv create mode 100644 reports/mangueiral_top15/README.md rename reports/{mangueiral_top10 => mangueiral_top15}/analysis.py (97%) create mode 100644 reports/mangueiral_top15/parameters.json rename reports/{mangueiral_top10 => mangueiral_top15}/photo_similarity.py (92%) rename reports/{mangueiral_top10 => mangueiral_top15}/query.sql (92%) create mode 100644 reports/mangueiral_top15/results.csv create mode 100644 run_scraper.ps1 create mode 100644 scripts/migrate_dfimoveis_schema_v2.py create mode 100644 tests/test_dfimoveis_schema_v2.py diff --git a/.gitignore b/.gitignore index aaea57d..4b3b1ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,25 @@ -dfimoveis_data \ No newline at end of file +# Banco e arquivos transitórios +*.sqlite +*.sqlite3 +*.db +*.sqlite-wal +*.sqlite-shm + +# Dados coletados +/dfimoveis_data/photos/ +/dfimoveis_data/raw/ +/dfimoveis_data/browser-profile/ + +# Derivações grandes +/data/derived/ +/exports/ + +# Python +__pycache__/ +*.py[cod] +.venv/ + +# Segredos e sessões +.env +.env.* +*.cookies.json diff --git a/AGENTS.md b/AGENTS.md index 870de41..10a9d3a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,191 +1,200 @@ # AGENTS.md — Busca de Imóveis DF -## Finalidade +## Missão -Este repositório apoia a coleta, organização e análise histórica de anúncios imobiliários do Distrito Federal para uma decisão residencial de longo prazo. +Apoiar uma decisão de compra residencial de longo prazo no Distrito Federal usando o banco histórico de anúncios, as fotografias e os critérios pessoais consolidados do usuário. -Antes de trabalhar no projeto, leia integralmente: +O trabalho deve ser cético, reproduzível e orientado a evitar uma compra ruim. Não maximizar apenas preço por metro quadrado, valorização ou número de atributos anunciados. +## Leitura obrigatória + +Antes de analisar imóveis ou alterar scripts, leia: + +- `docs/CURRENT_STATE.md` - `docs/PROJECT_CONTEXT.md` +- `docs/LOCATION_CRITERIA.md` - `docs/PROPERTY_CRITERIA.md` +- `docs/FINANCIAL_STRATEGY.md` +- `docs/SCRAPER_OPERATIONS.md` - `docs/DATA_MODEL.md` - `docs/ANALYSIS_RULES.md` -Esses documentos são a referência operacional do projeto. Quando houver conflito: +Quando houver conflito, use esta precedência: -1. uma instrução explícita e recente do usuário prevalece; -2. depois, prevalecem estes documentos; -3. por último, hipóteses inferidas a partir dos dados. +1. instrução explícita e recente do usuário; +2. `docs/CURRENT_STATE.md`; +3. documento especializado do tema; +4. snapshot ou relatório datado; +5. contexto histórico e inferências. -Nunca trate uma hipótese como preferência confirmada. +Nunca transforme inferência, relato de corretor, percepção de morador ou padrão do banco em preferência confirmada. -Para auxiliar no entendimento e contexto, as plantas das casas dos Jardins Mangueiral estão em: +--- -- `resources/plans` +## Prioridade regional vigente -Para auxiliar no entendimento e contexto, foram feitas capturas dos mapas dos Jardins Mangueiral, que foram salvas na em: +- **Prioridade principal:** Jardins Mangueiral. +- **Alternativa secundária e oportunística:** Cruzeiro, especialmente Cruzeiro Novo. -- `resources/maps` +O ranking interno de quadras do Cruzeiro continua sendo usado para avaliar apartamentos concretos, mas não torna o Cruzeiro prioritário sobre o Mangueiral. --- -## Banco de dados +## Regras pessoais obrigatórias -O banco principal deve ser tratado como **estritamente somente leitura**. +### Jardins Mangueiral -### Regras obrigatórias +- Considerar somente casas com **planta original de três quartos**. +- Excluir plantas originalmente de dois quartos, mesmo que tenham sido adaptadas. +- Não penalizar automaticamente o fechamento do quintal. +- Avaliar se a conversão cria espaço social útil, iluminado, ventilado e sem redundância funcional. +- Usar a hierarquia de QCs de `LOCATION_CRITERIA.md`, sem ignorar a posição exata da casa. +- Avaliar explicitamente ruído de vizinhos, animais, música, áreas sociais, ruas e equipamentos comuns. +- Dar preferência a casas reformadas, com bom espaço na cozinha. -- Nunca execute `INSERT`, `UPDATE`, `DELETE`, `DROP`, `ALTER`, `CREATE`, `REPLACE`, `VACUUM`, `REINDEX`, `ATTACH` ou comandos equivalentes contra o banco original. -- Não substitua, mova, compacte, migre ou renomeie o arquivo original. -- Não remova arquivos `-wal` ou `-shm` sem uma instrução explícita. -- Não execute scripts de scraping contra o banco durante uma análise sem autorização. -- Use `LIMIT` em consultas exploratórias. -- Não imprima tabelas inteiras no terminal. -- Prefira agregações, amostras pequenas e exportações derivadas. -- Salve resultados em `reports/`, `exports/` ou `data/derived/`. -- Nunca grave resultados derivados dentro do banco original. -- Antes de qualquer análise, confirme o caminho do banco e abra-o em modo somente leitura. +### Cruzeiro -### Abertura recomendada em Python +- Tratar como oportunidade secundária. +- Usar o ranking interno Ouro/Prata/Bronze de `LOCATION_CRITERIA.md`. +- Uma unidade fora da faixa superior pode vencer pela combinação de reforma, silêncio, planta, andar, elevador, garagem e preço. +- Ausência de elevador e garagem é concessão relevante. +- Confirmar se os três quartos são utilizáveis e coerentes com a planta. +- Inspecionar sistemas antigos, condomínio, fachada, prumadas, elétrica, hidráulica e obras previstas. -```python -import sqlite3 +--- -DB_PATH = "data/imoveis.sqlite" +## Banco principal -connection = sqlite3.connect( - f"file:{DB_PATH}?mode=ro&immutable=1", - uri=True, -) -connection.execute("PRAGMA query_only = ON") +Caminho esperado: + +```text +dfimoveis_data/dfimoveis.sqlite3 ``` -Se o banco estiver sendo atualizado por outro processo, não use `immutable=1`; mantenha `mode=ro` e confirme a consistência do snapshot antes de analisar. +O banco original é **estritamente somente leitura durante análises**. -### Cliente de linha de comando +### Proibições -```bash -sqlite3 -readonly data/imoveis.sqlite -``` +Nunca executar contra o banco original: ---- +- `INSERT` +- `UPDATE` +- `DELETE` +- `DROP` +- `ALTER` +- `CREATE` +- `REPLACE` +- `VACUUM` +- `REINDEX` +- `ATTACH` -## Fluxo inicial obrigatório +Não mover, substituir, compactar, migrar ou renomear o banco sem instrução explícita. Não remover arquivos `-wal` ou `-shm` arbitrariamente. -Ao iniciar uma nova tarefa de análise: +### Abertura recomendada -1. Identifique o arquivo SQLite correto e seu tamanho. -2. Leia `sqlite_master` para listar tabelas, views, índices e triggers. -3. Examine o esquema real antes de escrever consultas analíticas. -4. Conte registros por tabela. -5. Identifique campos de data, preço, área, quartos, localização, URL, anunciante e identificadores. -6. Verifique valores nulos, duplicidades, formatos inconsistentes e possíveis quebras de coleta. -7. Descubra se os dados representam: - - anúncios; - - imóveis físicos; - - observações históricas do mesmo anúncio; - - execuções do scraper. -8. Documente qualquer ambiguidade antes de produzir conclusões. +```python +import sqlite3 -Não presuma que o modelo lógico descrito em `docs/DATA_MODEL.md` já existe fisicamente. +DB_PATH = "dfimoveis_data/dfimoveis.sqlite3" +connection = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True) +connection.execute("PRAGMA query_only = ON") +``` + +Use `immutable=1` somente em cópia ou snapshot que não esteja sendo atualizado pelo scraper. --- -## Princípios de análise - -- Diferencie sempre **anúncio**, **imóvel físico** e **observação histórica**. -- Um mesmo imóvel pode aparecer em vários anúncios, portais, corretores ou datas. -- Um mesmo anúncio pode mudar de preço, descrição, área informada ou status. -- Preço anunciado não é preço efetivamente negociado. -- Ausência de anúncio não prova venda. -- Remoção de anúncio não prova fechamento de negócio. -- Campo preenchido pelo anunciante não deve ser considerado confiável sem validação. -- Não calcule preço por metro quadrado quando a área não for comparável ou confiável. -- Não misture área privativa, útil, construída e total sem separar os conceitos. -- Não compare períodos sem verificar mudanças no universo coletado. -- Não compare regiões sem controlar tipologia, quartos, área, garagem, estado e padrão do imóvel. -- Sempre informe tamanho da amostra, cobertura temporal, filtros e limitações. -- Prefira mediana, percentis e intervalos interquartis a médias isoladas. -- Mostre a sensibilidade das conclusões a outliers, duplicidades e dados ausentes. +## Separação entre scraper e análise + +- Não executar coleta e análise pesada simultaneamente no mesmo arquivo SQLite sem avaliar bloqueios e consistência. +- Para análise longa, preferir uma cópia/snapshot consistente ou conexão read-only. +- Resultados derivados devem ir para `reports/`, `exports/` ou `data/derived/`. +- Nunca gravar rankings, notas ou tabelas derivadas dentro do banco original sem autorização. --- -## Segurança e desempenho +## Fluxo analítico obrigatório -- Nunca exponha credenciais, cookies, tokens, chaves, sessões ou dados pessoais em relatórios. -- Não envie o banco ou grandes amostras para serviços externos sem autorização explícita. -- Não tente contornar mecanismos de proteção de sites. -- Respeite limites de requisição, termos aplicáveis e regras definidas para o scraper. -- Evite consultas que façam varreduras desnecessárias repetidas em tabelas grandes. -- Use `EXPLAIN QUERY PLAN` em consultas lentas. -- Sugira índices apenas como recomendação; não os crie no banco original. -- Para transformações pesadas, crie um banco derivado ou arquivos Parquet/CSV separados. +0. Verificar se a primeira foto do anúncio não demonstra que ele foi vendido. +1. Confirmar o snapshot e a cobertura temporal. +2. Ler o esquema real. +3. Identificar anúncios, observações históricas, fotos e execuções de coleta. +4. Verificar falhas de coleta e mudanças do scraper. +5. Separar anúncio, observação e imóvel físico provável. +6. Deduplicar republicações sem apagar registros originais. +7. Auditar a primeira foto e a descrição para “VENDIDO”, “negócio fechado” ou equivalente. +8. Colocar inconsistências em quarentena, sem exclusão silenciosa. +9. Avaliar fotografias; CSV isolado não é evidência suficiente para planta e conservação. +10. Registrar amostra, filtros, exclusões, limitações e método. --- -## Código +## Regras analíticas essenciais -- Escreva scripts reproduzíveis, parametrizados e idempotentes. -- Use caminhos relativos ao repositório. -- Evite constantes escondidas. -- Registre os filtros e premissas usados. -- Inclua tratamento de erros. -- Use consultas SQL parametrizadas. -- Adicione testes para regras de normalização, deduplicação e cálculo. -- Não altere arquivos sem relação com a tarefa. -- Não faça refatorações amplas sem necessidade. -- Não invente significado para colunas pouco claras; documente a dúvida. +- Preço anunciado não é preço negociado. +- Desaparecimento do anúncio não prova venda. +- Selo de vendido é evidência operacional, não prova registral. +- Não misturar área privativa, útil, construída, total e terreno. +- Não usar preço por m² quando a área não for comparável ou confiável. +- Não multiplicar republicações visualmente equivalentes. +- Não considerar “reformado” como prova de renovação elétrica, hidráulica ou estrutural. +- Não atribuir causalidade à QC ou quadra sem controlar tipologia e qualidade. +- Não inferir liquidez com uma janela curta de coleta. +- Rankings servem para ordenar investigação e visitas, não para automatizar a compra. --- -## Saídas esperadas +## Finanças -Relatórios analíticos devem, sempre que aplicável, conter: +Antes de recomendar compra ou preço máximo, leia `FINANCIAL_STRATEGY.md`. -1. pergunta respondida; -2. período e universo analisados; -3. filtros e exclusões; -4. tamanho da amostra; -5. método de deduplicação; -6. qualidade dos dados; -7. resultados; -8. limitações; -9. implicações para a decisão de compra; -10. consultas ou scripts usados para reproduzir o resultado. +Regras fixas: -Use gráficos apenas quando eles melhorarem a interpretação. Todo gráfico deve ter título, unidade, período, tamanho da amostra e definição da métrica. +- preservar pelo menos R$ 50.000 de reserva líquida após compra, custos e adequações; +- não tratar FGTS como reserva de emergência; +- excluir Pró-Cotista e MCMV Faixa 4 das simulações atuais; +- distinguir entrada, custos de compra, reforma, reserva e amortizações futuras; +- comparar financiamento, consórcio e aluguel com premissas datadas; +- não usar taxas antigas como atuais; +- incorporar fatores psicológicos sem convertê-los em justificativa para um imóvel inadequado. --- -## Contexto imobiliário prioritário +## Código e relatórios -As regiões prioritárias são: +- Escrever scripts reproduzíveis, parametrizados e idempotentes. +- Usar SQL parametrizado. +- Não imprimir milhares de linhas no terminal. +- Usar `LIMIT` na exploração. +- Usar `EXPLAIN QUERY PLAN` quando necessário. +- Sugerir índices sem criá-los no banco original. +- Preservar HTML bruto, fotos e dados originais. +- Toda conclusão deve separar fato, inferência, percepção e pendência. -1. Cruzeiro, especialmente Cruzeiro Novo; -2. Jardins Mangueiral. +Um relatório deve conter: -Não exclua outras regiões de uma análise exploratória quando forem úteis como referência, mas não desvie o projeto de seu objetivo principal sem instrução. - -Para Jardins Mangueiral: - -- considerar somente casas com planta original de três quartos; -- não penalizar automaticamente o fechamento do quintal; -- avaliar a funcionalidade real da conversão; -- considerar as preferências de QC descritas em `docs/PROPERTY_CRITERIA.md`; -- tratar relatos de moradores como evidência anedótica, não como fato conclusivo. +1. pergunta; +2. snapshot e período; +3. universo e amostra; +4. filtros e quarentenas; +5. método de deduplicação; +6. avaliação fotográfica; +7. resultados; +8. limitações; +9. implicação prática; +10. consultas e scripts para reprodução. --- ## Atualização do contexto -Não altere estes documentos apenas porque encontrou um padrão no banco. - -Atualize-os somente quando: +Atualizar os documentos somente quando: - o usuário corrigir uma premissa; - uma preferência for explicitamente confirmada; -- um valor financeiro for atualizado; -- uma regra metodológica for aprovada; -- o esquema real do projeto for consolidado. +- houver nova visita relevante; +- um snapshot substituir o anterior; +- uma regra financeira ou operacional mudar; +- o esquema real for documentado. -Ao atualizar uma premissa financeira, registre a data de referência e preserve o histórico em Git. +Registrar a mudança em `docs/CONTEXT_CHANGELOG.md` com data e motivo. diff --git a/README.md b/README.md index 0d38a33..cf17ae6 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,7 @@ Projeto pessoal para coletar, organizar e analisar anúncios imobiliários do Distrito Federal, com foco em uma decisão residencial de longo prazo. -As regiões prioritárias são: - -1. Cruzeiro, especialmente Cruzeiro Novo; -2. Jardins Mangueiral. +A prioridade operacional atual é o Jardins Mangueiral. O Cruzeiro permanece apenas como histórico e alternativa oportunística; sua URL foi removida da coleta corrente. O objetivo não é identificar simplesmente o menor preço anunciado. A análise considera funcionalidade, estado do imóvel, risco de reforma, localização, liquidez, custos de aquisição e manutenção e adequação à vida familiar. @@ -21,8 +18,8 @@ O repositório contém: Relatórios principais: -- [Top 10 — Cruzeiro Novo](reports/cruzeiro_top10/README.md) -- [Top 10 — Jardins Mangueiral](reports/mangueiral_top10/README.md) +- [Top 15 — Cruzeiro Novo](reports/cruzeiro_top15/README.md) +- [Top 15 — Jardins Mangueiral](reports/mangueiral_top15/README.md) - [Cruzeiro Novo x Jardins Mangueiral](reports/cruzeiro_vs_mangueiral/README.md) Os rankings são prioridades de investigação, não recomendações automáticas de compra nem estimativas de preço negociado. @@ -82,10 +79,7 @@ As ferramentas de similaridade fotográfica usam Pillow, incluído no arquivo de ## Configuração das buscas -O arquivo [search_list.txt](search_list.txt) contém uma URL completa por linha. Atualmente ele cobre: - -- apartamentos de três quartos no Cruzeiro Novo; -- casas anunciadas com três ou quatro quartos no Jardins Mangueiral. +O arquivo [search_list.txt](search_list.txt) contém uma URL completa por linha. Atualmente ele cobre casas anunciadas com três ou quatro quartos no Jardins Mangueiral. A busca do Mangueiral inclui quatro quartos para ampliar a coleta, mas esses imóveis são excluídos da análise regional. Mesmo três quartos anunciados não comprovam a planta original. @@ -138,6 +132,8 @@ dfimoveis_data/dfimoveis.sqlite3 Ele deve ser tratado como estritamente somente leitura durante análises. Nunca grave resultados derivados dentro dele. +O schema v2 preserva os valores brutos e oferece campos normalizados, quarentenas e visões auditáveis. Consultas de fotos devem preferir `v_primary_photos`; a migração idempotente está em `scripts/migrate_dfimoveis_schema_v2.py`. + Quando não houver processo de coleta e não existir WAL relevante: ```python @@ -180,15 +176,15 @@ reports// Extração tabular somente leitura: ```powershell -.\.venv-scraper\Scripts\python.exe .\reports\cruzeiro_top10\analysis.py -.\.venv-scraper\Scripts\python.exe .\reports\mangueiral_top10\analysis.py +.\.venv-scraper\Scripts\python.exe .\reports\cruzeiro_top15\analysis.py +.\.venv-scraper\Scripts\python.exe .\reports\mangueiral_top15\analysis.py ``` Triagem de possíveis duplicatas por similaridade visual: ```powershell -.\.venv-scraper\Scripts\python.exe .\reports\cruzeiro_top10\photo_similarity.py -.\.venv-scraper\Scripts\python.exe .\reports\mangueiral_top10\photo_similarity.py +.\.venv-scraper\Scripts\python.exe .\reports\cruzeiro_top15\photo_similarity.py +.\.venv-scraper\Scripts\python.exe .\reports\mangueiral_top15\photo_similarity.py ``` Similaridade de imagem é apenas um sinal para revisão humana, nunca prova automática de que dois anúncios representam o mesmo imóvel. diff --git a/__pycache__/dfimoveis_scraper.cpython-314.pyc b/__pycache__/dfimoveis_scraper.cpython-314.pyc index 97f8133f96abd1ce3beb780eb3a825261620f872..accb37e45d5af5b50c5715843dd70686c0e0c16d 100644 GIT binary patch delta 39629 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 z str: return re.sub(r"[^a-zA-Z0-9._-]+", "_", value).strip("_")[:120] or "unnamed" +def normalize_creci(value: str | None) -> str | None: + """Return only the registration number, preserving the raw portal value elsewhere.""" + if not value: + return None + match = re.search(r"(? str | None: + """Build a conservative location label without retaining portal UI text.""" + slug = urlparse(url).path.lower().replace("-", " ") + qc = re.search(r"\bqc\s*(\d{1,2})\b", slug) + street = re.search(r"\brua\s+([a-z0-9]+)\b", slug) + parts = ["Jardins Mangueiral"] if "jardins mangueiral" in slug else [] + if qc: + parts.append(f"QC {int(qc.group(1))}") + if street: + parts.append(f"Rua {street.group(1).upper()}") + if parts: + return " - ".join(parts) + # JSON-LD addresses are normally short. Reject known UI-contaminated fallbacks. + cleaned = clean_text(address) + return cleaned if cleaned and len(cleaned) <= 250 else None + + +def normalize_description_for_hash(value: str | None) -> str | None: + cleaned = clean_text(value) + if not cleaned: + return None + cleaned = re.sub( + r"(?:última|ultima)\s+atualiza(?:ção|cao)\s*:\s*\d{2}/\d{2}/\d{2,4}\s+\d{2}:\d{2}", + "última atualização: ", + cleaned, + flags=re.I, + ) + return cleaned + + @dataclasses.dataclass(slots=True) class Config: searches: list[str] @@ -245,35 +283,109 @@ class Database: status TEXT NOT NULL, stats_json TEXT NOT NULL DEFAULT '{}' ); + CREATE TABLE IF NOT EXISTS availability_observations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + listing_id TEXT NOT NULL, + observed_at TEXT NOT NULL, + portal_active INTEGER, + classification TEXT NOT NULL, + source TEXT NOT NULL, + evidence TEXT, + run_id INTEGER, + FOREIGN KEY (listing_id) REFERENCES listings(listing_id), + FOREIGN KEY (run_id) REFERENCES crawl_runs(id) + ); + CREATE TABLE IF NOT EXISTS data_quality_issues ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + listing_id TEXT, + issue_code TEXT NOT NULL, + severity TEXT NOT NULL, + evidence TEXT, + first_seen_at TEXT NOT NULL, + last_seen_at TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'open', + UNIQUE(listing_id, issue_code), + FOREIGN KEY (listing_id) REFERENCES listings(listing_id) + ); + CREATE TABLE IF NOT EXISTS listing_change_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + listing_id TEXT NOT NULL, + captured_at TEXT NOT NULL, + event_type TEXT NOT NULL, + changes_json TEXT NOT NULL, + stable_content_hash TEXT NOT NULL, + run_id INTEGER, + FOREIGN KEY (listing_id) REFERENCES listings(listing_id), + FOREIGN KEY (run_id) REFERENCES crawl_runs(id) + ); + """ + ) + self._ensure_column("listings", "address_normalized", "TEXT") + self._ensure_column("listings", "neighborhood_normalized", "TEXT") + self._ensure_column("listings", "creci_normalized", "TEXT") + self._ensure_column("listings", "raw_content_hash", "TEXT") + self._ensure_column("listings", "stable_content_hash", "TEXT") + self._ensure_column("photos", "discovery_source", "TEXT") + self._ensure_column("photos", "is_primary_gallery", "INTEGER NOT NULL DEFAULT 1") + self._ensure_column("photos", "review_status", "TEXT NOT NULL DEFAULT 'unreviewed'") + self._ensure_column("crawl_runs", "searches_json", "TEXT NOT NULL DEFAULT '[]'") + self._ensure_column("crawl_runs", "config_json", "TEXT NOT NULL DEFAULT '{}'") + self._ensure_column("crawl_runs", "scope_complete", "INTEGER") + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_listings_creci_normalized ON listings(creci_normalized)") + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_listings_inactive_at ON listings(inactive_at)") + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_photos_primary ON photos(listing_id,is_primary_gallery,ordinal)") + self.conn.execute("CREATE INDEX IF NOT EXISTS idx_change_events_listing ON listing_change_events(listing_id,captured_at)") + self.conn.executescript( + """ + CREATE VIEW IF NOT EXISTS v_primary_photos AS + SELECT * FROM photos WHERE is_primary_gallery=1; + CREATE VIEW IF NOT EXISTS v_open_data_quality_issues AS + SELECT * FROM data_quality_issues WHERE status='open'; + CREATE VIEW IF NOT EXISTS v_listing_current_availability AS + SELECT l.listing_id,l.url,(l.inactive_at IS NULL) AS portal_active,l.inactive_at, + a.classification,a.source,a.evidence,a.observed_at + FROM listings AS l + LEFT JOIN availability_observations AS a ON a.id=( + SELECT a2.id FROM availability_observations AS a2 + WHERE a2.listing_id=l.listing_id ORDER BY a2.observed_at DESC,a2.id DESC LIMIT 1 + ); """ ) self.conn.commit() - def start_run(self) -> int: + def _ensure_column(self, table: str, column: str, declaration: str) -> None: + existing = {row[1] for row in self.conn.execute(f"PRAGMA table_info({table})")} + if column not in existing: + self.conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {declaration}") + + def start_run(self, searches: list[str], config: dict[str, Any]) -> int: cur = self.conn.execute( - "INSERT INTO crawl_runs(started_at,status) VALUES(?,?)", (now_utc(), "running") + "INSERT INTO crawl_runs(started_at,status,searches_json,config_json,scope_complete) VALUES(?,?,?,?,?)", + (now_utc(), "running", json.dumps(searches, ensure_ascii=False), json.dumps(config, ensure_ascii=False), 0), ) self.conn.commit() return int(cur.lastrowid) def finish_run(self, run_id: int, status: str, stats: dict[str, Any]) -> None: self.conn.execute( - "UPDATE crawl_runs SET finished_at=?, status=?, stats_json=? WHERE id=?", - (now_utc(), status, json.dumps(stats, ensure_ascii=False), run_id), + "UPDATE crawl_runs SET finished_at=?, status=?, stats_json=?, scope_complete=? WHERE id=?", + (now_utc(), status, json.dumps(stats, ensure_ascii=False), int(status == "ok" and stats.get("details_failed", 0) == 0), run_id), ) self.conn.commit() - def upsert_listing(self, data: dict[str, Any], search_url: str) -> bool: + def upsert_listing(self, data: dict[str, Any], search_url: str, run_id: int | None = None) -> bool: ts = now_utc() existing = self.conn.execute( - "SELECT content_hash FROM listings WHERE listing_id=?", (data["listing_id"],) + "SELECT * FROM listings WHERE listing_id=?", (data["listing_id"],) ).fetchone() - changed = not existing or existing["content_hash"] != data["content_hash"] + changed = not existing or existing["stable_content_hash"] != data["stable_content_hash"] cols = [ "listing_id", "url", "title", "description", "transaction_type", "property_type", "price_brl", "condominium_brl", "iptu_brl", "area_m2", "bedrooms", "suites", "parking_spaces", "address", "neighborhood", "city", "state", "advertiser_name", - "advertiser_code", "creci", "latitude", "longitude", "published_at", "content_hash", + "advertiser_code", "creci", "address_normalized", "neighborhood_normalized", + "creci_normalized", "latitude", "longitude", "published_at", "content_hash", + "raw_content_hash", "stable_content_hash", "raw_html_path", "extra_json" ] values = [data.get(c) for c in cols] @@ -300,24 +412,64 @@ class Database: snapshot = {k: v for k, v in data.items() if k not in {"raw_html_path"}} self.conn.execute( "INSERT OR IGNORE INTO listing_history(listing_id,captured_at,content_hash,data_json) VALUES(?,?,?,?)", - (data["listing_id"], ts, data["content_hash"], json.dumps(snapshot, ensure_ascii=False)), + (data["listing_id"], ts, data["stable_content_hash"], json.dumps(snapshot, ensure_ascii=False)), ) + tracked = [ + "url", "title", "description", "transaction_type", "property_type", "price_brl", + "condominium_brl", "iptu_brl", "area_m2", "bedrooms", "suites", "parking_spaces", + "address_normalized", "neighborhood_normalized", "advertiser_name", "creci_normalized", + "latitude", "longitude", "published_at", + ] + changes = ( + {key: {"before": existing[key], "after": data.get(key)} for key in tracked if existing[key] != data.get(key)} + if existing else {"listing": {"before": None, "after": "created"}} + ) + event_type = "new_listing" if not existing else ("price_change" if "price_brl" in changes else "content_change") + self.conn.execute( + """INSERT INTO listing_change_events + (listing_id,captured_at,event_type,changes_json,stable_content_hash,run_id) + VALUES(?,?,?,?,?,?)""", + (data["listing_id"], ts, event_type, json.dumps(changes, ensure_ascii=False), data["stable_content_hash"], run_id), + ) + self._record_automatic_quality_issues(data, ts) self.conn.commit() return changed + def _record_automatic_quality_issues(self, data: dict[str, Any], ts: str) -> None: + checks: list[tuple[str, str, str]] = [] + if data.get("price_brl") and data["price_brl"] > 20_000_000: + checks.append(("implausible_price", "high", str(data["price_brl"]))) + if data.get("area_m2") and data["area_m2"] < 10: + checks.append(("implausible_area", "high", str(data["area_m2"]))) + description = (data.get("description") or "").lower() + if "procuro casa para comprar" in description: + checks.append(("source_mismatch_buyer_request", "high", "description declares a purchase request")) + if "quarto adaptado" in description or "escritório adaptado" in description: + checks.append(("adapted_room", "medium", "description mentions an adapted room")) + for code, severity, evidence in checks: + self.conn.execute( + """INSERT INTO data_quality_issues(listing_id,issue_code,severity,evidence,first_seen_at,last_seen_at) + VALUES(?,?,?,?,?,?) ON CONFLICT(listing_id,issue_code) DO UPDATE SET + severity=excluded.severity,evidence=excluded.evidence,last_seen_at=excluded.last_seen_at,status='open'""", + (data["listing_id"], code, severity, evidence, ts, ts), + ) + def upsert_photo(self, listing_id: str, ordinal: int, url: str, **meta: Any) -> None: ts = now_utc() self.conn.execute( """ - INSERT INTO photos(listing_id,ordinal,source_url,sha256,mime_type,bytes,local_path,first_seen_at,last_seen_at) - VALUES(?,?,?,?,?,?,?,?,?) + INSERT INTO photos(listing_id,ordinal,source_url,sha256,mime_type,bytes,local_path,first_seen_at,last_seen_at,discovery_source,is_primary_gallery,review_status) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(listing_id,source_url) DO UPDATE SET ordinal=excluded.ordinal, sha256=COALESCE(excluded.sha256,photos.sha256), mime_type=COALESCE(excluded.mime_type,photos.mime_type), bytes=COALESCE(excluded.bytes,photos.bytes), - local_path=COALESCE(excluded.local_path,photos.local_path), last_seen_at=excluded.last_seen_at + local_path=COALESCE(excluded.local_path,photos.local_path), last_seen_at=excluded.last_seen_at, + discovery_source=COALESCE(excluded.discovery_source,photos.discovery_source), + is_primary_gallery=excluded.is_primary_gallery """, - (listing_id, ordinal, url, meta.get("sha256"), meta.get("mime_type"), meta.get("bytes"), meta.get("local_path"), ts, ts), + (listing_id, ordinal, url, meta.get("sha256"), meta.get("mime_type"), meta.get("bytes"), meta.get("local_path"), ts, ts, + meta.get("discovery_source", "listing_id_url"), int(meta.get("is_primary_gallery", True)), meta.get("review_status", "unreviewed")), ) self.conn.commit() @@ -329,7 +481,9 @@ class Database: def mark_inactive(self, search_url: str, seen_ids: set[str], run_started_at: str) -> int: rows = self.conn.execute( - "SELECT listing_id FROM listing_searches WHERE search_url=? AND last_seen_at < ?", + """SELECT s.listing_id FROM listing_searches AS s + JOIN listings AS l USING(listing_id) + WHERE s.search_url=? AND s.last_seen_at < ? AND l.inactive_at IS NULL""", (search_url, run_started_at), ).fetchall() stale = [r[0] for r in rows if r[0] not in seen_ids] @@ -926,8 +1080,14 @@ class Parser: for match in IMAGE_URL_RE.finditer(page_html.replace("\\/", "/")): image_urls[html.unescape(match.group(0))] = None - # Exclude common site chrome and tiny assets based on URL hints. - photos = [u for u in image_urls if not re.search(r"logo|favicon|sprite|icon|avatar|banner", u, re.I)] + # The portal renders recommendations on the same page. Only URLs whose + # /fotos// owner matches this listing belong to the primary gallery. + owner_re = re.compile(r"/fotos/(\d+)/", re.I) + photos = [] + for image_url in image_urls: + owner = owner_re.search(urlparse(image_url).path) + if owner and owner.group(1) == listing_id: + photos.append(image_url) data = { "listing_id": listing_id, @@ -956,8 +1116,35 @@ class Parser: "raw_html_path": raw_path, "extra_json": json.dumps({"json_ld": ld}, ensure_ascii=False), } - stable = {k: v for k, v in data.items() if k not in {"content_hash", "raw_html_path"}} - data["content_hash"] = hashlib.sha256(json.dumps(stable, sort_keys=True, ensure_ascii=False).encode()).hexdigest() + data["address_normalized"] = normalize_listing_address(data["url"], data["address"]) + data["neighborhood_normalized"] = "Jardins Mangueiral" if "jardins-mangueiral" in data["url"] else clean_text(data["neighborhood"]) + data["creci_normalized"] = normalize_creci(data["creci"]) + raw_payload = {k: v for k, v in data.items() if k not in {"content_hash", "raw_html_path"}} + data["raw_content_hash"] = hashlib.sha256(json.dumps(raw_payload, sort_keys=True, ensure_ascii=False).encode()).hexdigest() + stable_payload = { + "url": data["url"], + "title": data["title"], + "description": normalize_description_for_hash(data["description"]), + "transaction_type": data["transaction_type"], + "property_type": data["property_type"], + "price_brl": data["price_brl"], + "condominium_brl": data["condominium_brl"], + "iptu_brl": data["iptu_brl"], + "area_m2": data["area_m2"], + "bedrooms": data["bedrooms"], + "suites": data["suites"], + "parking_spaces": data["parking_spaces"], + "address_normalized": data["address_normalized"], + "neighborhood_normalized": data["neighborhood_normalized"], + "advertiser_name": data["advertiser_name"], + "creci_normalized": data["creci_normalized"], + "latitude": data["latitude"], + "longitude": data["longitude"], + "published_at": data["published_at"], + } + data["stable_content_hash"] = hashlib.sha256(json.dumps(stable_payload, sort_keys=True, ensure_ascii=False).encode()).hexdigest() + # Retain the legacy column for compatibility, but give it the stable meaning from schema v2 onward. + data["content_hash"] = data["stable_content_hash"] return data, photos @@ -1007,7 +1194,7 @@ class Scraper: page_html, body, final_url = await self.fetcher.get_html(url) raw_path = self.save_raw("listing", listing_id_from_url(url) or "unknown", body) if self.cfg.save_raw_html else None data, photos = Parser.detail(final_url, page_html, raw_path) - changed = self.db.upsert_listing(data, search_url) + changed = self.db.upsert_listing(data, search_url, self.run_id) self.stats["details_ok"] += 1 self.stats["changed"] += int(changed) logging.info("Listing %s: %s; %d photos", data["listing_id"], "changed" if changed else "unchanged", len(photos)) @@ -1049,7 +1236,18 @@ class Scraper: logging.exception("Failed photo for %s: %s", listing_id, url) async def run(self) -> dict[str, Any]: - run_id = self.db.start_run() + run_config = { + "max_pages": self.cfg.max_pages, + "concurrency": self.cfg.concurrency, + "photo_concurrency": self.cfg.photo_concurrency, + "download_photos": self.cfg.download_photos, + "save_raw_html": self.cfg.save_raw_html, + "use_playwright": self.cfg.use_playwright, + "browser_channel": self.cfg.browser_channel, + "attach_chrome": self.cfg.attach_chrome, + } + run_id = self.db.start_run(self.cfg.searches, run_config) + self.run_id = run_id status = "ok" try: for search_url in self.cfg.searches: @@ -1212,4 +1410,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/docs/ACTUAL_SCHEMA.md b/docs/ACTUAL_SCHEMA.md index b80e3fe..6cd2b48 100644 --- a/docs/ACTUAL_SCHEMA.md +++ b/docs/ACTUAL_SCHEMA.md @@ -1,23 +1,24 @@ # 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 em 13 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. +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 | 5.017.600 bytes | -| SHA-256 | `1ce3128b488c50c993d7328ac6e24b5905f083a7e04e3f636407eea1edd02b9d` | -| Modificação do arquivo | `2026-07-13 14:20:34.601285975 -03:00` | -| SQLite | 3.45.1 | +| 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 | 1.225 páginas de 4.096 bytes; `freelist_count = 0` | +| Páginas | 2.314 páginas de 4.096 bytes; `freelist_count = 2` | | `user_version` / `application_id` | 0 / 0 | -| Método de abertura | `sqlite3 -readonly` e URI `mode=ro&immutable=1`, sempre com `PRAGMA query_only = ON` | +| Método de abertura | URI `mode=ro`, `PRAGMA query_only = ON` e transação de leitura | -Não havia arquivo `-wal` ou `-shm` nem processo com o banco aberto na verificação inicial. Isso permitiu tratar o conteúdo como snapshot imutável. `PRAGMA quick_check` retornou `ok` e `PRAGMA foreign_key_check` não retornou violações. Na validação final, o cliente SQLite havia criado os sidecars de runtime `dfimoveis.sqlite3-shm` (32.768 bytes) e `dfimoveis.sqlite3-wal` (vazio), comportamento possível ao abrir em leitura um banco configurado em WAL. Eles não foram removidos. O arquivo principal manteve tamanho, `mtime` e SHA-256 idênticos. +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 @@ -72,21 +73,21 @@ erDiagram | Tabela | Linhas | Interpretação física | |---|---:|---| -| `crawl_runs` | 4 | Execuções do coletor | -| `listings` | 156 | Identidades de anúncios e seu estado corrente | -| `listing_history` | 156 | Estados distintos dos anúncios por hash | -| `listing_searches` | 156 | Vínculos entre anúncio e URL de busca | -| `photos` | 5.386 | Mídias associadas diretamente aos anúncios | +| `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 possui exatamente um registro em `listing_history`; - cada anúncio está ligado a exatamente uma URL de busca; -- cada anúncio possui de 5 a 61 linhas em `photos`; +- 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`; -- os 156 hashes de `listing_history` coincidem com o hash corrente em `listings`; -- `captured_at` coincide com `first_seen_at` nos 156 anúncios. +- 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 @@ -99,10 +100,10 @@ Representa execuções técnicas do coletor. Não registra fonte, versão do scr | `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; somente `ok` na amostra | +| `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 quatro estruturas JSON são válidas. As chaves presentes são `search_pages`, `listing_urls`, `details_ok`, `details_failed`, `changed`, `photos_ok`, `photos_failed` e `inactive`, todas inteiras. +As estruturas JSON observadas são válidas. Além das chaves originais, execuções recentes também registram `photos_skipped`. ### 3.2 `listings` @@ -219,20 +220,64 @@ As FKs de `listing_history`, `listing_searches` e `photos` apontam para `listing 1. `listing_id` identifica um **anúncio no portal**, não um imóvel físico. 2. 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 em `last_seen_at`. -3. O banco ainda não contém histórico suficiente para preços ou permanência: há um snapshot por anúncio e todos os anúncios têm `first_seen_at = last_seen_at`. +3. 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. 4. 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. 5. `area_m2` não deve ser usada para preço por m² até que o conceito de área seja validado por tipologia e fonte. 6. `bedrooms` é a quantidade anunciada. Ela não comprova a planta original de três quartos exigida para Jardins Mangueiral. -7. Datas com sufixo `+00:00` são tratadas como UTC. A cobertura observada é de um único dia. +7. Datas com sufixo `+00:00` são tratadas como UTC. A cobertura observada vai de 13 a 21/07/2026. 8. 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 -- 146 de 156 endereços têm mais de 300 caracteres; 154 de 156 bairros têm mais de 100 caracteres. Os dois bairros curtos também não formam valores confiáveis. +- 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_at` e `advertiser_name` estão integralmente ausentes. -- 98 códigos de anunciante e 153 valores de CRECI são longos demais para o significado esperado, indicando extração contaminada. +- 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 anúncios e não oferece uma fonte estruturada alternativa no snapshot. -- Três downloads de foto falharam e deixaram linhas sem hash, MIME, tamanho e caminho local. -- Hashes de foto idênticos aparecem em muitos anúncios, inclusive um hash presente nos 156; isso demonstra a presença de ativos genéricos e impede usar igualdade de hash como prova isolada de imóvel duplicado. +- 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//` 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_check` retornou `ok` após a migração. diff --git a/docs/ANALYSIS_RULES.md b/docs/ANALYSIS_RULES.md index 7bd3e70..7573507 100644 --- a/docs/ANALYSIS_RULES.md +++ b/docs/ANALYSIS_RULES.md @@ -1,490 +1,283 @@ # Regras de análise -## 1. Objetivo metodológico +## 1. Unidade de análise -Produzir análises reproduzíveis e úteis para decisão de compra, sem transformar dados de anúncios em uma falsa precisão de mercado. +Definir explicitamente: -Toda conclusão deve distinguir: - -- o que os dados mostram; -- o que foi inferido; -- o que permanece desconhecido; -- o que depende de visita, documentação ou negociação. - ---- - -## 2. Unidade de análise - -Definir antes de calcular: - -- anúncio único; -- snapshot de anúncio; +- anúncio; +- observação; - imóvel físico provável; -- imóvel físico confirmado; - região; -- tipologia; -- período. - -Não misturar unidades. - -Exemplo de erro: - -> Contar cada snapshot mensal como um imóvel diferente. - -Exemplo correto: +- período; +- snapshot. -> Para estoque histórico, usar um anúncio ativo por data; para oferta única, deduplicar anúncios e imóveis prováveis. +Não contar cada observação como imóvel novo. --- -## 3. Cobertura e consistência temporal - -Antes de comparar meses: +## 2. Cobertura -- verificar dias coletados; -- verificar fontes ativas; -- verificar falhas; -- verificar mudanças do scraper; -- verificar novos filtros; -- verificar alterações no site; -- verificar interrupções; -- verificar mudanças de campos. +Antes de comparar períodos: -Uma queda no número de anúncios pode ser falha de coleta. +- conferir dias coletados; +- falhas e 403; +- fontes e pesquisas ativas; +- mudanças do scraper; +- cobertura de fotos; +- inatividade artificial. -Relatórios temporais devem exibir cobertura. +O snapshot de 13–21/07/2026 não permite inferir liquidez. --- -## 4. Limpeza +## 3. Limpeza -### 4.1 Preços inválidos +### Preço -Identificar: +Colocar em quarentena: - zero; -- preço mensal; -- preço de aluguel em anúncio de venda; -- valor parcial; +- preço de aluguel em venda; - erro de escala; -- preço simbólico; -- preço por fração; -- moeda incorreta; -- preço inconsistente com descrição. +- valor simbólico; +- valor incompatível com descrição; +- casos como R$ 570 milhões quando o contexto sugere erro. -Não remover silenciosamente. Registrar regra e contagem. +### Área -### 4.2 Áreas inválidas +Verificar: -Identificar: +- conceito da área; +- casa ampliada; +- terreno versus área construída; +- erro de decimal; +- divergência entre campos e descrição. -- zero; -- área total usada como privativa; -- área do terreno; -- área multiplicada; -- vírgula decimal interpretada incorretamente; -- área do condomínio; -- área incompatível com a tipologia. - -### 4.3 Quartos e tipologia +### Planta -Verificar divergências entre: +Cruzar: - título; - campos estruturados; - descrição; -- planta; -- fotografias; -- tipologia original. - -Para Jardins Mangueiral, não aceitar tipologia anunciada como prova da planta original. +- fotos; +- planta padrão; +- endereço e implantação. --- -## 5. Preço por metro quadrado - -Calcular somente quando: - -- o preço é válido; -- a área é válida; -- a área representa conceito comparável; -- o imóvel pertence a uma tipologia comparável. - -Registrar a área usada. +## 4. Preço por metro quadrado -Não comparar diretamente: +Não usar como métrica principal no estado atual do banco. -- área privativa com área total; -- casa com apartamento; -- imóvel reformado com imóvel para reforma sem controle; -- dois quartos com três quartos apenas pela média regional; -- cobertura com unidade padrão; -- casa ampliada com planta original sem ajustar a área. +Somente calcular quando: -Estatísticas mínimas: +- preço é válido; +- área é validada e comparável; +- tipologia é homogênea; +- ampliação é tratada corretamente. -- número de observações; -- mediana; -- percentil 25; -- percentil 75; -- mínimo e máximo após validação; -- proporção excluída. +Os rankings de 21/07/2026 não usaram preço por m² porque os campos de área não eram uniformes. --- -## 6. Outliers +## 5. Fotografias -Não excluir outliers apenas porque são extremos. +A avaliação deve incluir: -Investigar se representam: - -- erro; -- imóvel premium; -- reforma; -- venda urgente; -- leilão; -- área incorreta; -- anúncio duplicado; -- preço desatualizado; -- unidade atípica. +- primeira foto; +- galeria completa; +- coerência de planta; +- condição aparente; +- iluminação e ventilação; +- qualidade da reforma; +- ampliação e cobertura; +- duplicatas visuais; +- selo de vendido. -Apresentar resultados com e sem outliers quando a conclusão mudar. +Registrar limitações quando a galeria for curta ou não mostrar toda a planta. -Métodos permitidos: +--- -- IQR; -- MAD; -- limites específicos por tipologia; -- inspeção manual; -- modelos robustos. +## 6. Duplicatas -Não usar corte automático sem explicar. +- identificar grupos de republicação; +- contar como um imóvel provável no ranking; +- preservar todos os anúncios no histórico; +- definir anúncio de referência; +- registrar divergências de endereço, área e preço; +- testar sensibilidade quando a deduplicação for incerta. --- -## 7. Duplicidades +## 7. Ranking de imóveis + +O ranking serve para ordenar investigação. -Relatar: +Critérios comuns: -- duplicatas dentro da mesma fonte; -- duplicatas entre fontes; -- snapshots repetidos; -- imóveis anunciados por vários corretores; -- anúncios republicados com novo identificador. +- quartos utilizáveis; +- planta; +- funcionalidade; +- condição aparente; +- iluminação e ventilação; +- localização; +- acessibilidade; +- elevador; +- garagem; +- custo; +- regularidade; +- qualidade da evidência. -Para preço de oferta atual, escolher uma regra explícita, por exemplo: +### Mangueiral -- menor preço vigente por imóvel provável; -- anúncio mais recente; -- anúncio primário; -- mediana entre anúncios do mesmo imóvel. +Adicionar: -Testar sensibilidade da conclusão à regra. +- planta original de três quartos; +- QC; +- conversão do quintal; +- calor e ventilação; +- cobertura, drenagem e impermeabilização; +- regularidade da ampliação; +- ruído de vizinhos e áreas comuns. + +Aplicar também as preferências confirmadas: + +- favorecer cozinha funcional e gourmet conectado ao uso cotidiano; +- favorecer conservação e mudança com poucas intervenções; +- tratar piscina, jacuzzi e banheira como incompatibilidades funcionais, não amenidades positivas; +- penalizar cubas de vidro, marcenaria degradada, gourmet isolado e área externa sem uso coerente; +- testar calor e ventilação em fechamentos envidraçados; +- distinguir nota dada diretamente pelo usuário de aderência inferida pelo analista; +- quando novo feedback alterar o funil de entrada, reprocessar o universo completo em vez de apenas reordenar a shortlist existente. ---- +### Cruzeiro -## 8. Histórico de anúncios +Adicionar: -Para cada anúncio ou imóvel provável, tentar identificar: +- ranking interno da quadra; +- andar; +- elevador; +- garagem; +- posição do bloco; +- sistemas do prédio; +- reforma real versus estética. -- primeira aparição; -- última aparição; -- tempo observado; -- mudanças de preço; -- períodos de ausência; -- republicação; -- alteração de corretor; -- alteração de descrição; -- alteração de área ou quartos. +Não criar pesos secretos. Se houver pontuação, documentar pesos e sensibilidade. -Não concluir “vendido” apenas porque desapareceu. +--- -Estados possíveis: +## 8. Outliers -- ativo; -- ausente na coleta; -- removido; -- republicado; -- provável venda; -- status desconhecido. +Investigar antes de excluir: -“Provável venda” exige sinais adicionais. +- erro; +- imóvel premium; +- reforma; +- venda urgente; +- leilão; +- área incorreta; +- duplicata; +- preço desatualizado. + +Apresentar resultados com e sem outliers quando a conclusão mudar. --- ## 9. Comparáveis -Um conjunto de comparáveis deve controlar, quando possível: +Controlar, quando possível: -- região; -- quadra, bloco ou QC; -- tipo de imóvel; +- região e micro-localização; +- tipo; - planta original; - quartos; -- área; +- área validada; - garagem; - elevador; - andar; -- posição; -- reforma; -- conservação; +- estado; +- ampliação; - data; - condomínio; -- situação de ocupação. - -Quando não for possível controlar, declarar a limitação. +- ocupação. -Nunca usar uma média ampla do bairro como avaliação definitiva de uma unidade específica. +Média ampla do bairro não define preço justo de unidade específica. --- -## 10. Tendências +## 10. Tendência e liquidez -Para avaliar tendência de preço: +Para tendência: -- usar imóveis comparáveis; -- controlar mudanças no mix; -- ponderar cobertura; -- evitar média simples; -- usar mediana e regressões robustas quando necessário; -- separar preço de entrada, preço atual e último preço; -- mostrar intervalo de confiança ou dispersão; -- evitar extrapolação longa. +- aumentar a janela histórica; +- controlar mudança de mix; +- usar mediana e métodos robustos; +- separar entrada, preço atual e último preço; +- mostrar dispersão. -Uma alta no preço mediano pode ocorrer porque imóveis menores desapareceram da amostra. - ---- +Para liquidez: -## 11. Estoque e liquidez - -Métricas úteis: - -- anúncios ativos; -- imóveis prováveis ativos; -- entradas; -- saídas; -- republicações; +- entradas e saídas; - tempo observado; +- republicações; - reduções de preço; -- percentual com múltiplas reduções; -- concentração por corretor; -- dispersão de preços; -- taxa de reposição do estoque. - -Limitações: - -- tempo observado não é tempo real no mercado quando a coleta começou depois; -- retirada não confirma venda; -- republicação pode reiniciar o relógio artificialmente. - ---- - -## 12. Análise financeira - -### 12.1 Premissas obrigatórias - -Toda simulação deve declarar: - -- preço; -- entrada em dinheiro; -- FGTS; -- reserva preservada; -- custos de aquisição; -- reforma; -- valor financiado; -- taxa efetiva; -- CET, se disponível; -- sistema; -- prazo; -- seguros e tarifas; -- condomínio; -- IPTU; -- manutenção; -- reajuste do aluguel; -- retorno líquido dos investimentos; -- inflação; -- valorização; -- horizonte; -- custos de venda; -- estratégia de amortização. - -### 12.2 Liquidez - -Separar: - -- dinheiro; -- investimentos com liquidez; -- FGTS; -- patrimônio imobiliário; -- reserva mínima. - -Não tratar FGTS como reserva de emergência. - -### 12.3 Compra versus aluguel - -Comparar: - -- fluxo mensal; -- patrimônio líquido; -- custo de oportunidade; -- risco; -- liquidez; -- flexibilidade; -- estabilidade; -- manutenção; -- impostos; -- custos de transação. - -Mostrar cenários: - -- conservador; -- central; -- otimista. - -Não decidir apenas pelo patrimônio final médio. - -### 12.4 Estresse - -Testar: - -- juros maiores; -- renda menor; -- despesas extraordinárias; -- reforma acima do orçamento; -- vacância ou dificuldade de revenda; -- valorização baixa; -- investimento com retorno menor; -- manutenção elevada; -- redução da capacidade de poupança. - ---- +- anúncios ativos; +- imóveis prováveis ativos. -## 13. Consórcio - -Para analisar um grupo: - -- usar histórico do próprio grupo; -- registrar assembleias; -- lances vencedores; -- lance livre; -- lance fixo; -- lance embutido; -- número de contemplações; -- saldo; -- reajuste da carta; -- taxa de administração; -- fundo de reserva; -- seguros; -- regras de uso; -- prazo; -- valor total pago. - -Não inferir probabilidade de contemplação usando apenas média histórica. - -Analisar: - -- distribuição; -- percentis; -- sazonalidade; -- número de concorrentes; -- mudanças de regra; -- eventos de renda; -- concentração de lances; -- sensibilidade ao valor ofertado. - -Relatos de gerente são hipóteses a testar. +Retirada não confirma venda. --- -## 14. Relatórios por região - -### Cruzeiro +## 11. Integração financeira -Separar por: +Não recomendar imóvel apenas pelo ranking físico. -- Cruzeiro Novo e Velho; -- quadra; -- bloco; -- tipologia; -- área; -- elevador; -- garagem; -- andar; -- conservação; -- reforma. +Calcular: -### Jardins Mangueiral +- preço provável negociado; +- custos de compra; +- reforma e regularização; +- entrada e FGTS; +- reserva remanescente; +- financiamento e CET; +- prestação inicial; +- custo mensal total; +- cenário de amortização; +- comparação com aluguel. -Separar por: - -- QC; -- planta original de três quartos; -- área construída; -- ampliação; -- qualidade da área fechada; -- condomínio; -- posição; -- ruído; -- proximidade de serviços; -- padrão de reforma. - -Excluir casas originalmente de dois quartos. +A classificação final pode ser reduzida se o imóvel exigir consumir a reserva. --- -## 15. Relatório padrão - -### Resumo executivo - -- conclusão principal; -- confiança; -- implicação prática; -- maior risco. - -### Dados - -- fontes; -- período; -- cobertura; -- tamanho da amostra; -- exclusões; -- deduplicação. - -### Resultados - -- tabelas; -- distribuições; -- comparáveis; -- tendências; -- sensibilidade. - -### Interpretação - -- o que é sustentado; -- o que é apenas sugestivo; -- o que requer visita; -- o que requer confirmação financeira ou documental. - -### Próxima ação - -Classificar como: - -- ignorar; -- monitorar; -- aprofundar; -- visitar; -- negociar; -- candidato forte; -- decisão insuficiente. +## 12. Saída padrão + +Cada ficha deve conter: + +- anúncio e URL; +- snapshot; +- localização; +- tipologia original provável; +- preço e histórico; +- áreas e confiabilidade; +- quartos, vagas, elevador e andar; +- possíveis duplicatas; +- status visual; +- pontos fortes; +- pontos fracos; +- custos e riscos; +- pendências de visita e documentação; +- posição relativa; +- próxima ação. --- -## 16. Reprodutibilidade +## 13. Reprodutibilidade -Para cada relatório, salvar: +Estrutura recomendada: ```text -reports// +reports// ├── README.md ├── query.sql ├── analysis.py @@ -493,50 +286,25 @@ reports// └── figures/ ``` -Registrar: - -- data; -- hash do código; -- identificação do banco; -- filtros; -- versão; -- ambiente; -- contagens. +Registrar snapshot, hash do código, filtros, contagens e julgamento manual. --- -## 17. Linguagem das conclusões +## 14. Linguagem Preferir: -- “os anúncios observados sugerem”; -- “na amostra coletada”; +- “na amostra observada”; - “após deduplicação”; -- “não há evidência suficiente”; -- “a conclusão é sensível a”; -- “o dado não permite afirmar”. +- “as fotos sugerem”; +- “exige confirmação”; +- “não há série suficiente”; +- “a conclusão é sensível a”. Evitar: -- “o mercado vale”; - “foi vendido” sem prova; -- “esta é a melhor quadra” sem critério; -- “o preço justo é” sem intervalo e comparáveis; -- “vai valorizar” como certeza; -- “o lance será contemplado” como previsão. - ---- - -## 18. Critério final - -Uma análise é boa quando ajuda a evitar: - -- comprar cedo demais; -- pagar por uma reforma disfarçada; -- confundir anúncio com transação; -- superestimar valorização; -- subestimar custos; -- comprometer liquidez; -- aceitar uma planta inadequada; -- tomar decisão por ansiedade; -- descartar um bom imóvel por uma métrica superficial. +- “preço justo” sem intervalo; +- “vai valorizar”; +- “melhor quadra” sem critério; +- “contemplação provável” sem dados do grupo. diff --git a/docs/CONTEXT_CHANGELOG.md b/docs/CONTEXT_CHANGELOG.md new file mode 100644 index 0000000..fd4b3f4 --- /dev/null +++ b/docs/CONTEXT_CHANGELOG.md @@ -0,0 +1,90 @@ +# Histórico de mudanças do contexto + +## 22/07/2026 — versão 9 + +- relatório Top 15 do Mangueiral refeito isoladamente sobre a execução 22 e o schema v2; +- snapshot consolidado em 95 anúncios do Mangueiral, 78 ativos e 17 inativos; banco com 162 anúncios, 638 observações e 7.457 registros de foto; +- análise fotográfica passou a admitir somente os 3.521 registros da galeria primária; +- teto eliminatório de R$ 750 mil aplicado ao ranking, removendo `1352633` (R$ 755 mil); +- `1401074`, QC 13 e R$ 720 mil, entrou em 8º após revisão da primeira foto, descrição e galeria completa; aderência permanece inferida e pendente de avaliação do usuário; +- `1290512` registrado como inativo sem inferência de venda; +- `348115` atualizado de R$ 530 mil para R$ 550 mil conforme evento material registrado na execução 22. + +## 22/07/2026 — versão 8 + +- confirmado pelo usuário o preço máximo tolerado de **R$ 750.000** para o imóvel; +- registrado que esse valor é teto, não meta de gasto, e não inclui custos de compra, mudança, reforma ou regularização; +- preservada a exigência de ao menos R$ 50.000 de reserva líquida após todos os desembolsos. + +## 22/07/2026 — versão 7 + +- primeira execução após a migração validada: 78 detalhes, zero falhas e apenas uma mudança material (`348115`, de R$ 530 mil para R$ 550 mil); +- captura restrita às galerias primárias reutilizou 1.854 fotos e não adicionou recomendações do portal; +- corrigido o contador `inactive` para contar apenas novas inativações, não repetir todo o estoque já inativo; +- execução 21 concluída somente para o Mangueiral: 95 acumulados, 78 ativos e 17 inativos; +- novo anúncio `1401074`; `1290512` inativado sem inferência de venda; `1346118` alterado de R$ 530 mil para R$ 550 mil; +- criado backup consistente anterior à migração v2; +- schema migrado de forma aditiva, preservando integralmente anúncios, histórico, fotos e execuções; +- adicionadas normalização de CRECI/localização, hash estável, escopo de execução, disponibilidade, eventos materiais e quarentenas; +- identificados 3.936 registros legados fora da galeria principal; 3.286 apontam para fotos de outros IDs e o restante para ativos do portal; +- relatórios analíticos passaram a usar somente `is_primary_gallery=1`. + +## 21/07/2026 — versão 6 + +Revisão integral após a execução 20 do scraper. + +Mudanças: + +- snapshot atualizado para 161 anúncios, 562 observações e 7.363 fotos; +- Mangueiral consolidado em 94 anúncios acumulados, 78 ativos e 16 inativos; +- Cruzeiro consolidado em 67 anúncios acumulados, 63 ativos e 4 inativos; +- documentado que 398 de 401 transições históricas alteram `creci` contaminado e só três anúncios tiveram mudança real de preço; +- revalidadas dez quarentenas por selo visual de vendido, uma no Cruzeiro e nove no Mangueiral; +- `1356197` removido do Top 15 por novo selo visual e substituído por `1290502`; +- triagem de similaridade fotográfica reexecutada com Pillow 12.3.0, sem alteração dos rankings; +- recebida avaliação direta de 1 a 5 dos 15 finalistas do Mangueiral; após reprocessar todo o universo ativo, `1279892`, `1319996` e `1377683` passam a formar o Top 3, sendo a aderência do terceiro ainda inferida; +- confirmada preferência por cozinha funcional e integrada ao gourmet, conservação e baixa necessidade de intervenção; +- piscina, jacuzzi e banheira confirmadas como incompatibilidades; cubas de vidro, marcenaria ruim, gourmet isolado e fechamento envidraçado quente como fortes negativos; +- análise do Mangueiral refeita sobre os 78 anúncios ativos, evitando limitar o feedback aos 15 antigos; +- `1377683`, `1371718` e `1140317` incluídos no Top 5 por aderência inferida, explicitamente pendente de avaliação do usuário; +- auditadas as primeiras fotos dos 78 anúncios ativos; `1301400` foi incluído na quarentena visual e `1369473` foi separado por ser solicitação de compra, não oferta; +- incluídos `1381514` e `1400223` no Top 15 condicionado do Cruzeiro; +- removido `1375199` por inatividade, sem inferir venda; +- relatórios, parâmetros, resultados, shortlist, estado atual, esquema e manual operacional sincronizados com 21/07. + +## 20/07/2026 — versão 5 + +Revisão dos demais chats do projeto e dos relatórios atuais. + +Mudanças: + +- consolidada a prioridade atual do Jardins Mangueiral; +- Cruzeiro definido como alternativa secundária e oportunística; +- preservado o ranking interno Ouro/Prata/Bronze do Cruzeiro; +- incluídas QCs preferenciais do Mangueiral; +- incorporado o filtro de planta original de três quartos; +- incorporada a regra de avaliar funcionalidade, não apenas perda do quintal; +- incorporada avaliação explícita de ruído de vizinhança; +- criado documento financeiro com saldos, renda, aluguel, faixa-alvo e reserva; +- Pró-Cotista e MCMV Faixa 4 marcados como excluídos; +- incorporado o cenário de consórcio BB e a decisão de não contratá-lo; +- criado manual do scraper com caminho real, Playwright e Cloudflare; +- incorporados os snapshots Top 15 de 20/07/2026; +- corrigidas contradições remanescentes no `AGENTS.md` e no contexto do projeto. + +## 13/07/2026 — mudança de prioridade + +A consolidação inicial tratava Cruzeiro e Jardins Mangueiral como igualmente prioritários. + +Correção: + +1. Jardins Mangueiral passou a ser o foco principal. +2. Cruzeiro passou a ser secundário e oportunístico. +3. O ranking interno do Cruzeiro permaneceu válido para imóveis concretos. + +## 11/07/2026 — filtro do Mangueiral + +- casas originalmente de dois quartos excluídas; +- somente plantas originais de três quartos; +- quintal fechado deixou de ser penalidade automática; +- conversão deve ser julgada por funcionalidade, luz, ventilação e redundância. diff --git a/docs/CURRENT_SHORTLIST.md b/docs/CURRENT_SHORTLIST.md new file mode 100644 index 0000000..a8b5238 --- /dev/null +++ b/docs/CURRENT_SHORTLIST.md @@ -0,0 +1,53 @@ +# Shortlist atual — snapshot de 22/07/2026 + +Este documento resume os relatórios completos em `reports/mangueiral_top15/`, `reports/cruzeiro_top15/` e `reports/cruzeiro_vs_mangueiral/`. + +## 1. Jardins Mangueiral — prioridade principal + +| # | Anúncio | QC | Preço | Razão principal | Pendência principal | +|---:|---:|---:|---:|---|---| +| 1 | 1279892 | 10 | R$ 595 mil | nota 4/5; cozinha e gourmet funcionais, poucas alterações | planta, áreas e regularização | +| 2 | 1319996 | 14 | R$ 649 mil | nota 4/5; cozinha, gourmet, quintal e conservação | reversão do closet, cobertura e sistema solar | +| 3 | 1377683 | 11 | R$ 585 mil | aderência inferida; cozinha funcional e gourmet aberto | usuário avaliar, planta, cobertura e drenagem | +| 4 | 1371718 | 14 | R$ 650 mil | aderência inferida; cozinha, conservação e gourmet aberto | usuário avaliar, sistemas e insolação | +| 5 | 1140317 | 5 | R$ 599 mil | aderência inferida; cozinha equipada e gourmet conectado | usuário avaliar, cobertura e uma vaga | + +Observações: + +- o imóvel `1346017`, antes em terceiro, saiu por selo visual de vendido; +- `1356197`, antes em nono, também passou a exibir “VENDIDO” e foi substituído por `1290502`, QC 11; +- outras unidades ficaram em quarentena por indicação visual de venda; +- casas fora das QCs preferenciais podem entrar quando qualidade e preço compensam; +- áreas anunciadas não foram usadas para preço por m². +- a ordem foi recalibrada com as notas e comentários diretos do usuário em 21/07; +- o funil foi então refeito sobre os 78 ativos; `1377683`, `1371718` e `1140317` são achados novos, ainda sem nota pessoal; +- piscina, jacuzzi e banheira são incompatibilidades confirmadas; cubas de vidro e necessidade de reforma antes da mudança são fortes negativos. +- o teto de R$ 750 mil excluiu `1352633`, anunciado por R$ 755 mil; +- `1401074`, QC 13 e R$ 720 mil, entrou em 8º por aderência inferida; ainda depende de avaliação pessoal e diligência documental; +- `1290512` ficou inativo em 22/07; a ausência não foi tratada como venda. + +## 2. Cruzeiro Novo — alternativa secundária + +| # | Anúncio | Local | Preço | Razão principal | Pendência principal | +|---:|---:|---|---:|---|---| +| 1 | 1345146 | SHCES 1207 A | R$ 670 mil | reforma coerente, suíte e elevador anunciado | vaga, matrícula e sistemas | +| 2 | 1357415 | SHCES 1109 | R$ 630 mil | reforma, suíte, nascente e vazado | confirmar elevador | +| 3 | 1357633 | SHCES 309 | R$ 600 mil | reforma uniforme e sistemas renovados declarados | elevador, garagem e obra | +| 4 | 1376958 | SHCES 911 B | R$ 599 mil | reformado, nascente, vazado e suíte | sem elevador e garagem incerta | +| 5 | 1345903 | SHCES 1303 | R$ 565 mil | primeiro andar e preço competitivo | sem garagem, ruído e segurança | + +Observações: + +- `1263868` foi retirado por “VENDIDO” na primeira foto; +- `1381514` e `1400223` entraram no Top 15 condicionado; nenhum supera o Top 5 atual; +- `1375199` ficou inativo em 21/07 e saiu do ranking; ausência não prova venda; +- a janela de coleta é insuficiente para liquidez; +- a posição no ranking de imóvel concreto pode superar a faixa geral da quadra; +- andar, elevador, vaga, matrícula, instalações e ruído exigem confirmação. + +## 3. Uso correto + +- começar visitas pelo Mangueiral; +- usar o Cruzeiro quando surgir oportunidade concreta excepcional; +- reprocessar o ranking quando o snapshot mudar; +- não carregar posições de 21/07/2026 como verdade permanente. diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md new file mode 100644 index 0000000..4ccf607 --- /dev/null +++ b/docs/CURRENT_STATE.md @@ -0,0 +1,145 @@ +# Estado atual do projeto + +**Data de referência:** 22/07/2026 + +Este é o resumo vigente. Ele prevalece sobre fases anteriores da conversa. + +## 1. Decisão imobiliária + +### Prioridade regional + +1. **Jardins Mangueiral — foco principal.** +2. **Cruzeiro — alternativa secundária e oportunística.** + +O entendimento mudou após a comparação de imóveis concretos e uma visita ao Cruzeiro. O estoque do Mangueiral passou a oferecer, com maior frequência, casas mais novas, funcionais e compatíveis com moradia de longo prazo. No Cruzeiro, apareceram mais concessões em reforma, garagem, elevador, conservação e planta. + +Isso não exclui o Cruzeiro. Um apartamento excepcional pode superar uma casa mediana do Mangueiral. + +### Filosofia + +- Esperar pelo imóvel certo é preferível a comprar com grandes concessões. +- A decisão deve equilibrar finanças, segurança patrimonial, funcionalidade e bem-estar. +- Possuir uma casa física no longo prazo tem valor psicológico, mas não justifica comprometer liquidez ou aceitar um imóvel inadequado. + +--- + +## 2. Filtros vigentes + +### Jardins Mangueiral + +- somente planta original de três quartos; +- QCs 8 e 9 preferenciais; +- QCs 6 e 7 alternativas fortes; +- QCs 10 e 11 também consideradas boas; +- posição exata pode superar a classificação da QC; +- quintal fechado é aceitável quando convertido em espaço útil; +- evitar duplicação de cozinha, perda de ventilação, calor e circulação ruim; +- avaliar ruído de paredes compartilhadas, pets, música, áreas comuns e rua. + +### Cruzeiro + +Ranking interno de localização: + +- **Ouro:** SHCES 1501, 1503, 1505, 1203, 1205 e 1207. +- **Prata:** SHCES 1103, 1105, 1107, 1109, 1111, 1303, 1305, 1307 e 1309. +- **Bronze:** conjunto SHCES 1400. +- **Caso a caso:** 1211 e 1507. + +Esse ranking é heurístico e não substitui a avaliação da unidade. + +--- + +## 3. Referência financeira vigente + +Últimos valores informados: + +- renda bruta: aproximadamente R$ 18.500 por mês; +- recursos líquidos: R$ 222.000; +- FGTS: R$ 130.000; +- reserva líquida mínima pós-compra: R$ 50.000; +- capacidade de poupança: R$ 15.000 a cada seis meses; +- aluguel usado nas comparações recentes: R$ 3.200 por mês, sem condomínio e IPTU; +- faixa de imóveis-alvo: aproximadamente R$ 650.000 a R$ 750.000; +- preço máximo tolerado para o imóvel: **R$ 750.000**. + +Esses números devem ser datados e confirmados antes de decisão ou simulação nova. + +O teto de R$ 750.000 é um limite pessoal confirmado, não uma meta de gasto nem uma autorização para comprometer a reserva. Custos de compra, mudança, reforma e regularização devem ser considerados separadamente e ainda precisam preservar a reserva mínima. + +### Exclusões + +- Pró-Cotista: fora do conjunto de opções, pelas regras consideradas de renda bruta máxima de R$ 12.000 e imóvel usado até R$ 500.000. +- MCMV Faixa 4: não usar nas simulações. +- Imóveis sem matrícula/escritura/regularidade suficiente: não presumir financiamento bancário. + +### Estratégia atual + +- financiamento tradicional é a referência principal quando houver compra; +- consórcio pode ter custo nominal menor em alguns cenários, mas perde em previsibilidade, liquidez e risco de contemplação; +- a proposta específica de consórcio BB não foi contratada; +- preservar reserva e manter estratégia lenta é preferível a forçar a compra. + +--- + +## 4. Banco e coleta + +Caminho esperado: + +```text +dfimoveis_data/dfimoveis.sqlite3 +``` + +Snapshot estrutural de 22/07/2026: + +- 162 anúncios; +- 638 observações históricas; +- 7.457 registros históricos de foto, dos quais 3.521 pertencem à galeria primária; +- Cruzeiro Novo: 67 anúncios acumulados, 63 ativos, 4 inativos; +- Jardins Mangueiral: 95 anúncios acumulados, 78 ativos, 17 inativos; +- cobertura: 13 a 22/07/2026; +- Cruzeiro não integra mais a lista de buscas; seus dados permanecem preservados com última coleta em 21/07. + +Em 22/07 foi aplicada migração aditiva do schema, após backup consistente. Valores brutos e registros históricos foram preservados. Endereço, bairro e CRECI agora possuem colunas normalizadas; fotos de recomendações do portal são mantidas, mas marcadas como fora da galeria principal; disponibilidade e problemas de qualidade possuem tabelas auditáveis. + +A janela é curta demais para inferir liquidez. + +A maioria dos snapshots adicionais é ruído de campos textuais contaminados: 398 transições alteram `creci`, enquanto somente três anúncios tiveram mudança real de preço. Não interpretar o número bruto de observações como mudanças de mercado. + +--- + +## 5. Rankings vigentes + +Os rankings completos estão em: + +- `reports/mangueiral_top15/README.md` +- `reports/cruzeiro_top15/README.md` +- `reports/cruzeiro_vs_mangueiral/README.md` + +Eles são snapshots datados, não preferências permanentes. + +Top 3 Mangueiral em 22/07/2026: + +1. anúncio 1279892 — QC 10 — R$ 595 mil; +2. anúncio 1319996 — QC 14 — R$ 649 mil; +3. anúncio 1377683 — QC 11 — R$ 585 mil, aderência inferida e pendente de avaliação do usuário. + +A ordem incorpora avaliação direta do usuário sobre os 15 finalistas: prioridade para cozinha e gourmet funcionais, conservação e baixa necessidade de intervenção; piscina, jacuzzi, banheira e cubas de vidro são incompatibilidades ou fortes negativos. O teto eliminatório de R$ 750 mil retirou `1352633` (R$ 755 mil); o novo `1401074`, QC 13 e R$ 720 mil, entrou em 8º com aderência inferida e pendente de avaliação pessoal. + +Após o feedback, o funil foi refeito sobre os 78 anúncios ativos. `1377683`, `1371718` e `1140317` entraram no Top 5 por aderência inferida nas fotos e descrições, mas ainda não possuem nota pessoal e não devem ser tratados como preferência confirmada. + +Top 3 Cruzeiro em 21/07/2026: + +1. anúncio 1345146 — SHCES 1207 A — R$ 670 mil; +2. anúncio 1357415 — SHCES 1109 — R$ 630 mil; +3. anúncio 1357633 — SHCES 309 — R$ 600 mil. + +--- + +## 6. Pendências principais + +- visitar o Mangueiral e validar QCs, trânsito, comércio, ruído e sensação de segurança; +- atualizar saldos e taxas antes de simulações finais; +- definir limite confortável de prestação, não apenas capacidade bancária; +- confirmar documentação, planta original e regularidade das ampliações; +- aumentar a série histórica antes de conclusões de liquidez e tendência; +- corrigir a instabilidade de `creci`/campos contaminados antes de usar o histórico bruto como mudança real. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md index f7ac56f..3ddd33b 100644 --- a/docs/DATA_MODEL.md +++ b/docs/DATA_MODEL.md @@ -1,481 +1,254 @@ -# Modelo de dados — referência conceitual +# Modelo de dados conceitual ## 1. Aviso -Este documento descreve o modelo lógico desejável para análise. +Este documento descreve a interpretação desejada. O esquema real deve ser registrado separadamente em `docs/ACTUAL_SCHEMA.md`. -Ele **não afirma** que o banco SQLite atual possui essas tabelas ou colunas. O esquema real deve ser inspecionado antes de qualquer consulta. - -Não migre o banco original para este modelo sem autorização. +Não migrar o banco original apenas para fazê-lo coincidir com este modelo. --- -## 2. Entidades fundamentais - -O projeto deve distinguir cinco conceitos: +## 2. Entidades essenciais -1. **fonte**: site, portal, corretor ou canal; -2. **anúncio**: publicação identificável em uma fonte; -3. **observação**: estado do anúncio em uma data de coleta; -4. **imóvel físico**: unidade residencial que pode aparecer em vários anúncios; -5. **execução de coleta**: processo técnico que obteve os dados. +### `scrape_run` -Essa separação é essencial para séries históricas e deduplicação. - ---- - -## 3. Fonte - -Entidade conceitual: `source` +Representa uma execução de coleta. Campos desejáveis: -- `source_id` -- `name` -- `base_url` -- `source_type` -- `active` -- `notes` -- `created_at` - -Não usar domínio ou nome de corretor como identificador único sem normalização. - ---- - -## 4. Execução de coleta - -Entidade conceitual: `scrape_run` - -Campos desejáveis: - -- `run_id` -- `source_id` -- `started_at` -- `finished_at` -- `status` -- `pages_requested` -- `pages_succeeded` -- `pages_failed` -- `items_seen` -- `items_inserted` -- `items_updated` -- `http_status_summary` -- `scraper_version` -- `configuration_hash` -- `error_summary` - -Serve para identificar: - -- períodos sem coleta; -- mudanças de cobertura; -- falhas; -- alterações de código; -- diferenças entre ausência real e ausência causada por erro. - ---- - -## 5. Anúncio - -Entidade conceitual: `listing` - -Representa a identidade do anúncio, não cada coleta. - -Campos desejáveis: - -- `listing_id` -- `source_id` -- `source_listing_id` -- `canonical_url` -- `first_seen_at` -- `last_seen_at` -- `last_successful_seen_at` -- `status` -- `seller_type` -- `seller_name_normalized` -- `broker_registration` -- `property_candidate_id` -- `created_at` -- `updated_at` - -Chave natural potencial: - -```text -(source_id, source_listing_id) -``` - -A URL não deve ser a única chave, pois pode mudar. - ---- +- `run_id`; +- `started_at`; +- `finished_at`; +- `status`; +- `scraper_version`; +- `configuration_hash`; +- páginas e erros; +- contagens de anúncios e fotos; +- resumo de status HTTP. -## 6. Observação histórica +### `listing` -Entidade conceitual: `listing_snapshot` +Identidade do anúncio na fonte. -Representa o conteúdo observado em uma execução. +- identificador interno; +- identificador do portal; +- URL canônica; +- primeira e última aparição; +- status; +- anunciante; +- vínculo com imóvel provável. -Campos desejáveis: +### `listing_observation` -- `snapshot_id` -- `listing_id` -- `run_id` -- `observed_at` -- `title` -- `description` -- `price` -- `condominium_fee` -- `property_tax` -- `area_private` -- `area_useful` -- `area_built` -- `area_total` -- `bedrooms` -- `suites` -- `bathrooms` -- `parking_spaces` -- `floor` -- `elevator` -- `furnished` -- `property_type` -- `address_raw` -- `neighborhood_raw` -- `latitude` -- `longitude` -- `image_count` -- `status_raw` -- `payload_hash` -- `raw_payload_reference` - -Não sobrescrever o histórico quando um anúncio muda. - -Se os dados atuais usam uma única linha mutável por anúncio, documentar essa limitação. +Estado do anúncio em uma data de coleta. ---- +- data; +- título e descrição; +- preço; +- condomínio e IPTU; +- áreas separadas; +- quartos, suítes, banheiros e vagas; +- endereço bruto; +- status bruto; +- hash do conteúdo; +- referência ao HTML. -## 7. Imóvel físico candidato +Não sobrescrever histórico. -Entidade conceitual: `property_candidate` +### `photo` -Representa uma hipótese de que anúncios diferentes descrevem o mesmo imóvel. +- anúncio ou observação; +- URL; +- caminho local; +- posição; +- hash criptográfico; +- hash perceptual; +- dimensões; +- status de download. -Campos desejáveis: +A posição da primeira foto deve ser preservada, pois ela é usada na auditoria visual de disponibilidade. -- `property_candidate_id` -- `canonical_region` -- `canonical_address` -- `canonical_unit` -- `property_type` -- `original_floor_plan` -- `dedup_confidence` -- `dedup_method` -- `review_status` -- `created_at` -- `updated_at` +### `property_candidate` -Não tratar correspondência probabilística como certeza. +Hipótese de imóvel físico comum a um ou mais anúncios. -Estados sugeridos: +- região e endereço normalizados; +- tipologia; +- planta original provável; +- método de deduplicação; +- confiança; +- estado de revisão. -- `unreviewed` -- `probable` -- `confirmed` -- `rejected` -- `ambiguous` +Não converter correspondência probabilística em certeza. --- -## 8. Endereço e localização +## 3. Localização no DF -Entidade conceitual: `location` - -Campos desejáveis: - -- `location_id` -- `region` -- `administrative_region` -- `neighborhood` -- `superquadra` -- `quadra` -- `block` -- `qc` -- `street` -- `lot` -- `unit` -- `postal_code` -- `latitude` -- `longitude` -- `geocode_precision` -- `geocode_source` - -No Distrito Federal, o endereço deve permitir estruturas como: +A normalização deve suportar: +- região administrativa; +- bairro; - SHCES; - quadra; - bloco; -- superquadra; - conjunto; -- lote; - QC; +- rua; +- lote; +- unidade; - condomínio; -- unidade. +- latitude e longitude; +- precisão da geocodificação. -Não forçar todos os endereços a um modelo de rua e número. +Não forçar todos os endereços ao modelo rua/número. --- -## 9. Mídia - -Entidade conceitual: `listing_media` - -Campos desejáveis: - -- `media_id` -- `snapshot_id` -- `media_url` -- `media_type` -- `position` -- `content_hash` -- `perceptual_hash` -- `width` -- `height` -- `download_status` - -Hashes perceptuais de imagens podem ajudar na deduplicação, mas não devem decidir sozinhos. - ---- - -## 10. Histórico de preço - -O histórico pode ser derivado de `listing_snapshot`. - -View conceitual: - -```sql -SELECT - listing_id, - observed_at, - price, - price - LAG(price) OVER ( - PARTITION BY listing_id - ORDER BY observed_at - ) AS absolute_change -FROM listing_snapshot; -``` - -Regras: +## 4. Áreas -- ignorar repetições idênticas; -- registrar aumentos e reduções; -- não interpretar ausência como venda; -- identificar alterações simultâneas de área ou tipologia; -- detectar preços promocionais artificiais; -- separar moeda e unidades. - ---- - -## 11. Deduplicação - -### 11.1 Sinais fortes - -- mesmo identificador na fonte; -- mesmo endereço e unidade; -- mesmo conjunto de imagens; -- mesmo telefone ou corretor com descrição muito semelhante; -- mesma planta, metragem e características; -- coordenadas muito próximas; -- texto quase idêntico. - -### 11.2 Sinais moderados - -- mesma quadra e bloco; -- mesmo preço; -- mesma área; -- mesmos quartos e vagas; -- datas próximas; -- imagens parcialmente coincidentes. - -### 11.3 Sinais fracos - -- mesmo bairro; -- título genérico; -- preço arredondado; -- número de quartos isolado. - -### 11.4 Regras - -- manter o anúncio original; -- criar relacionamento de duplicidade; -- registrar método e confiança; -- permitir revisão manual; -- não fundir registros de forma irreversível; -- não usar apenas texto normalizado; -- evitar que atualizações do mesmo anúncio sejam contadas como imóveis novos. - ---- - -## 12. Normalização - -### Preço - -- armazenar como número inteiro em centavos ou valor monetário consistente; -- preservar valor bruto; -- registrar moeda; -- detectar preço por mês confundido com preço total; -- detectar valores incompletos. - -### Área - -Manter campos separados: +Manter separadas: - privativa; - útil; - construída; - total; -- terreno. +- terreno; +- ampliada declarada. + +Preservar valor bruto e fonte. + +No Mangueiral, áreas anunciadas frequentemente misturam área original e ampliação. Não calcular preço por m² sem validação. -Nunca preencher uma área com outra apenas para completar dados. +--- -### Quartos +## 5. Quartos e planta Separar: -- quartos; +- quartos anunciados; +- quartos utilizáveis; - suítes; - dependência; - escritório; -- quarto reversível; -- tipologia original; -- tipologia anunciada. +- quarto adaptado; +- planta original provável; +- confiança da classificação. -### Localização +No Mangueiral, a planta original de três quartos é um filtro obrigatório. -Preservar: +--- -- texto bruto; -- versão normalizada; -- componentes extraídos; -- confiança da extração. +## 6. Status e disponibilidade -### Datas +Campos derivados desejáveis: -Usar ISO 8601 e timezone explícito quando relevante. +- ativo na última coleta; +- inativo; +- vendido na primeira foto; +- vendido na descrição; +- republicado; +- duplicata provável; +- conflito de dados; +- data da última evidência. ---- +Ausência não é prova de venda. -## 13. Qualidade dos dados - -Criar métricas por fonte, período e região: +--- -- percentual sem preço; -- percentual sem área; -- percentual sem localização; -- percentual com preço por m² calculável; -- duplicidade estimada; -- taxa de mudança de identificador; -- cobertura diária; -- falhas de coleta; -- distribuições implausíveis; -- inconsistência entre título e campos; -- divergência entre snapshots. +## 7. Deduplicação ---- +### Sinais fortes -## 14. Inspeção inicial do SQLite +- mesmo ID da fonte; +- endereço e unidade; +- conjunto de imagens; +- implantação idêntica; +- telefone e descrição coincidentes. -Consultas somente leitura úteis: +### Sinais moderados -### Tabelas e views +- quadra/bloco/QC; +- área, preço e quartos; +- texto semelhante; +- datas próximas; +- fotos parcialmente coincidentes. -```sql -SELECT - type, - name, - tbl_name, - sql -FROM sqlite_master -ORDER BY type, name; -``` +### Regras -### Contagem aproximada de tabelas +- preservar anúncios originais; +- criar grupo de imóvel provável; +- registrar confiança e método; +- permitir revisão manual; +- não fundir irreversivelmente. -Gerar consultas após listar os nomes. Não concatenar nomes externos sem validação. +--- -### Colunas +## 8. Histórico de preço -```sql -PRAGMA table_info('nome_da_tabela'); -``` +Derivar por anúncio e por imóvel provável. -### Chaves estrangeiras +Registrar: -```sql -PRAGMA foreign_key_list('nome_da_tabela'); -``` +- primeiro preço; +- preço atual; +- reduções e aumentos; +- republicações; +- mudanças simultâneas de área, quartos ou descrição; +- períodos de ausência. -### Índices +Não interpretar retirada como venda. -```sql -PRAGMA index_list('nome_da_tabela'); -``` +--- -### Integridade +## 9. Qualidade dos dados -```sql -PRAGMA quick_check; -``` +Métricas mínimas: -`quick_check` é somente leitura, mas pode ser custoso em bancos grandes. Executar quando apropriado. +- nulos por campo; +- preços implausíveis; +- áreas implausíveis; +- divergência entre título e campos; +- cobertura por dia; +- falhas por página; +- fotos ausentes; +- duplicidade estimada; +- anúncios com selo de vendido; +- mudanças de ID; +- distribuição por região e tipologia. --- -## 15. Camada derivada recomendada +## 10. Camada derivada -Sem alterar o banco original, criar: +Sugestão: ```text data/derived/ ├── listings_clean.parquet -├── snapshots_clean.parquet +├── observations_clean.parquet +├── photos_index.parquet ├── property_candidates.parquet ├── price_history.parquet ├── locations_normalized.parquet └── quality_metrics.parquet ``` -Ou um SQLite derivado: - -```text -data/derived/analytics.sqlite -``` - -Toda derivação deve registrar: - -- data; -- versão do código; -- fonte; -- filtros; -- hash ou identificação do banco de origem; -- contagem de entrada e saída. +Registrar versão do código, data, banco de origem e contagens de entrada/saída. --- -## 16. Dicionário do esquema real - -Após inspecionar o banco, produzir: - -```text -docs/ACTUAL_SCHEMA.md -``` +## 11. Implementação aditiva v2 — 22/07/2026 -Esse arquivo deve conter: +Com autorização explícita do usuário e backup consistente, parte deste modelo foi incorporada ao banco operacional sem remover dados: -- diagrama das tabelas; -- descrição de cada coluna; -- chaves; -- índices; -- cardinalidades; -- exemplos pequenos; -- problemas conhecidos; -- correspondência com este modelo conceitual; -- decisões de interpretação. +- campos brutos preservados e colunas normalizadas em `listings`; +- `stable_content_hash` separado do hash bruto legado; +- `is_primary_gallery`, origem e revisão em `photos`; +- `availability_observations` para evidência de disponibilidade; +- `data_quality_issues` para quarentenas reversíveis; +- `listing_change_events` para mudanças materiais futuras; +- escopo e configuração das futuras execuções em `crawl_runs`; +- visões `v_primary_photos`, `v_open_data_quality_issues` e `v_listing_current_availability`. -Não alterar `DATA_MODEL.md` para simplesmente espelhar um esquema ruim. Manter separação entre modelo conceitual e implementação real. +As tabelas e colunas antigas continuam preservadas para reprodução histórica. diff --git a/docs/FINANCIAL_STRATEGY.md b/docs/FINANCIAL_STRATEGY.md new file mode 100644 index 0000000..a9e8fc1 --- /dev/null +++ b/docs/FINANCIAL_STRATEGY.md @@ -0,0 +1,195 @@ +# Estratégia financeira + +## 1. Snapshot pessoal + +**Data de referência:** julho de 2026 + +| Item | Valor de trabalho | +|---|---:| +| Renda bruta mensal | aproximadamente R$ 18.500 | +| Recursos líquidos | R$ 222.000 | +| FGTS | R$ 130.000 | +| Reserva líquida mínima pós-compra | R$ 50.000 | +| Poupança adicional | R$ 15.000 a cada seis meses | +| Aluguel-base usado nas comparações | R$ 3.200/mês | +| Faixa de imóveis-alvo | R$ 650.000–R$ 750.000 | +| Preço máximo tolerado do imóvel | **R$ 750.000** | + +O aluguel-base exclui condomínio e IPTU. Não misturar com valores brutos ou contratuais sem decomposição. + +Esses valores são premissas datadas, não constantes permanentes. + +O preço de R$ 750.000 é o teto máximo tolerado para o imóvel, não o orçamento recomendado. Custos de aquisição, mudança, reforma e regularização ficam fora desse preço e continuam sujeitos à preservação da reserva líquida mínima. + +--- + +## 2. Restrições + +- preservar ao menos R$ 50.000 líquidos depois de entrada, custos, mudança e adequações; +- FGTS não é reserva de emergência; +- não usar toda a capacidade bancária como orçamento; +- não aceitar prestação inicial desconfortável apenas porque cabe na análise de crédito; +- incorporar custos de transação e reforma; +- evitar imóvel que exija comprometer a reserva logo após a compra. +- não recomendar nem simular como candidato aceitável um imóvel com preço acima de R$ 750.000, salvo se o usuário alterar explicitamente esse teto. + +--- + +## 3. Programas e linhas excluídos + +### Pró-Cotista + +Fora do conjunto atual de opções. + +Regras consideradas pelo usuário: + +- renda bruta máxima de R$ 12.000; +- imóvel usado de até R$ 500.000. + +A renda aproximada de R$ 18.500 e a faixa-alvo de R$ 650.000–R$ 750.000 não se enquadram. + +### Minha Casa Minha Vida — Faixa 4 + +Não incluir nas simulações atuais. + +### Imóvel irregular + +Financiamento bancário tradicional depende de matrícula, registro e regularidade suficientes para a garantia. Não presumir financiamento de imóvel sem escritura/matrícula regular. + +--- + +## 4. Financiamento tradicional + +É a referência principal para compra quando o imóvel correto aparecer. + +Simulações recentes citaram, aproximadamente: + +- Caixa: 11,5% ao ano; +- Banco do Brasil: 11,7% ao ano. + +Essas taxas são snapshots de propostas/simulações, não taxas vigentes garantidas. Sempre substituir por CET atual e documentos do banco. + +### Campos obrigatórios em qualquer simulação + +- preço do imóvel; +- entrada em dinheiro; +- FGTS; +- reserva preservada; +- ITBI, registro e tarifas; +- reforma e mudança; +- valor financiado; +- SAC ou Price; +- taxa nominal, efetiva e CET; +- indexador; +- prazo; +- seguros; +- primeira e última prestação; +- amortização extraordinária; +- uso de FGTS a cada 24 meses; +- horizonte de permanência; +- custo de oportunidade. + +### Comparação com o aluguel + +O usuário observou que, para um imóvel em torno de R$ 700.000, a prestação inicial estimada poderia superar o aluguel atual. + +A análise deve comparar: + +- fluxo mensal; +- patrimônio acumulado; +- liquidez; +- risco; +- estabilidade; +- custos de manutenção; +- reajuste do aluguel; +- valorização do imóvel; +- retorno líquido alternativo; +- custos de venda. + +Não decidir apenas porque a prestação é maior ou menor que o aluguel. + +--- + +## 5. Consórcio BB analisado + +Cenário específico discutido, não contratado: + +- carta: R$ 660.000; +- prazo informado: 161 meses; +- taxa de administração: 0,11% ao mês, aproximadamente 18,7% no total da carta; +- lance pretendido: 60%, equivalente a R$ 420.000; +- recursos próprios considerados: R$ 300.000; +- empréstimo complementar: R$ 120.000; +- empréstimo: 120 parcelas de R$ 1.596,66; +- parcela pós-lance estimada: aproximadamente R$ 1.924; +- último lance vencedor informado: cerca de 58%; +- lances recentes mencionados: aproximadamente 58,99% a 60%; +- grupo não era novo. + +Relatos do gerente sobre sazonalidade de agosto/setembro, competição em outubro e participação de funcionários do BB são evidência anedótica e não devem ser modelados como regra sem histórico do próprio grupo. + +### Conclusão de trabalho + +- o consórcio pode ter vantagem de custo nominal em algumas combinações; +- exige imobilização de recursos e aceita risco de contemplação; +- pode demandar empréstimo complementar, reduzindo a vantagem; +- financiamento tradicional oferece maior certeza, flexibilidade e execução imediata; +- a proposta específica foi deixada passar; +- a estratégia atual é manter paciência e precisão. + +--- + +## 6. Custos de compra + +Incluir, conforme o caso: + +- ITBI; +- registro; +- escritura quando aplicável; +- avaliação bancária; +- tarifas; +- seguros; +- mudança; +- reforma; +- mobiliário; +- regularização; +- condomínio e IPTU proporcionais; +- contingência. + +Não usar percentual genérico sem conferir regras e valores atuais. + +--- + +## 7. Cenários obrigatórios + +Toda recomendação financeira deve apresentar ao menos: + +- conservador; +- central; +- otimista; +- cenário de estresse. + +Estressar: + +- renda menor; +- juros ou CET maiores; +- reforma acima do orçamento; +- valorização baixa; +- retorno de investimento menor; +- manutenção extraordinária; +- redução da poupança; +- necessidade de vender antes do horizonte. + +--- + +## 8. Critério de decisão + +A compra é financeiramente aceitável somente quando: + +- a reserva é preservada; +- o custo mensal é confortável, não apenas aprovado; +- o imóvel atende à planta e à localização; +- custos de adequação são conhecidos; +- o horizonte justifica custos de transação; +- a comparação com aluguel não depende de premissas otimistas; +- a decisão continua boa sem contar com valorização excepcional. diff --git a/docs/LOCATION_CRITERIA.md b/docs/LOCATION_CRITERIA.md new file mode 100644 index 0000000..31dce6d --- /dev/null +++ b/docs/LOCATION_CRITERIA.md @@ -0,0 +1,138 @@ +# Critérios de localização + +## 1. Regra geral + +Avaliar localização em três níveis: + +1. região; +2. quadra, bloco, QC ou condomínio; +3. posição exata da unidade. + +A classificação geral nunca substitui a posição concreta. + +--- + +## 2. Jardins Mangueiral — prioridade principal + +### 2.1 Hierarquia de QCs + +| Nível | QCs | Uso | +|---|---|---| +| Preferenciais | 8 e 9 | Priorizar triagem e visitas quando o imóvel também for bom | +| Alternativas fortes | 6 e 7 | Manter entre as candidatas principais | +| Também boas | 10 e 11 | Avaliar normalmente, sem penalidade automática | +| Não classificadas | demais QCs | Analisar caso a caso; não excluir somente pela QC | + +A hierarquia veio de relatos de moradores e deve ser marcada como **anedótica**. + +O ranking de imóveis concretos pode produzir candidatos fortes em outras QCs. No snapshot de 21/07/2026, por exemplo, imóveis em QC 10, QC 6 e QC 8 lideraram, enquanto unidades em QCs 14, 12, 4, 5 e 3 também entraram no Top 15. + +### 2.2 O que “bem localizada” significa + +- acesso prático a mercado, farmácia e serviços; +- qualidade do trajeto a pé; +- facilidade de entrada e saída; +- tempo real em horários relevantes; +- circulação interna; +- topografia; +- estacionamento de visitantes; +- iluminação noturna; +- distância de portaria e áreas técnicas; +- distância de quadra esportiva, salão, parque infantil e lixeiras; +- privacidade; +- posição solar; +- ruído. + +### 2.3 Posição da casa + +Uma casa na QC 8 ou 9 pode perder prioridade se estiver: + +- junto a área social barulhenta; +- exposta a via interna movimentada; +- com pouca privacidade; +- em posição quente e mal ventilada; +- próxima a equipamentos ou lixeiras; +- em trecho ruim para estacionamento. + +Uma unidade excepcional nas QCs 6, 7, 10, 11 ou mesmo em QC não classificada pode superar uma opção mediana nas QCs preferenciais. + +### 2.4 Trânsito e rotina + +O custo do deslocamento ao Plano Piloto deve ser medido em campo. A flexibilidade de horário, home office e o horário escolar no início da tarde reduzem parte do impacto, mas não o eliminam. + +Registrar tempos em: + +- manhã de dia útil; +- fim de tarde; +- noite; +- fim de semana; +- trajeto ao SBS; +- trajeto ao Sudoeste; +- trajeto a São Sebastião. + +--- + +## 3. Cruzeiro — alternativa secundária + +### 3.1 Ranking interno de quadras + +Esse ranking orienta a localização relativa de apartamentos concretos no Cruzeiro Novo. + +| Faixa | Quadras SHCES | +|---|---| +| Ouro | 1501, 1503, 1505, 1203, 1205, 1207 | +| Prata | 1103, 1105, 1107, 1109, 1111, 1303, 1305, 1307, 1309 | +| Bronze | conjunto 1400 | +| Caso a caso | 1211 e 1507 | + +Regras: + +- a faixa não compensa um apartamento ruim; +- uma unidade Prata bem reformada, silenciosa e acessível pode superar uma Ouro problemática; +- edifício, bloco, fachada, andar e posição são decisivos; +- o ranking não torna o Cruzeiro prioritário sobre o Mangueiral. + +### 3.2 Imóveis concretos podem fugir da hierarquia + +O Top 15 de 21/07/2026 incluiu imóveis em SHCES 1207, 1109, 1303 e também em 309, 911, 703, 207, 305, 301, 101, 609 e 203. + +Essas entradas não redefinem automaticamente as faixas. Elas mostram que qualidade, preço, estado e funcionalidade podem justificar investigação fora da hierarquia original. + +### 3.3 Critérios locais + +Avaliar: + +- acesso ao Eixo Monumental e Plano Piloto; +- comércio útil à rotina; +- caminhabilidade; +- ônibus e vias; +- tráfego e ruído; +- estacionamento e garagem; +- iluminação e arborização; +- estado dos blocos vizinhos; +- proximidade de bares, escolas, templos e quadras esportivas; +- posição do bloco em relação à rua; +- orientação solar e ventilação; +- vista e privacidade. + +### 3.4 Observação de campo + +A visita anterior ao Cruzeiro reduziu sua prioridade porque o perfil de comércio percebido não correspondeu ao desejado. Tratar isso como preferência pessoal e hipótese a validar, não como julgamento socioeconômico da região. + +--- + +## 4. Protocolo de visita de localização + +Para um candidato real: + +- visitar em dia útil; +- observar pico de trânsito; +- visitar à noite ou no fim de semana; +- testar estacionamento; +- caminhar até serviços; +- medir ruído com janelas abertas e fechadas; +- conversar com moradores quando possível; +- verificar iluminação, circulação e segurança percebida; +- registrar tempos reais de deslocamento. + +Não depender apenas de mapa, anúncio ou corretor. diff --git a/docs/PROJECT_CONTEXT.md b/docs/PROJECT_CONTEXT.md index df23eb9..5035c23 100644 --- a/docs/PROJECT_CONTEXT.md +++ b/docs/PROJECT_CONTEXT.md @@ -2,264 +2,152 @@ ## 1. Objetivo -Construir uma base histórica e uma metodologia confiável para apoiar a compra de um imóvel residencial no Distrito Federal. +Construir uma base histórica de anúncios e uma metodologia de avaliação para uma compra residencial de longo prazo no Distrito Federal. -O objetivo não é apenas encontrar o menor preço. A decisão deve equilibrar: +A compra deve oferecer: -- segurança patrimonial de longo prazo; -- adequação funcional do imóvel; -- qualidade de vida; -- liquidez; -- custo total de aquisição e manutenção; -- risco de reformas; -- impacto financeiro mensal; -- fatores psicológicos associados a possuir uma residência estável. +- residência funcional e estável; +- segurança patrimonial; +- custo mensal sustentável; +- preservação de reserva de emergência; +- baixo risco de reforma e custos escondidos; +- adequação à rotina familiar; +- liquidez razoável; +- possibilidade de permanência por 10 a 20 anos. -A análise deve ser lenta, comparativa e baseada em evidências. Não existe urgência artificial para comprar. +O projeto não busca comprar rapidamente. Busca reduzir a probabilidade de arrependimento. --- -## 2. Filosofia de decisão +## 2. Evolução da prioridade regional -### Princípios confirmados +### Fase anterior -- Evitar comprar apenas por medo de valorização futura. -- Evitar comprometer a reserva de emergência. -- Não aceitar grandes concessões de planta, conservação ou funcionalidade apenas para alcançar um preço nominal. -- Considerar reforma como custo financeiro, operacional e psicológico. -- Preferir imóvel pronto ou com intervenções pequenas e previsíveis. -- Comparar comprar com continuar alugando. -- Considerar que a compra será provavelmente em uma região diferente da moradia atual. -- Separar segurança emocional de justificativas financeiras. -- Reconhecer o valor psicológico de possuir um imóvel físico de longo prazo, sem transformar isso em justificativa para uma compra ruim. -- Tratar preço anunciado como ponto inicial de negociação, não como valor de mercado definitivo. +Cruzeiro chegou a ser tratado como candidato principal por centralidade, acesso ao Plano Piloto e tradição patrimonial. ---- - -## 3. Situação residencial atual +### Entendimento atual -Últimos valores consolidados no histórico do projeto: +Jardins Mangueiral tornou-se a prioridade principal. Cruzeiro permanece como alternativa secundária e oportunística. -- localização atual: Asa Sul, Brasília; -- imóvel alugado: aproximadamente 66 m²; -- aluguel: R$ 3.200 por mês; -- condomínio e IPTU não incluídos nesse valor; -- reajuste anual usado nas simulações anteriores: 4,5%. +Motivos para a mudança: -Esses números devem ser confirmados antes de novas simulações, pois podem mudar. +- casas mais novas e maior oferta no Mangueiral; +- maior chance de encontrar planta e conservação adequadas; +- percepção de ambiente residencial, segurança e silêncio; +- serviços de perfil familiar em crescimento; +- melhor integração com São Sebastião; +- flexibilidade de horário e trabalho remoto reduz parte do custo do trânsito; +- no Cruzeiro, imóveis concretos apresentaram mais concessões de reforma, garagem, elevador e funcionalidade. -A comparação aluguel versus compra deve considerar que o padrão, a localização e a tipologia dos imóveis candidatos podem ser diferentes do imóvel alugado atual. +Em visita ao Cruzeiro, o usuário percebeu uma composição de comércio e serviços menos alinhada às suas preferências cotidianas, com menor presença percebida de padarias, farmácias, restaurantes e academias. Essa é uma percepção pessoal de campo; não deve ser convertida em inferência de renda, segurança ou qualidade objetiva da região. --- -## 4. Regiões prioritárias - -### 4.1 Cruzeiro - -O Cruzeiro, especialmente o Cruzeiro Novo, é um dos dois principais candidatos. - -Aspectos relevantes: - -- localização central; -- acesso ao Plano Piloto; -- blocos e quadras com diferenças importantes; -- estoque antigo, com variação de conservação; -- risco de imóveis que exigem reforma; -- diferenças de estacionamento, garagem, elevador, ruído e posição do bloco; -- apartamentos de dois e três quartos podem ter áreas totais semelhantes; -- preço deve ser analisado em conjunto com planta, estado e custos de adequação. +## 3. Rotina e logística -### 4.2 Jardins Mangueiral +Contexto relevante: -Jardins Mangueiral é o outro principal candidato. +- trabalho associado à área central de Brasília/SBS; +- filha estuda no Sudoeste e entra no início da tarde; +- existe flexibilidade de horário e trabalho remoto em parte da rotina; +- namorada e familiares dela vivem em São Sebastião; +- Jardins Mangueiral pode melhorar a logística da relação e da convivência familiar. -Aspectos relevantes: - -- casas mais novas em comparação com parte do estoque do Cruzeiro; -- sensação de segurança, silêncio e residência de longo prazo; -- proximidade de São Sebastião, onde vivem a namorada do usuário e familiares dela; -- potencial de desenvolvimento da região em horizonte de 15 a 20 anos; -- impacto de trânsito parcialmente mitigado por flexibilidade de horário e trabalho remoto; -- logística compatível com o horário escolar da filha em parte dos dias; -- necessidade de avaliar ruído de vizinhança e qualidade das adaptações internas. - -Somente casas de planta original de três quartos fazem parte do conjunto de candidatos. +A compra deve continuar racional e funcional mesmo se as circunstâncias afetivas mudarem. A proximidade da namorada é benefício, não fundamento exclusivo da decisão. --- -## 5. Preferências funcionais - -O imóvel ideal deve: +## 4. Preferências habitacionais -- funcionar como residência de longo prazo; -- exigir pouca ou nenhuma reforma relevante; -- ter planta coerente; -- evitar circulação desperdiçada; -- ter boa iluminação e ventilação; -- oferecer espaço social útil; -- evitar duplicações de cozinha ou área gourmet sem função clara; -- ter conservação compatível com o preço; -- minimizar custos escondidos; -- preservar flexibilidade para mudanças futuras da família. +- imóvel pronto ou com intervenções pequenas e previsíveis; +- evitar grandes concessões de planta, conservação e funcionalidade; +- três quartos funcionais são valorizados; +- boa iluminação e ventilação; +- espaço social útil; +- pouca circulação desperdiçada; +- armazenamento adequado; +- ruído controlado; +- manutenção sustentável; +- documentação financiável; +- flexibilidade para mudanças futuras da família. -No Jardins Mangueiral, o fechamento do quintal não é automaticamente negativo. A análise deve avaliar se a área foi convertida em espaço realmente útil, ventilado e integrado. +Preferências funcionais confirmadas em avaliação direta dos anúncios: ---- - -## 6. Referência financeira - -### Último retrato consolidado - -Valores informados no histórico, sujeitos a atualização: - -- recursos líquidos: R$ 222.000; -- saldo de FGTS: R$ 130.000; -- reserva mínima desejada após a compra: R$ 50.000; -- capacidade de poupança: R$ 15.000 a cada seis meses; -- faixa de preço mais compatível com os imóveis desejados: aproximadamente R$ 650.000 a R$ 750.000; -- faixa anterior de referência: R$ 600.000. - -Não use esses valores como atuais sem registrar a data da análise e pedir confirmação quando a conclusão depender deles. - -### Regras financeiras - -- FGTS não deve ser tratado como liquidez disponível para emergências. -- Custos de compra devem ser adicionados ao preço: - - ITBI; - - registro; - - escritura quando aplicável; - - avaliação; - - tarifas; - - mudança; - - reparos; - - mobiliário; - - eventual reforma; - - custos de oportunidade. -- A reserva mínima de R$ 50.000 não deve ser consumida pela entrada, custos ou reforma. -- A prestação não deve ser analisada isoladamente. -- Comparar: - - aluguel; - - condomínio; - - IPTU; - - seguros; - - manutenção; - - juros; - - amortização; - - custo de oportunidade da entrada; - - valorização; - - reajuste do aluguel; - - liquidez do imóvel. -- Distinguir patrimônio acumulado de fluxo de caixa mensal. +- cozinha bem planejada e, idealmente, semi-integrada ao convívio externo; +- churrasqueira e gourmet conectados ao fluxo social da casa; +- prioridade para conservação e baixa necessidade de intervenção antes da mudança; +- piscina, jacuzzi e banheira não são desejadas; +- cubas de vidro, marcenaria ruim e áreas externas abandonadas são fortes negativos; +- fechamento em vidro deve ser penalizado quando houver risco de calor excessivo; +- closet criado pela perda de um quarto pode exigir reversão futura; +- placas solares são bônus, não compensação para planta ou funcionalidade inadequada. ---- +### Quintal e ampliação no Mangueiral -## 7. Financiamento e consórcio +A perda do quintal original não é defeito automático. É aceitável quando a área vira ambiente útil de convivência, refeição, trabalho ou armazenamento. -### Financiamento +É negativa quando: -Simulações anteriores consideraram diferentes cenários de: +- duplica a cozinha sem necessidade; +- reduz ventilação e iluminação; +- cria calor ou umidade; +- compromete drenagem e impermeabilização; +- produz circulação inútil; +- ocupa a área externa sem ganho funcional real. -- SAC; -- prazos de 20, 30 e até 35 anos; -- juros próximos de 9% ao ano em alguns cenários; -- amortizações periódicas com FGTS; -- amortizações extraordinárias em dinheiro. - -Toda nova simulação deve explicitar: - -- taxa nominal e efetiva; -- indexador; -- CET; -- sistema de amortização; -- prazo; -- entrada; -- custos acessórios; -- uso de FGTS; -- estratégia de amortização; -- inflação; -- retorno líquido alternativo; -- horizonte de permanência. - -### Pró-cotista - -A estratégia não deve assumir que o Pró-cotista estará disponível. O usuário já percebeu que provavelmente não receberá essa linha. - -### Consórcio - -O consórcio do Banco do Brasil foi considerado como alternativa. - -Informações recebidas do gerente, tratadas como relatos e não como garantias: +--- -- grupos com desconto podem atrair participantes dispostos a oferecer lances elevados; -- agosto e setembro podem ser meses relativamente melhores para ofertar lance; -- outubro pode ficar mais competitivo por coincidir com pagamento de PLR de funcionários do BB; -- muitos participantes dos consórcios do BB seriam funcionários do próprio banco. +## 5. Filosofia financeira e psicológica -Essas afirmações precisam ser verificadas com dados do grupo sempre que forem usadas. Não extrapolar um grupo para todos os grupos. +O usuário valoriza possuir uma residência física de longo prazo. Essa segurança tem peso legítimo na decisão, especialmente com o avanço da idade e a preocupação com estabilidade futura. -A estratégia atual é não agir com pressa e observar os percentuais de lances ao longo do tempo. +Ao mesmo tempo: ---- +- reserva de emergência não deve ser sacrificada; +- prestação maior que o aluguel atual exige justificativa e folga; +- capacidade máxima de financiamento não é orçamento confortável; +- custo de oportunidade da entrada deve ser medido; +- reforma e mudança têm custo psicológico, além do financeiro; +- comprar para aliviar ansiedade pode produzir uma decisão pior. -## 8. Fatores familiares e logísticos - -- O usuário tem uma filha em idade escolar. -- A namorada mora em São Sebastião e tem um filho. -- Jardins Mangueiral pode reduzir a distância para a namorada e a família dela. -- O usuário tem flexibilidade de trabalho e de horário em parte da semana. -- A logística familiar deve ser considerada, mas não deve justificar um imóvel inadequado. -- A compra deve continuar válida mesmo se circunstâncias relacionais mudarem. +A conclusão financeira não deve ignorar o benefício de estabilidade, e o benefício psicológico não deve ignorar juros, liquidez e risco. --- -## 9. Horizonte +## 6. Mercado e dados -A decisão deve ser avaliada como residência e patrimônio de longo prazo. +O banco contém anúncios, observações históricas e fotos. Fotografias são essenciais porque campos estruturados e descrições não capturam adequadamente: -Questões centrais: +- planta; +- conservação; +- iluminação; +- ventilação; +- qualidade da reforma; +- fechamento do quintal; +- redundância de áreas gourmet; +- sinais visuais de anúncio vendido; +- duplicatas entre corretores. -- O imóvel continuará funcional em 10, 15 ou 20 anos? -- A localização atende a rotina sem depender de uma única circunstância atual? -- O custo mensal permanece confortável em cenários adversos? -- Existe reserva para manutenção, saúde, carro e emergências? -- A planta permite adaptação? -- A revenda é razoável? -- A compra reduz ou aumenta a fragilidade financeira? +Preço anunciado é evidência de oferta, não transação. --- -## 10. Questões em aberto +## 7. Regiões fora do foco -Manter estas questões explícitas até que os dados permitam respondê-las: +Outras regiões já foram discutidas, incluindo Asa Sul, Asa Norte, Guará I, Lúcio Costa, Águas Claras e outras. Elas não compõem o foco operacional atual. -- valor atual disponível em dinheiro e FGTS; -- limite confortável de prestação; -- prazo máximo desejável; -- custo total aceitável de aquisição; -- peso relativo entre Cruzeiro e Jardins Mangueiral; -- blocos e quadras prioritários no Cruzeiro; -- QCs prioritárias no Mangueiral após visitas; -- tolerância a imóveis sem elevador ou garagem; -- impacto real do trânsito; -- custo de manutenção de casas versus apartamentos; -- tamanho mínimo funcional; -- horizonte provável de permanência; -- estratégia final entre financiamento, consórcio e espera. +Não ampliar automaticamente o universo de busca sem instrução. Elas podem ser usadas como referência comparativa quando isso responder a uma pergunta específica. --- -## 11. Fonte de verdade - -Este documento consolida preferências e premissas, mas não substitui uma confirmação recente quando a decisão depender de: +## 8. Horizonte de decisão -- saldo financeiro; -- renda; -- taxas bancárias; -- regras de FGTS; -- custos cartorários; -- disponibilidade de crédito; -- preços atuais; -- condições de consórcio; -- situação familiar ou logística. +Perguntas obrigatórias para qualquer candidato: -Toda informação temporal deve carregar data de referência. +- continuará funcional em 10, 15 ou 20 anos? +- atende à rotina sem depender de uma única circunstância atual? +- permite preservar reserva e capacidade de poupança? +- é habitável sem obra extensa? +- possui saída razoável para revenda? +- reduz ou aumenta a fragilidade financeira? +- oferece qualidade de vida suficiente para justificar a mudança? diff --git a/docs/PROPERTY_CRITERIA.md b/docs/PROPERTY_CRITERIA.md index 8fd2ced..a0b2d2e 100644 --- a/docs/PROPERTY_CRITERIA.md +++ b/docs/PROPERTY_CRITERIA.md @@ -1,438 +1,278 @@ # Critérios de avaliação dos imóveis -## 1. Estrutura da avaliação +## 1. Classificação -Classificar cada critério como: +Cada achado deve ser marcado como: -- **eliminatório**: imóvel deve ser excluído; -- **forte**: pode inviabilizar a compra mesmo com bom preço; -- **moderado**: influencia comparação e negociação; -- **informativo**: deve ser registrado, mas não decide sozinho. +- **eliminatório**; +- **forte**; +- **moderado**; +- **informativo**; +- **pendente de confirmação**. -Não criar pesos numéricos arbitrários sem aprovação. Quando usar pontuação, mostrar os pesos, a justificativa e o efeito de cada critério. +Falta de informação não é aprovação nem eliminação automática. --- -## 2. Critérios eliminatórios gerais +## 2. Critérios eliminatórios ou de quarentena -Excluir ou colocar em quarentena analítica quando houver: +- planta incompatível com a necessidade de longo prazo; +- no Mangueiral, planta originalmente de dois quartos; +- documentação que impeça a estratégia de financiamento e não possa ser regularizada; +- problema estrutural, umidade grave ou reforma extensa sem desconto suficiente; +- ruído intolerável; +- preço total incompatível com a reserva; +- localização materialmente divergente; +- anúncio com preço ou área absurdamente inconsistente; +- indicação visual ou textual de “VENDIDO” ou “negócio fechado”; +- duplicata provável sem identificação do anúncio principal; +- conflito relevante entre título, descrição, fotos e campos. -- documentação incompatível ou não esclarecida; -- área, tipologia ou localização materialmente divergentes; -- planta que não atenda às necessidades de longo prazo; -- preço total incompatível com a preservação da reserva; -- problemas graves de umidade, infiltração ou estrutura; -- ruído intolerável identificado em visita; -- custo oculto que destrua a vantagem aparente; -- anúncio sem informação mínima para comparação; -- indícios relevantes de fraude ou inconsistência. - -Um imóvel não deve ser eliminado apenas por falta de dados no anúncio. Nesse caso, classifique-o como pendente de verificação. +Anúncio vendido deve ficar em quarentena histórica, não ser apagado. --- -## 3. Critérios gerais de qualidade - -### 3.1 Planta e funcionalidade +## 3. Critérios gerais -Avaliar: +### Planta e funcionalidade -- distribuição dos cômodos; -- privacidade dos quartos; -- largura de circulação; -- áreas mortas; -- integração social; +- três quartos utilizáveis; +- circulação eficiente; +- privacidade; - capacidade de mobiliamento; - armazenamento; -- número e localização de banheiros; -- ventilação cruzada; +- integração social; - iluminação natural; -- possibilidade de adaptação futura; -- conflito entre portas, móveis e circulação; -- uso real de varandas, quintais e áreas gourmet. +- ventilação cruzada; +- banheiros funcionais; +- possibilidade de adaptação futura. -### 3.2 Estado de conservação +### Conservação e sistemas -Registrar separadamente: +Avaliar separadamente: - estrutura; - elétrica; - hidráulica; -- revestimentos; -- esquadrias; -- telhado; - impermeabilização; +- telhado; +- esquadrias; +- revestimentos; - pintura; -- armários; +- marcenaria; - climatização; -- equipamentos; -- sinais de reforma improvisada. +- sinais de obra improvisada. -Não resumir tudo como “reformado”. Uma reforma estética pode esconder sistemas antigos. +“Reformado” não comprova renovação dos sistemas. -### 3.3 Custos de adequação +### Conforto -Estimar: - -- reparos imediatos; -- intervenções em até dois anos; -- mobiliário obrigatório; -- eletrodomésticos; -- mudança; -- custo de desocupação, quando aplicável; -- custo de tempo e transtorno; -- contingência de obra. - -Aplicar margem de segurança para reformas. Não usar apenas o orçamento mais otimista. - -### 3.4 Conforto ambiental - -Avaliar em horários diferentes: - -- incidência solar; +- insolação; - calor; - ventilação; -- iluminação; -- ruído; +- ruído interno e externo; - odores; - privacidade; -- proximidade de vias; -- fluxo de pedestres; -- iluminação noturna; +- drenagem; - segurança percebida; -- drenagem e risco de alagamento. - -### 3.5 Liquidez e revenda - -Considerar: - -- tipologia; -- padrão da região; -- facilidade de financiamento; -- regularidade documental; -- garagem; -- elevador; -- posição; -- conservação; -- faixa de preço; -- público comprador provável; -- excesso de personalização; -- limitações permanentes de planta. - ---- - -## 4. Cruzeiro Novo +- internet e sinal de celular. -### 4.1 Pontos a registrar +### Custos de adequação -- quadra; -- bloco; -- posição no bloco; -- andar; -- orientação solar; -- vista; -- proximidade de vias e comércio; -- ruído; -- elevador; -- garagem ou estacionamento; -- estado das áreas comuns; -- taxa de condomínio; -- obras previstas; -- idade e conservação do prédio; -- prumadas; -- elétrica; -- fachada; -- acessibilidade; -- área privativa e área total; -- número original de quartos; -- alterações de planta; -- ocupação atual; -- situação documental. - -### 4.2 Dois versus três quartos - -No Cruzeiro, apartamentos anunciados como dois ou três quartos podem ter áreas semelhantes. - -A análise deve verificar: - -- planta original; -- quarto revertido; -- dependência transformada; -- sala dividida; -- ventilação do cômodo adicional; -- dimensão real dos quartos; -- impacto na circulação; -- valor de revenda. - -Não classificar automaticamente três quartos como superior se o terceiro cômodo for inadequado. - -### 4.3 Elevador e garagem - -Ausência de elevador ou garagem é uma concessão relevante, não um detalhe. - -Registrar: - -- andar; -- número de lances de escada; -- acessibilidade futura; -- facilidade de mudança; -- uso por idosos; -- disponibilidade real de estacionamento; -- segurança ao estacionar; -- regras do condomínio. - -O efeito no preço deve ser analisado com imóveis comparáveis. - -### 4.4 Reforma - -Imóveis antigos podem exigir modernização invisível no anúncio. - -Durante a visita, verificar: +Estimar: -- quadro elétrico; -- aterramento; -- tomadas; -- tubulações; -- registros; -- pressão de água; -- vazamentos; -- esquadrias; -- ruído entre unidades; -- estado da laje; -- gás; -- ventilação de banheiros; -- infiltrações em fachadas; -- histórico de obras do bloco. +- reparo imediato; +- obra em até dois anos; +- móveis e eletrodomésticos indispensáveis; +- regularização; +- mudança; +- contingência; +- tempo e transtorno. --- -## 5. Jardins Mangueiral - -### 5.1 Filtro de tipologia +## 4. Jardins Mangueiral -Somente considerar casas com **planta de três quartos**. +### 4.1 Planta original -Casas originalmente de dois quartos devem ser excluídas, mesmo quando reformadas ou anunciadas como tendo três quartos. +Somente casas originalmente de três quartos. -A validação pode usar: +Validar por: -- planta original; +- planta padrão; +- implantação; - posição da escada; - dimensões; - fachada; -- implantação; - documentos; -- comparação com unidades padrão; -- fotografias. +- fotos; +- comparação com unidades conhecidas. -### 5.2 Quintal fechado e ampliações +Quarto adaptado não transforma uma planta original de dois quartos em candidata válida. -A perda do quintal original não é automaticamente um problema. +### 4.2 Quintal e ampliação -A conversão deve ser considerada positiva quando cria: +Conversão positiva: - sala ampliada; -- espaço de convivência; - ambiente de refeições; +- espaço de convivência; - escritório; -- área social ventilada; -- integração útil com a casa; -- armazenamento funcional. - -Penalizar quando a conversão: - -- duplica cozinha ou cooktop sem necessidade; -- cria circulação inútil; -- reduz ventilação e iluminação; -- aumenta calor; -- usa material de baixa qualidade; -- compromete impermeabilização; -- dificulta manutenção; -- cria ambiente sem função clara; -- prejudica privacidade; -- ocupa todo o espaço sem compensação funcional. - -### 5.3 Área gourmet +- armazenamento útil; +- integração ventilada com a casa. + +Conversão negativa: + +- segunda cozinha redundante; +- cooktop sem função clara; +- circulação de baixo valor; +- redução de luz e ventilação; +- calor excessivo; +- impermeabilização duvidosa; +- drenagem comprometida; +- manutenção difícil; +- ocupação total sem ganho funcional. + +### 4.3 Preferências funcionais confirmadas + +- cozinha bem planejada, espaçosa e, idealmente, semi-integrada à área gourmet; +- área gourmet com churrasqueira deve participar do fluxo social da casa, não ficar isolada; +- priorizar casas conservadas e que exijam poucas intervenções antes da mudança; +- boa marcenaria, quartos e banheiros funcionais têm peso material; +- piscina, jacuzzi e banheira não são desejadas e reduzem fortemente a adequação; +- cubas de vidro são indesejadas por limpeza e aparência; +- penalizar marcenaria barata ou degradada, cozinha mal executada e área externa abandonada; +- fechamento em vidro exige teste rigoroso de calor e ventilação pelo risco de efeito estufa; +- quarto convertido em closet é uma concessão quando precisar ser revertido no futuro; +- placas solares são bônus, condicionadas à documentação e ao estado do sistema; +- detalhes estéticos reversíveis, como cortinas e sancas, pesam menos que planta, cozinha, conservação e área social. + +### 4.4 Regularidade + +Confirmar: + +- cobertura e fechamento; +- drenagem e calhas; +- aprovação condominial; +- licenças quando aplicáveis; +- impacto em financiamento e seguro; +- área efetiva e conceito da área anunciada; +- placas solares e homologação; +- piscina, spa e impermeabilização. -Não atribuir valor automático ao rótulo “área gourmet”. +### 4.5 Ruído de vizinhança -Avaliar: +Investigar: -- frequência provável de uso; -- integração com a cozinha; -- redundância de equipamentos; -- exaustão; -- ventilação; -- limpeza; -- circulação; -- capacidade de receber pessoas; -- qualidade da execução; -- manutenção. - -Um segundo cooktop ao lado da cozinha principal pode ser redundante. +- paredes compartilhadas; +- passos e móveis; +- música; +- festas; +- cães; +- crianças; +- área de lazer; +- quadra esportiva; +- comércio; +- motos; +- coleta de lixo; +- obras. -### 5.4 QCs e acesso a serviços +--- -Relatos de moradores usados como referência anedótica: +## 5. Cruzeiro -- QCs 8 e 9: percebidas como mais fortes para acesso prático a comércio e serviços; -- QCs 6 e 7: alternativas consideradas; -- QCs 10 e 11: também consideradas boas. +### 5.1 Planta e quartos -Essas preferências não substituem verificação de campo. +No estoque do Cruzeiro, dois e três quartos podem ter área semelhante e o terceiro cômodo pode ser conversão. -Para cada QC, medir ou registrar: +Confirmar: -- distância a comércio; -- acesso a mercado, farmácia e serviços; -- saída viária; -- tempo em horários relevantes; -- transporte; -- topografia; -- iluminação; -- tráfego interno; -- ruído; -- estacionamento; -- áreas comuns; -- manutenção do condomínio. +- planta original; +- dependência convertida; +- sala dividida; +- ventilação e dimensão do terceiro quarto; +- impacto na circulação e revenda. -### 5.5 Ruído e vizinhança +O ranking corrente prioriza três quartos utilizáveis, mas a contagem anunciada não é suficiente. -Avaliar explicitamente: +### 5.2 Elevador, garagem e andar -- paredes compartilhadas; -- ruído de passos e móveis; -- música; -- festas; -- cães; -- crianças; -- áreas de convivência; -- quadras esportivas; -- comércio; -- portarias; -- vias internas; -- motos; -- coleta de lixo; -- obras; -- templos; -- escolas; -- bares. +Ausência de elevador ou garagem é concessão relevante. -Fazer visitas em mais de um horário quando possível, incluindo noite ou fim de semana. +Avaliar: -Perguntar aos moradores sem depender apenas do corretor. +- número de lances de escada; +- acessibilidade futura; +- mudança e uso por idosos; +- vagas vinculadas ou apenas informais; +- disputa por estacionamento; +- segurança noturna; +- posição do apartamento. -### 5.6 Reformas e regularidade +### 5.3 Prédio e sistemas Verificar: -- fechamento de área externa; -- cobertura; -- drenagem; -- calhas; -- telhado; -- impermeabilização; -- ventilação; -- padrão elétrico; -- hidráulica; -- alterações estruturais; -- aprovação condominial; -- regularidade documental; -- impacto em seguro e financiamento. +- quadro elétrico e aterramento; +- tubulações e registros; +- pressão de água; +- gás; +- prumadas; +- fachada; +- laje e infiltração; +- obras previstas; +- taxas extraordinárias; +- estado das áreas comuns; +- ruído entre unidades. --- -## 6. Checklist de visita +## 6. Fotografias e anúncios -### Antes da visita +As fotos fazem parte da evidência principal. -- salvar anúncio e data; -- registrar preço; -- registrar condomínio e IPTU; -- conferir área e quartos; -- buscar histórico do anúncio; -- verificar anúncios duplicados; -- mapear entorno; -- listar dúvidas. +Procedimento: -### Durante a visita - -- fotografar com autorização; -- registrar ruído com janelas abertas e fechadas; -- testar torneiras, descargas, chuveiros e registros; -- observar umidade e odores; -- verificar tomadas e quadro; -- medir cômodos críticos; -- conferir incidência solar; -- observar circulação; -- avaliar armazenamento; -- verificar sinal de celular e internet; -- examinar garagem ou estacionamento; -- conversar com porteiro ou moradores quando apropriado; -- perguntar motivo da venda; -- perguntar tempo de anúncio; -- perguntar reformas e documentação; -- verificar taxas extraordinárias. - -### Depois da visita +1. abrir a primeira foto atual; +2. procurar selo de vendido, permuta ou negócio fechado; +3. inspecionar galeria completa; +4. comparar imagens com outros anúncios; +5. verificar coerência de planta, estado e área; +6. identificar fotos antigas, decorativas ou de outro imóvel; +7. registrar qualidade e lacunas da evidência. -- registrar impressão no mesmo dia; -- separar fatos de percepções; -- estimar custos; -- comparar com alternativas reais; -- listar riscos; -- definir preço máximo; -- registrar pendências documentais; -- evitar decisão motivada apenas por entusiasmo. +Republicações visualmente equivalentes contam como um imóvel provável. --- -## 7. Negociação +## 7. Checklist de visita -O preço máximo deve considerar: +### Antes -- valor comparável; -- estado; -- custos de compra; -- reforma; -- liquidez; -- concessões; -- risco; -- tempo de anúncio; -- histórico de preço; -- condição de pagamento; -- custo de oportunidade. +- salvar anúncio, preço e data; +- revisar histórico; +- verificar duplicatas; +- mapear localização; +- listar pendências; +- calcular custo total preliminar. -Não negociar com base apenas no percentual de desconto. +### Durante -O desconto nominal deve ser comparado ao custo real de corrigir as deficiências. +- medir cômodos críticos; +- testar torneiras, registros e descargas; +- observar umidade e odores; +- conferir quadro elétrico; +- avaliar sol, calor e ventilação; +- medir ruído; +- conferir garagem ou estacionamento; +- perguntar motivo da venda e tempo de anúncio; +- pedir documentação e histórico de reformas; +- perguntar por taxas extraordinárias e conflitos de vizinhança. ---- +### Depois -## 8. Saída padrão por imóvel - -Cada ficha analítica deve conter: - -- URL e identificador; -- data de coleta; -- região, quadra, bloco ou QC; -- tipologia original; -- área; -- quartos; -- garagem; -- elevador; -- preço; -- condomínio; -- IPTU; -- preço por m² válido ou não; -- histórico de preço; -- possíveis duplicatas; -- pontos fortes; -- pontos fracos; -- reformas; -- custo estimado de adequação; -- riscos; -- pendências; -- posição relativa aos comparáveis; -- preço máximo sugerido, quando houver dados; -- decisão: excluir, monitorar, visitar, negociar ou candidato forte. +- registrar impressão no mesmo dia; +- separar fatos de entusiasmo; +- estimar custo de adequação; +- definir preço máximo; +- classificar: excluir, monitorar, aprofundar, visitar, negociar ou candidato forte. diff --git a/docs/SCRAPER_OPERATIONS.md b/docs/SCRAPER_OPERATIONS.md new file mode 100644 index 0000000..b285356 --- /dev/null +++ b/docs/SCRAPER_OPERATIONS.md @@ -0,0 +1,206 @@ +# Operação do scraper e do banco + +## 1. Arquitetura atual + +Estrutura esperada: + +```text +dfimoveis_data/ +├── dfimoveis.sqlite3 +├── photos/ +│ └── / +├── raw/ +│ ├── listing/ +│ └── search/ +└── browser-profile/ +``` + +O SQLite é a fonte estruturada. Fotos e HTML bruto são evidências complementares essenciais. + +--- + +## 2. Funções já previstas no scraper + +- múltiplas pesquisas; +- paginação com `pagina=N`; +- requisições assíncronas; +- concorrência controlada; +- retentativas; +- extração de páginas de anúncio; +- download de todas as fotos; +- armazenamento em SQLite; +- observações históricas e mudanças; +- HTML bruto comprimido; +- marcação de anúncios removidos ou inativos; +- fallback opcional por Playwright. + +Não simplificar o scraper de modo a perder histórico, fotos ou HTML bruto. + +--- + +## 3. Cloudflare e 403 + +Foi confirmado desafio JavaScript do Cloudflare, com referências a `challenges.cloudflare.com` e `cf-ray` associado a GRU. + +Consequências: + +- `httpx` isolado não resolve desafio JavaScript; +- trocar apenas User-Agent, aumentar retries ou reduzir concorrência não garante acesso; +- o scraper deve tentar o cliente HTTP normal e usar Playwright como fallback quando houver 403 ou challenge. + +### Playwright + +Configuração operacional: + +- Chromium visível no primeiro uso; +- perfil persistente em `dfimoveis_data/browser-profile`; +- reutilização de cookies e sessão; +- resolução manual do challenge quando necessário; +- depois, possibilidade de execução automatizada enquanto a sessão permanecer válida; +- não usar modo headless no primeiro desafio. + +O objetivo é usar uma sessão legítima do navegador, não tentar derrotar ou explorar mecanismos de proteção. + +Respeitar termos aplicáveis, limites de requisição e carga do site. + +--- + +## 4. User-Agent e sessão + +- usar `DFIMOVEIS_USER_AGENT` configurável; +- preservar cookies e perfil local; +- não gravar credenciais no repositório; +- não compartilhar o diretório `browser-profile`; +- incluir perfis e cookies no `.gitignore`. + +--- + +## 5. Banco em análise + +Caminho: + +```text +dfimoveis_data/dfimoveis.sqlite3 +``` + +Abrir em modo somente leitura: + +```python +connection = sqlite3.connect( + "file:dfimoveis_data/dfimoveis.sqlite3?mode=ro", + uri=True, +) +connection.execute("PRAGMA query_only = ON") +``` + +Não executar análises que escrevam no banco. + +Para transformações: + +- exportar Parquet/CSV; +- criar `data/derived/analytics.sqlite`; +- ou trabalhar em uma cópia consistente. + +--- + +## 6. Snapshot de 22/07/2026 + +- 162 anúncios; +- 637 observações históricas; +- 7.457 registros de foto: 3.521 da galeria primária e 3.936 evidências legadas excluídas da análise; +- Mangueiral: 95 anúncios acumulados, 78 ativos e 17 inativos; +- Cruzeiro Novo: 67 acumulados, 63 ativos e 4 inativos; +- coleta entre 13 e 22/07/2026; +- a partir da execução 21, somente o Mangueiral permanece no arquivo de buscas. + +A cobertura é curta. Não inferir liquidez, sazonalidade ou tendência de preço. + +O histórico legado contém mudanças infladas por campos textuais instáveis. O schema v2 usa `stable_content_hash`, exclui ruído de interface e registra mudanças materiais futuras em `listing_change_events`. + +### Migração v2 + +- backup anterior: `dfimoveis_data/backups/dfimoveis_before_schema_v2_20260722.sqlite3`; +- migração idempotente: `python scripts/migrate_dfimoveis_schema_v2.py`; +- nunca apagar fotos legadas: usar `v_primary_photos` ou `is_primary_gallery=1` nas análises; +- usar `creci_normalized`, `address_normalized` e `neighborhood_normalized`, preservando os campos brutos; +- consultar `v_listing_current_availability` e `v_open_data_quality_issues` para triagem; +- futuras execuções registram buscas e configuração em `crawl_runs`. + +--- + +## 7. Qualidade e deduplicação + +O scraper e a análise devem preservar três níveis: + +- anúncio; +- observação histórica; +- imóvel físico provável. + +Sinais de duplicidade: + +- imagens iguais ou muito semelhantes; +- mesma implantação; +- texto quase idêntico; +- mesmo endereço; +- mesmos quartos, área e preço; +- anúncios de corretores diferentes. + +Não apagar duplicatas. Registrar grupos, confiança e anúncio de referência. + +--- + +## 8. Disponibilidade e “vendido” + +Auditar: + +- primeira foto; +- descrição; +- status estruturado; +- atividade na coleta. + +Estados sugeridos: + +- ativo; +- inativo; +- vendido visualmente indicado; +- negócio fechado declarado; +- republicado; +- duplicata provável; +- status desconhecido. + +Selo visual é evidência operacional. Não prova transação registral. + +--- + +## 9. Observabilidade + +Cada execução deve registrar: + +- início e fim; +- versão do scraper; +- configuração; +- pesquisas executadas; +- páginas solicitadas e concluídas; +- status HTTP; +- desafios Cloudflare; +- anúncios vistos; +- fotos baixadas; +- erros; +- tempo de execução; +- número de anúncios marcados inativos. + +Diferenciar falha de coleta de redução real de estoque. + +--- + +## 10. Segurança operacional + +- não versionar banco, fotos, HTML bruto, cookies ou perfil de navegador; +- não expor tokens e sessões; +- limitar concorrência; +- usar backoff; +- evitar coleta agressiva; +- não remover dados históricos; +- manter backup antes de migrações; +- testar mudanças em banco de desenvolvimento; +- nunca migrar o banco original durante uma análise. diff --git a/reports/creci_26903_mangueiral/README.md b/reports/creci_26903_mangueiral/README.md new file mode 100644 index 0000000..060ff6a --- /dev/null +++ b/reports/creci_26903_mangueiral/README.md @@ -0,0 +1,55 @@ +# Imóveis do CRECI 26.903 — Jardins Mangueiral + +**Snapshot:** 21/07/2026 +**Fonte:** `dfimoveis_data/dfimoveis.sqlite3`, aberto somente para leitura. + +## Pergunta + +Quais anúncios de imóveis no Jardins Mangueiral estão associados ao CRECI **26.903**, separados entre vendidos e disponíveis? + +## Método e ressalva + +O portal apresenta o registro como `26.903`. O campo `creci` foi normalizado para a busca removendo pontos e espaços, porque seu conteúdo bruto inclui texto residual da página. Foram encontrados **15 anúncios acumulados**, todos ainda marcados como ativos pelo coletor no snapshot. + +A separação abaixo usa a primeira foto corrente: + +- **vendido:** há selo explícito “VENDIDO”, “negócio fechado” ou equivalente; +- **disponível:** anúncio ativo e sem selo visual de venda na primeira foto. + +“Disponível” significa apenas **aparentemente disponível no portal**. É necessário confirmar diretamente com o anunciante. Da mesma forma, o selo de vendido é evidência operacional, não prova registral da transação. + +## Vendidos — 8 anúncios + +| Anúncio | QC | Preço anunciado | Área estruturada | Evidência | +|---:|---:|---:|---:|---| +| [1366801](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1366801) | 11 | R$ 575 mil | 68 m² | selo na primeira foto | +| [1346017](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346017) | 10 | R$ 577 mil | 68 m² | selo na primeira foto | +| [1349519](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1349519) | 14 | R$ 640 mil | 68 m² | selo na primeira foto | +| [1356197](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1356197) | 4 | R$ 649 mil | 68 m² | selo na primeira foto | +| [1346231](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346231) | 10 | R$ 660 mil | 68 m² | selo na primeira foto | +| [1352943](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-3-1352943) | 3 | R$ 670 mil | 68 m² | selo na primeira foto | +| [1351729](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-9-1351729) | 9 | R$ 680 mil | 68 m² | selo na primeira foto | +| [1362968](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-13-1362968) | 13 | R$ 730 mil | 68 m² | selo na primeira foto | + +## Aparentemente disponíveis — 7 anúncios + +| Anúncio | QC | Preço anunciado | Área estruturada | Situação no snapshot | +|---:|---:|---:|---:|---| +| [1173275](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-15-1173275) | 15 | R$ 549 mil | 68 m² | ativo, sem selo de venda | +| [1336887](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-condominio-jardins-mangueiral-1336887) | não informado | R$ 550 mil | 65 m² | ativo, sem selo de venda | +| [1363016](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1363016) | 14 | R$ 550 mil | 68 m² | ativo, sem selo de venda | +| [1345134](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1345134) | 11 | R$ 640 mil | 68 m² | ativo, sem selo de venda | +| [1324014](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-12-rua-k-1324014) | 12 | R$ 649 mil | 68 m² | ativo, sem selo de venda | +| [1358920](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-13-1358920) | 13 | R$ 675 mil | 68 m² | ativo, sem selo de venda | +| [1278187](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1278187) | 10 | R$ 690 mil | 68 m² | ativo, sem selo de venda | + +## Leitura prática + +- O CRECI 26.903 concentra **8 de 15 anúncios com indicação visual de venda** no snapshot, ou 53,3% do recorte. +- Os oito continuam ativos tecnicamente no portal; portanto, o estado estruturado isolado superestima a oferta desse anunciante. +- Não é possível concluir velocidade de venda: o banco começou a observar esses anúncios em 13/07/2026, e não contém a data da negociação. +- Os anúncios podem representar estoque captado pelo mesmo corretor ou imobiliária, não necessariamente imóveis de sua propriedade. + +## Reprodução + +A consulta usada está em [`query.sql`](query.sql). A classificação visual está registrada em [`results.csv`](results.csv) e não é inferida pelo SQL. diff --git a/reports/creci_26903_mangueiral/query.sql b/reports/creci_26903_mangueiral/query.sql new file mode 100644 index 0000000..5337d5d --- /dev/null +++ b/reports/creci_26903_mangueiral/query.sql @@ -0,0 +1,15 @@ +-- Executar exclusivamente em conexão read-only. +SELECT + listing_id, + price_brl, + area_m2, + bedrooms, + address, + inactive_at, + first_seen_at, + last_seen_at, + url +FROM listings +WHERE lower(url) LIKE '%jardins-mangueiral%' + AND creci_normalized = '26903' +ORDER BY price_brl, listing_id; diff --git a/reports/creci_26903_mangueiral/results.csv b/reports/creci_26903_mangueiral/results.csv new file mode 100644 index 0000000..6f52190 --- /dev/null +++ b/reports/creci_26903_mangueiral/results.csv @@ -0,0 +1,16 @@ +listing_id,classification,qc,price_brl,area_m2,database_active,first_photo_sale_marker,url +1366801,sold_visual,11,575000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1366801 +1346017,sold_visual,10,577000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346017 +1349519,sold_visual,14,640000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1349519 +1356197,sold_visual,4,649000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1356197 +1346231,sold_visual,10,660000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346231 +1352943,sold_visual,3,670000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-3-1352943 +1351729,sold_visual,9,680000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-9-1351729 +1362968,sold_visual,13,730000,68,true,true,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-13-1362968 +1173275,apparently_available,15,549000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-15-1173275 +1336887,apparently_available,,550000,65,true,false,https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-condominio-jardins-mangueiral-1336887 +1363016,apparently_available,14,550000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1363016 +1345134,apparently_available,11,640000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1345134 +1324014,apparently_available,12,649000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-12-rua-k-1324014 +1358920,apparently_available,13,675000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-13-1358920 +1278187,apparently_available,10,690000,68,true,false,https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1278187 diff --git a/reports/cruzeiro_top10/README.md b/reports/cruzeiro_top10/README.md deleted file mode 100644 index 7bb3b99..0000000 --- a/reports/cruzeiro_top10/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Ranking preliminar — apartamentos no Cruzeiro Novo - -**Data de referência do snapshot:** 17/07/2026 -**Relatório revisado:** 17/07/2026 -**Unidade principal:** imóvel físico provável, não snapshot nem anúncio isolado -**Finalidade:** priorizar visitas; não estimar preço negociado nem substituir vistoria - -## Pergunta - -Quais são os dez imóveis do Cruzeiro Novo que mais merecem atenção segundo as preferências e regras do projeto, dando às fotografias importância comparável à dos dados estruturados? - -## Universo e método - -Foram avaliados os 65 anúncios encontrados pela busca: - -`https://www.dfimoveis.com.br/venda/df/cruzeiro/novo/apartamento/3-quartos` - -A coleta cobre um único dia e um único portal. O banco foi aberto somente em leitura. Os campos foram complementados com os HTMLs brutos, pois localização e descrição no SQLite têm limitações já documentadas em `docs/ACTUAL_SCHEMA.md`. - -Na coleta concluída em 17/07, os 65 anúncios do Cruzeiro permaneceram ativos e nenhum anúncio novo entrou. O único preço alterado foi o do anúncio `1372539`, de R$ 572.500 para R$ 569.000; ele continua fora do ranking porque a descrição confirma planta de três quartos convertida em dois. Assim, a ordem foi mantida após revisão das mudanças materiais. - -Não foi criada pontuação numérica, pois os pesos não foram aprovados. A ordem é uma **prioridade de visita ajustada por risco**, aplicando: - -1. critérios eliminatórios e inconsistências; -2. planta funcional de três quartos; -3. indícios de reforma relevante ou custos escondidos; -4. iluminação, ventilação, distribuição e conservação visíveis; -5. andar, elevador e garagem; -6. preço, área e condomínio anunciados; -7. qualidade e completude da evidência. - -### Revisão fotográfica - -Em 17/07/2026, a primeira foto dos dez escolhidos foi auditada especificamente para marcas de “vendido” ou “negócio fechado”. Nenhuma das dez apresentou esse sinal. - -A primeira passagem examinou até 16 imagens de cada um dos 65 anúncios, totalizando 1.029 arquivos. Depois, foram examinadas todas as 532 fotos dos 17 finalistas. Descontada a sobreposição entre as etapas, 1.289 arquivos distintos foram efetivamente vistos. - -As imagens foram usadas para observar conservação, coerência da reforma, luz, ventilação aparente, tamanho relativo dos quartos, circulação, integração social, cozinha, banheiros, área de serviço, fachada e estacionamento. Fotografias não permitem validar elétrica, hidráulica, infiltração, ruído, estrutura ou documentação. - -Muitos anúncios incluem, depois das fotos do imóvel, imagens genéricas do portal, logos e até cartões de outros imóveis marcados como “vendido”. Esses ativos foram desconsiderados na avaliação visual do apartamento. - -## Ranking - -### 1. Anúncio 1345146 — SHCES 1207, bloco A - -- **Anunciado:** R$ 670.000; 65 m²; condomínio R$ 605; 3 quartos, 1 suíte; 4º andar. -- **Por que lidera:** é o conjunto mais equilibrado entre reforma pronta, planta preservada e acessibilidade. O anúncio declara elevador, diferencial forte para uso de longo prazo. -- **Fotos:** reforma coerente e bem conservada; cozinha planejada, área de serviço separada, dois banheiros atualizados, três quartos reconhecíveis e boa luz. Os dormitórios e a área de serviço parecem compactos, mas funcionais. -- **Pendências:** confirmar funcionamento/obras do elevador, posição solar, área da matrícula, estacionamento/garagem e sistemas elétrico e hidráulico. -- **Ação:** candidato forte; visitar primeiro. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1207-bloco-a-1345146 - -### 2. Anúncio 1232283 — SHCES 309, bloco B - -- **Anunciado:** R$ 600.000; 70 m²; condomínio R$ 497; 3 quartos, 1 suíte; 3º andar; 2 vagas; nascente e vazado. -- **Por que está no topo:** maior área entre os primeiros colocados, preço competitivo, duas vagas anunciadas e reforma recente. O anúncio informa vista livre e ocupação imediata. -- **Fotos:** ambientes claros, armários abundantes, dois banheiros modernos e reforma consistente. Um dos dormitórios parece estreito; a qualidade da circulação precisa ser sentida presencialmente. -- **Pendências:** confirmar se as duas vagas são privativas/vinculadas e se há elevador — ele não é informado. No 3º andar, sua ausência é concessão forte. -- **Ação:** candidato forte; visitar, condicionado à confirmação das vagas e acessibilidade. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-bloco-b-1232283 - -### 3. Anúncio 1357415 — SHCES 1109 - -- **Anunciado:** R$ 630.000; 67,2 m²; condomínio R$ 350; 3 quartos, 1 suíte; 4º andar; nascente e vazado. -- **Por que se destaca:** reforma visualmente mais homogênea que a maioria, bloco revitalizado, vista livre, boa ventilação e condomínio anunciado baixo. -- **Fotos:** sala social bem resolvida, cozinha planejada, marcenaria útil, dois banheiros atualizados, quartos claros e conservação convincente. Não aparecem sinais evidentes de obra imediata. -- **Pendências:** não há elevador informado e o imóvel está no 4º andar; confirmar isso antes da visita. Verificar estacionamento, escopo real da reforma e autenticidade do condomínio de R$ 350. -- **Ação:** candidato forte se a escada for aceitável; caso contrário, rebaixar fortemente. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-1357415 - -### 4. Anúncio 1357633 — SHCES 309 - -- **Anunciado:** R$ 600.000; 64 m²; 3 quartos; 2º andar; nascente, vazado e de canto. -- **Por que se destaca:** o anúncio declara reforma de elétrica, hidráulica, pisos e acabamentos, além de documentação regular. O 2º andar reduz o impacto provável da ausência de elevador. -- **Fotos:** acabamento moderno e uniforme, boa marcenaria, iluminação natural e três dormitórios aparentes. A planta permanece convencional e funcional; não há suíte anunciada, mas há banheiro de serviço. -- **Pendências:** condomínio, IPTU, garagem e elevador estão ausentes. Confirmar notas/escopo da reforma e ventilação dos banheiros. -- **Ação:** candidato forte; visitar. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-1357633 - -### 5. Anúncio 1376958 — SHCES 911, bloco B - -- **Anunciado:** R$ 599.000; 62 m²; condomínio R$ 600; 3 quartos, 1 suíte; 2º andar; nascente e vazado. -- **Por que se destaca:** combina preço baixo no grupo reformado, andar razoável, suíte e ventilação cruzada declarada. -- **Fotos:** sala ampla para a metragem, cozinha e banheiros atualizados, boa vista e luz. O quarto com beliche aparenta ser pequeno, mas utilizável; a área de serviço é funcional, embora simples. -- **Pendências:** prédio sem elevador segundo a descrição e sem garagem informada. Confirmar área privativa, ruído da via visível e o que está incluído no condomínio. -- **Ação:** visitar. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-b-1376958 - -### 6. Anúncio 1345903 — SHCES 1303 - -- **Anunciado:** R$ 565.000; 64,5 m²; condomínio R$ 529; 3 quartos, 1 suíte; 1º andar; vazado. -- **Por que se destaca:** menor preço do top 10, acessibilidade melhor por estar no 1º andar, suíte e informação explícita de elétrica e esquadrias novas. -- **Fotos:** imóvel cuidado e aparentemente habitável sem obra imediata. Banheiros estão atualizados, mas cozinha, pisos e parte da marcenaria têm linguagem mais antiga. A planta parece íntegra e os três quartos são visíveis. -- **Pendências:** somente estacionamento público; confirmar ventilação, ruído do 1º andar, hidráulica e segurança das janelas. -- **Ação:** visitar com foco em custo real de modernização opcional. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1303-1345903 - -### 7. Anúncio 1339324 — SHCES 703 - -- **Anunciado:** R$ 665.000; 64 m²; condomínio R$ 390; 3 quartos, 1 suíte; 3º andar. -- **Por que se destaca:** reforma pronta, climatização, condomínio anunciado baixo e excelente aproveitamento de marcenaria. -- **Fotos:** uma das melhores apresentações: integração social coerente, cozinha funcional, dois banheiros bem executados, três quartos utilizáveis, boa iluminação e áreas comuns cuidadas. A circulação íntima parece estreita. -- **Pendências:** elevador, garagem, posição solar e escopo técnico da reforma não são informados. O 3º andar sem elevador seria concessão relevante. -- **Ação:** visitar após esclarecer acessibilidade e estacionamento. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-703-1339324 - -### 8. Imóvel provável dos anúncios 1370889 e 1375200 — SHCES 207 - -- **Anunciado:** R$ 695.000; 62 m²; condomínio entre R$ 435 e R$ 453; 3 quartos, 1 suíte. Um anúncio informa 2º andar. -- **Por que se destaca:** prédio reformado, planta social integrada, suíte e condição pronta para morar. -- **Fotos:** os dois anúncios mostram o mesmo apartamento com alta confiança visual. Sala/cozinha integradas funcionam bem, há área de serviço independente, dois banheiros atualizados e três dormitórios. A reforma parece consistente, embora bastante personalizada. -- **Pendências:** confirmar formalmente que os anúncios são da mesma unidade, andar, orientação solar, área, garagem e elevador. A duplicidade não deve inflar a oferta. -- **Ação:** visitar uma única vez após confirmar o anúncio primário. -- **URLs:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1370889 e https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1375200 - -### 9. Anúncio 1372268 — SHCES 305, bloco I - -- **Anunciado:** R$ 595.000; 62,61 m²; condomínio R$ 460; 3 quartos; 1º andar; canto e vazado; 2 vagas. -- **Por que entra:** duas vagas anunciadas e 1º andar são diferenciais duráveis. O preço deixa margem maior para atualização que os reformados mais caros. -- **Fotos:** planta convencional, clara e aparentemente conservada; cozinha e banheiros são funcionais, porém mais antigos. Não há sinal visual de obra estrutural, mas a modernização seria maior do que nos primeiros colocados. -- **Pendências:** confirmar documentalmente as duas vagas, ventilação, elétrica, hidráulica e orçamento real de adequação. -- **Ação:** visitar como alternativa de valor; não assumir que o desconto cobre reforma sem orçamento. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-bloco-i-1372268 - -### 10. Anúncio 1373131 — SHCES 305 - -- **Anunciado:** R$ 725.000; 65 m²; condomínio R$ 420; 3 quartos; 2º andar; nascente e vazado; sem elevador. -- **Por que entra:** é a reforma tecnicamente mais bem descrita: elétrica e hidráulica renovadas, projeto de arquiteta, boa orientação e ventilação cruzada. -- **Fotos:** execução visual limpa e consistente, cozinha e banheiro novos, três quartos preservados e boa luminosidade. Não possui suíte anunciada e a linguagem totalmente branca deve ser avaliada sem o efeito de grande-angular. -- **Pendências:** não há garagem informada; preço é alto para unidade sem suíte, elevador ou vaga. Confirmar documentação da reforma e medidas reais dos ambientes. -- **Ação:** visitar somente se a condição pronta justificar o prêmio pedido. -- **URL:** https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-1373131 - -## Quase entraram - -- **[1382466](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-101-bloco-c-1382466), R$ 650.000:** boa reforma aparente e uma vaga, mas faltam fotos da cozinha e informações de andar, elevador e orientação; aprofundar antes de substituir alguém do top 10. -- **[1290510](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-a-1290510), R$ 640.000:** três quartos com suíte, vazado e reformado, mas fica no 4º andar sem elevador informado e os valores de condomínio/IPTU foram claramente extraídos como R$ 1; precisa de confirmação. -- **[1371813](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-bloco-d-1371813), R$ 709.000:** reforma moderna e 68 m², mas o conjunto de fotos é curto e não esclarece bem três quartos, área de serviço, andar, garagem ou elevador. - -## Quarentenas e exclusões relevantes - -- **1373346:** é um imóvel no Noroeste capturado indevidamente pela busca; excluído. -- **1212351, 1338088, 1259968, 1315328 e 1372539:** planta de três quartos convertida em dois ou sala/escritório; não priorizados para residência de longo prazo. -- **1313649:** anunciado na descrição como dois quartos apesar do campo estruturado indicar três; condomínio de R$ 1.172; excluído até esclarecer planta. -- **1370861:** três quartos originais transformados em suíte master e escritório; não atende à funcionalidade desejada. -- **1365929, 1161400, 1278826 e similares:** fotos e descrição indicam reforma relevante, sem desconto claramente suficiente frente às alternativas prontas. -- **1366910, 1372290 e 1383534:** 22–35 fotos visualmente similares indicam o mesmo imóvel provável, mas os anúncios divergem entre SHCES 207 e SHCES 303 e entre 63 e 74 m². O conjunto seria competitivo, porém permanece em quarentena até confirmar endereço, área e anúncio válido. -- **1370948, 1371797 e 1372167:** grupo provável de anúncios duplicados, sem vantagem suficiente sobre o top 10. -- **1357834 e 1358242:** duplicata provável; há ainda um terceiro anúncio visualmente semelhante, 1382413. -- **1372907, 1348535 e 1380850:** elevador, garagem ou área podem ser atrativos, mas os preços de R$ 820 mil a R$ 990 mil não vêm acompanhados de vantagem funcional suficiente para superar as opções anteriores. A conclusão financeira depende de atualização dos saldos e limites do usuário. - -Uma imagem com selo “vendido” foi tratada como sinal de inconsistência, não como prova de venda, porque muitos anúncios contêm ativos genéricos de outros imóveis. - -## Limitações e verificações para visita - -O ranking é sensível a cinco informações ainda não confirmadas: tolerância pessoal a escadas, existência jurídica das vagas, conceito correto de área, qualidade invisível das reformas e limite financeiro atual. Antes de qualquer proposta: - -1. conferir matrícula, área privativa e vagas; -2. testar elétrica, hidráulica, pressão, esquadrias e sinais de infiltração; -3. medir os três quartos e validar que permanecem funcionais; -4. visitar em horário de tráfego e à noite para ruído e estacionamento; -5. verificar elevador, obras e atas do condomínio; -6. confirmar condomínio, IPTU, taxas extras e orientação solar; -7. comparar preço máximo somente depois de atualizar recursos, FGTS, reserva e prestação confortável. - -## Reprodutibilidade - -- `analysis.py`: extrai banco e HTMLs em leitura, produzindo TSV. -- `photo_similarity.py`: sinaliza duplicatas por hash visual simples; os resultados exigem revisão humana. -- `query.sql`: consultas principais de universo e qualidade. -- `parameters.json`: parâmetros e fingerprint do snapshot. -- `results.csv`: ranking em formato tabular. diff --git a/reports/cruzeiro_top10/__pycache__/analysis.cpython-314.pyc b/reports/cruzeiro_top10/__pycache__/analysis.cpython-314.pyc deleted file mode 100644 index c945a61a2c120755571d39f6967ae32c356f02ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6342 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 Nro*5b^dTbl{{kMLW-R~! diff --git a/reports/cruzeiro_top10/__pycache__/photo_similarity.cpython-314.pyc b/reports/cruzeiro_top10/__pycache__/photo_similarity.cpython-314.pyc deleted file mode 100644 index e7f824eaa009632e15296ac1379e663126c621a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4667 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 D-tz&T diff --git a/reports/cruzeiro_top10/parameters.json b/reports/cruzeiro_top10/parameters.json deleted file mode 100644 index 3c49b3d..0000000 --- a/reports/cruzeiro_top10/parameters.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "analysis_date": "2026-07-17", - "listing_data_reference_date": "2026-07-17", - "database_path": "dfimoveis_data/dfimoveis.sqlite3", - "database_main_file_sha256": "7a5102a62bc66a344bd9440fa36301d10a57711bcdd263b767b65e88f70957f4", - "logical_snapshot_sha256": "fe604aab8af7f67c833e6de2a0fc81c7dc2950ca440ad7b23ebd03d0c2546188", - "database_mode": "read_only_transaction_with_wal", - "search_url_fragment": "/cruzeiro/novo/", - "listing_limit": 100, - "observed_listing_count": 65, - "database_crawl_run_count": 16, - "active_listing_count": 65, - "new_listing_count_since_previous_report": 0, - "material_price_change_count": 1, - "ranking_unit": "probable_physical_property", - "numeric_score_used": false, - "photo_first_pass_limit_per_listing": 16, - "photo_finalist_count": 17, - "photo_files_reviewed_distinct": 1289, - "first_photo_sale_marker_audit_count": 10, - "visually_unavailable_listing_ids": [] -} diff --git a/reports/cruzeiro_top10/results.csv b/reports/cruzeiro_top10/results.csv deleted file mode 100644 index 1182129..0000000 --- a/reports/cruzeiro_top10/results.csv +++ /dev/null @@ -1,11 +0,0 @@ -rank,representative_listing_id,probable_duplicate_ids,price_brl,area_m2,bedrooms,suites,floor,elevator,parking_spaces,decision -1,1345146,,670000,65,3,1,4,yes,,candidato forte -2,1232283,,600000,70,3,1,3,unknown,2,candidato forte -3,1357415,,630000,67.2,3,1,4,unknown,,candidato forte condicionado a escadas -4,1357633,,600000,64,3,0,2,unknown,,visitar -5,1376958,,599000,62,3,1,2,no,,visitar -6,1345903,,565000,64.5,3,1,1,unknown,0,visitar -7,1339324,,665000,64,3,1,3,unknown,,visitar após esclarecer acessibilidade -8,1370889,1375200,695000,62,3,1,2,unknown,,visitar uma única vez -9,1372268,,595000,62.61,3,0,1,unknown,2,visitar como alternativa de valor -10,1373131,,725000,65,3,0,2,no,,visitar se o prêmio pela reforma for aceitável diff --git a/reports/cruzeiro_top15/README.md b/reports/cruzeiro_top15/README.md new file mode 100644 index 0000000..cddfd78 --- /dev/null +++ b/reports/cruzeiro_top15/README.md @@ -0,0 +1,64 @@ +# Top 15 — Cruzeiro Novo + +**Snapshot:** 21/07/2026 + +**Objetivo:** priorizar investigação para moradia de 10–20 anos, mantendo o Cruzeiro como alternativa secundária e oportunística; preço anunciado não é preço negociado. + +## Universo e método + +O banco `dfimoveis_data/dfimoveis.sqlite3` foi aberto em modo somente leitura (`mode=ro`, `PRAGMA query_only=ON`). O snapshot contém 161 anúncios, 562 observações históricas e 7.363 fotos; no recorte do Cruzeiro Novo há 67 anúncios acumulados, 63 ativos e 4 inativos. A coleta cobre 13–21/07/2026, período curto demais para inferir liquidez. + +Em 21/07 entraram `1381514` e `1400223`; `1375199` e `1383814` ficaram inativos. O primeiro novo anúncio é reformado, mas está no 4º andar e não informa elevador ou vaga. O segundo custa R$ 495 mil e está no 2º andar, porém as fotos mostram necessidade de reforma ampla. Ambos foram incluídos como investigações condicionadas, não como candidatos fortes. + +O ranking considera três quartos utilizáveis, funcionalidade, condição aparente, iluminação/ventilação, acessibilidade futura, elevador, natureza da vaga, custo de adequação e qualidade da evidência. Não usa preço por m², pois área e qualidade dos campos não são uniformes. Republicações visualmente equivalentes contam como um imóvel provável. A falta de informação é marcada como pendência, nunca como aprovação. + +## Auditoria da primeira foto + +A primeira foto corrente dos finalistas, dos dois novos anúncios e de `1263868` foi aberta e inspecionada para “vendido”, “negócio fechado” ou equivalente. O anúncio `1263868`, ainda ativo no banco, continua com **VENDIDO** na foto principal e permanece em quarentena. Nenhum dos 15 colocados apresenta esse indicativo. A imagem é evidência operacional de indisponibilidade, não prova registral da venda. + +A triagem perceptual foi reexecutada com Pillow 12.3.0 e confirmou os principais grupos de republicação já documentados. Entre os finalistas, `1348535` teve somente três fotos semelhantes às de `1370861`; os endereços distintos (SHCES 1307 e 913) não sustentam deduplicação. O ranking permaneceu inalterado. + +Todos os classificados continuam com a tipologia original de três quartos apenas como **provável e pendente de confirmação**. A contagem estruturada, isoladamente, não prova que o terceiro quarto seja original e funcional. + +## Ranking + +| # | Anúncio | Local | Preço | Área informada | Por que priorizar | Principal pendência | +|---:|---|---|---:|---:|---|---| +| 1 | [1345146](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1207-bloco-a-1345146) | SHCES 1207 A | R$ 670 mil | 65 m² | Reforma coerente, suíte e elevador anunciado | Vaga, matrícula e sistemas | +| 2 | [1357415](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-1357415) | SHCES 1109 | R$ 630 mil | 67,2 m² | Boa reforma, suíte, nascente e vazado | Confirmar elevador | +| 3 | [1357633](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-1357633) | SHCES 309 | R$ 600 mil | 64 m² | Reforma uniforme e sistemas renovados declarados | Elevador, garagem e comprovação da obra | +| 4 | [1376958](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-b-1376958) | SHCES 911 B | R$ 599 mil | 62 m² | Reformado, nascente, vazado e suíte | Sem elevador; garagem não informada | +| 5 | [1345903](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1303-1345903) | SHCES 1303 | R$ 565 mil | 64,5 m² | Primeiro andar e preço competitivo | Sem garagem; ruído e segurança | +| 6 | [1339324](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-703-1339324) | SHCES 703 | R$ 665 mil | 64 m² | Integração social, suíte e boa apresentação | Andar, elevador e garagem | +| 7 | [1370889](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1370889) / [1375200](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1375200) | SHCES 207 | R$ 695 mil | 62 m² | Reformado e com suíte | Duplicata provável; andar, elevador e vaga | +| 8 | [1372268](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-bloco-i-1372268) | SHCES 305 I | R$ 595 mil | 62,61 m² | Primeiro andar e duas vagas anunciadas | Provar vagas e orçar modernização | +| 9 | [1373131](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-1373131) | SHCES 305 | R$ 725 mil | 65 m² | Reforma tecnicamente bem descrita | Sem elevador/garagem e preço alto | +| 10 | [1381514](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-301-1381514) | SHCES 301 | R$ 690 mil | 63 m² estruturados; 93 m² nos detalhes | Reforma completa aparente, boa marcenaria e três quartos fotografados | 4º andar; elevador, vaga e conceito de área não informados | +| 11 | [1382466](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-101-bloco-c-1382466) | SHCES 101 C | R$ 650 mil | 62 m² | Boa reforma aparente e uma vaga | Galeria curta; andar e elevador ausentes | +| 12 | [1290510](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-a-1290510) | SHCES 911 A | R$ 640 mil | 62 m² | Suíte, vazado e reformado | 4º andar; condomínio/IPTU extraídos como R$ 1 | +| 13 | [1371813](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-bloco-d-1371813) | SHCES 1109 D | R$ 709 mil | 68 m² | Reforma moderna e área acima do padrão | Fotos não comprovam toda a planta, vaga ou elevador | +| 14 | [1400223](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-609-bloco-d-1400223) | SHCES 609 D | R$ 495 mil | 62,61 m² | 2º andar, boa luz e preço com margem para obra | Reforma ampla, vaga/elevador ausentes e sistemas antigos | +| 15 | [1369327](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-203-bloco-g-1369327) | SHCES 203 G | R$ 490 mil | 62 m² | Vaga e preço com margem para adequação | Estado interno, 4º andar e ausência de elevador | + +## Quase entraram + +- [1375154](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1205-1375154), R$ 550 mil: 67,2 m² e boa luminosidade, mas pede modernização e confirmação de andar/vaga. +- [1370890](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-105-1370890), R$ 560 mil: primeiro andar e DCE, porém condição mais básica e sem garagem informada. +- [1368610](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-1368610), R$ 579,9 mil: boa distribuição e melhorias declaradas; falta vantagem clara frente aos classificados. +- [1370891](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1370891), R$ 520 mil: vazado e de preço baixo, mas sem vaga e ainda dependente de modernização; saiu apenas pela entrada de evidência nova. + +## Quarentenas e limitações + +- `1232283` e `1345890` ficaram inativos em 20/07; `1375199` e `1383814`, em 21/07. A ausência da busca não prova venda. +- `1263868` foi retirado por “VENDIDO” na primeira foto. `1322206` também declara venda fechada na descrição. +- `1212351`, `1338088`, `1259968`, `1315328`, `1372539`, `1313649` e `1370861` têm conversão ou conflito relevante de planta. +- Os grupos `1366910`/`1372290`/`1383534`, `1370948`/`1371797`/`1372167` e `1357834`/`1358242`/`1382413` são duplicatas prováveis ou têm endereço/área divergentes. +- O ranking serve para ordenar investigação. Andar, elevador, vaga, matrícula, instalações, ruído, insolação e custo de obra exigem confirmação. + +## Próxima ação + +Solicitar, antes de cada visita, planta ou matrícula, andar, existência de elevador, natureza jurídica da vaga, atas recentes do condomínio e histórico de elétrica/hidráulica. As posições 13–15 são alternativas de preço: só avançam se orçamento de reforma, escadas e ausência de garagem forem compatíveis com permanência de longo prazo e preservação da reserva. + +## Reprodutibilidade + +Execute `python reports/cruzeiro_top15/analysis.py` para a extração somente leitura. A ordenação final inclui julgamento fotográfico documentado neste arquivo e não é produzida automaticamente pelo script. diff --git a/reports/cruzeiro_top10/analysis.py b/reports/cruzeiro_top15/analysis.py similarity index 97% rename from reports/cruzeiro_top10/analysis.py rename to reports/cruzeiro_top15/analysis.py index dacea23..f88a2dc 100644 --- a/reports/cruzeiro_top10/analysis.py +++ b/reports/cruzeiro_top15/analysis.py @@ -75,7 +75,7 @@ def main() -> int: l.raw_html_path, COUNT(p.source_url) AS photo_count FROM listings AS l JOIN listing_searches AS s USING (listing_id) - LEFT JOIN photos AS p USING (listing_id) + LEFT JOIN photos AS p ON p.listing_id=l.listing_id AND p.is_primary_gallery=1 WHERE instr(s.search_url, ?) > 0 AND l.inactive_at IS NULL GROUP BY l.listing_id diff --git a/reports/cruzeiro_top15/parameters.json b/reports/cruzeiro_top15/parameters.json new file mode 100644 index 0000000..0d2d045 --- /dev/null +++ b/reports/cruzeiro_top15/parameters.json @@ -0,0 +1,20 @@ +{ + "analysis_date": "2026-07-21", + "listing_data_reference_date": "2026-07-21", + "database_path": "dfimoveis_data/dfimoveis.sqlite3", + "database_main_file_sha256": "bfbbdd09e88f76afd917ebd981b848b9321aeb5fc0602f712645d33e1a5ea76c", + "database_mode": "read_only_transaction_with_wal", + "search_url_fragment": "/cruzeiro/novo/", + "listing_limit": 100, + "observed_listing_count": 67, + "active_listing_count": 63, + "inactive_listing_count": 4, + "database_crawl_run_count": 20, + "ranking_size": 15, + "ranking_unit": "probable_physical_property", + "numeric_score_used": false, + "price_per_m2_used": false, + "first_photo_finalist_audit_count": 16, + "visually_unavailable_listing_ids": ["1263868"], + "context_documents_reviewed": ["docs/CURRENT_STATE.md", "docs/PROJECT_CONTEXT.md", "docs/LOCATION_CRITERIA.md", "docs/PROPERTY_CRITERIA.md", "docs/FINANCIAL_STRATEGY.md", "docs/SCRAPER_OPERATIONS.md", "docs/DATA_MODEL.md", "docs/ANALYSIS_RULES.md"] +} diff --git a/reports/cruzeiro_top10/photo_similarity.py b/reports/cruzeiro_top15/photo_similarity.py similarity index 92% rename from reports/cruzeiro_top10/photo_similarity.py rename to reports/cruzeiro_top15/photo_similarity.py index a48e4fe..837f14b 100644 --- a/reports/cruzeiro_top10/photo_similarity.py +++ b/reports/cruzeiro_top15/photo_similarity.py @@ -22,7 +22,7 @@ DATA_ROOT = DB_PATH.parent def difference_hash(path: Path) -> int | None: try: with Image.open(path) as image: - pixels = list(image.convert("L").resize((9, 8)).getdata()) + pixels = list(image.convert("L").resize((9, 8)).get_flattened_data()) except (OSError, UnidentifiedImageError): return None value = 0 @@ -48,7 +48,7 @@ def main() -> None: ), uncommon AS ( SELECT sha256 FROM photos - WHERE sha256 IS NOT NULL + WHERE sha256 IS NOT NULL AND is_primary_gallery=1 GROUP BY sha256 HAVING COUNT(DISTINCT listing_id) <= 5 ) @@ -56,7 +56,7 @@ def main() -> None: FROM photos AS p JOIN cruzeiro AS c USING (listing_id) JOIN uncommon AS u USING (sha256) - WHERE p.local_path IS NOT NULL + WHERE p.local_path IS NOT NULL AND p.is_primary_gallery=1 ORDER BY p.listing_id, p.ordinal """ ).fetchall() diff --git a/reports/cruzeiro_top10/query.sql b/reports/cruzeiro_top15/query.sql similarity index 97% rename from reports/cruzeiro_top10/query.sql rename to reports/cruzeiro_top15/query.sql index ae13f95..4645c76 100644 --- a/reports/cruzeiro_top10/query.sql +++ b/reports/cruzeiro_top15/query.sql @@ -26,6 +26,7 @@ SELECT p.listing_id, COUNT(*) AS photo_count FROM photos AS p JOIN listing_searches AS s USING (listing_id) WHERE instr(s.search_url, '/cruzeiro/novo/') > 0 + AND p.is_primary_gallery = 1 AND EXISTS ( SELECT 1 FROM listings AS l WHERE l.listing_id = p.listing_id AND l.inactive_at IS NULL diff --git a/reports/cruzeiro_top15/results.csv b/reports/cruzeiro_top15/results.csv new file mode 100644 index 0000000..ad61336 --- /dev/null +++ b/reports/cruzeiro_top15/results.csv @@ -0,0 +1,16 @@ +rank,representative_listing_id,probable_duplicate_ids,price_brl,area_m2,bedrooms,suites,floor,elevator,parking_spaces,original_three_bedroom_status,visual_status,decision +1,1345146,,670000,65,3,1,4,yes,,probable_pending_confirmation,no_sale_marker,strong_candidate +2,1357415,,630000,67.2,3,1,4,unknown,,probable_pending_confirmation,no_sale_marker,confirm_elevator_then_visit +3,1357633,,600000,64,3,0,2,unknown,,probable_pending_confirmation,no_sale_marker,visit +4,1376958,,599000,62,3,1,2,no,,probable_pending_confirmation,no_sale_marker,visit +5,1345903,,565000,64.5,3,1,1,unknown,0,probable_pending_confirmation,no_sale_marker,visit_as_value_option +6,1339324,,665000,64,3,1,3,unknown,,probable_pending_confirmation,no_sale_marker,clarify_accessibility_then_visit +7,1370889,1375200,695000,62,3,1,2,unknown,,probable_pending_confirmation,no_sale_marker,identify_primary_listing_then_visit_once +8,1372268,,595000,62.61,3,0,1,unknown,2,probable_pending_confirmation,no_sale_marker,prove_parking_then_visit +9,1373131,,725000,65,3,0,2,no,,probable_pending_confirmation,no_sale_marker,visit_if_reform_premium_is_acceptable +10,1381514,,690000,63,3,0,4,unknown,,probable_pending_confirmation,no_sale_marker,confirm_elevator_parking_and_area_then_visit +11,1382466,,650000,62,3,0,,unknown,1,probable_pending_confirmation,no_sale_marker,request_missing_rooms_before_visit +12,1290510,,640000,62,3,1,4,no,,probable_pending_confirmation,no_sale_marker,validate_costs_then_visit +13,1371813,,709000,68,3,1,,unknown,,probable_pending_confirmation,no_sale_marker,request_full_gallery_then_decide +14,1400223,,495000,62.61,3,0,2,unknown,,probable_pending_confirmation,no_sale_marker,quote_full_renovation_and_confirm_parking +15,1369327,,490000,62,3,0,4,no,1,probable_pending_confirmation,no_sale_marker,quote_reform_and_test_stairs diff --git a/reports/cruzeiro_vs_mangueiral/README.md b/reports/cruzeiro_vs_mangueiral/README.md index 175277a..61e400a 100644 --- a/reports/cruzeiro_vs_mangueiral/README.md +++ b/reports/cruzeiro_vs_mangueiral/README.md @@ -1,133 +1,88 @@ -# Ranking reconstruído — Cruzeiro Novo x Jardins Mangueiral +# Top 30 — Jardins Mangueiral x Cruzeiro Novo -**Data do snapshot:** 17/07/2026 -**Unidade:** imóvel físico provável -**Finalidade:** priorizar investigação e visitas; não estimar preço de transação nem substituir vistoria +**Snapshot:** 21/07/2026 -## Resumo executivo +**Unidade:** imóvel físico provável -O Jardins Mangueiral continua oferecendo os melhores candidatos por espaço, suíte, vagas e preço anunciado. O Cruzeiro Novo continua superior em centralidade, acesso ao Plano Piloto e menor dependência de carro. +**Finalidade:** ordenar investigação para moradia de 10–20 anos; não estimar preço de transação nem substituir vistoria e análise financeira. -A casa `1279892`, QC 10, permanece em primeiro lugar geral pelo equilíbrio entre R$ 595 mil, condição visual, três quartos prováveis e quintal convertido sem eliminação total da área aberta. O apartamento `1345146`, SHCES 1207, é o melhor do Cruzeiro e fica em segundo: reforma coerente, suíte e elevador anunciado, mas com preço maior e vaga ainda incerta. +## Resposta -Confiança do ranking: **moderada para priorização de visitas e baixa para decisão de compra**. Fotos e anúncios não confirmam documentação, estrutura, sistemas, ruído, conforto térmico ou preço negociável. +Após reaplicar o contexto do projeto, o **Jardins Mangueiral é a região principal** e o Cruzeiro Novo é alternativa secundária e oportunística. O Mangueiral tende a entregar mais espaço, suíte, vagas e ambiente residencial pelo preço; o Cruzeiro só supera essa preferência quando uma unidade concreta compensa claramente as concessões de escadas, garagem, reforma e funcionalidade pela centralidade. -## Universo e atualização +`1279892` permanece em primeiro lugar geral. `1345146` é o melhor apartamento e ocupa o terceiro lugar, atrás também de `1350367`. A confiança é **moderada para ordenar investigação e baixa para decidir compra**, pois documentação, estrutura, ruído, conforto térmico, custos de adequação e preço negociado não estão comprovados. -- Fonte: DF Imóveis. -- Banco: 159 anúncios, 295 snapshots e 6.272 mídias. -- Cruzeiro Novo: 65 anúncios acumulados e ativos. -- Jardins Mangueiral: 94 anúncios acumulados; 84 ativos no banco e 10 inativos. -- Cobertura útil: coletas de 13 e 17/07/2026. -- Histórico: ainda curto demais para tendência, liquidez ou tempo real de mercado. +## Universo, período e qualidade -O banco foi aberto com `mode=ro`, `PRAGMA query_only = ON` e transação de leitura consistente para incluir o WAL. O `quick_check` retornou `ok`. +- Fonte: DF Imóveis; cobertura observada de 13–21/07/2026. +- Banco aberto com `mode=ro`, `PRAGMA query_only=ON`; `quick_check`: `ok`. +- 161 anúncios, 562 observações históricas e 7.363 fotos. +- Cruzeiro: 67 anúncios acumulados, 63 ativos e 4 inativos. +- Mangueiral: 94 acumulados, 78 ativos e 16 inativos. +- A coleta de 21/07 adicionou dois anúncios do Cruzeiro e nenhum do Mangueiral; 20 anúncios acumulados estão inativos. +- Das 401 transições históricas observadas, 398 envolvem o campo `creci` contaminado; somente três anúncios tiveram mudança real de preço. O histórico bruto não deve ser lido como 401 mudanças de mercado. +- Não há série suficiente para inferir liquidez, tendência ou venda a partir de retirada. +- Preços são de oferta; áreas não são comparáveis o bastante para preço por m². -## Método de reconstrução +## Método -O comparativo foi refeito a partir dos rankings regionais corrigidos, preservando a ordem relativa dentro de cada região. Não foram usados preço por metro quadrado nem pontuação numérica, porque as áreas das casas e apartamentos não são semanticamente comparáveis e os pesos não foram aprovados. +Foram relidos `PROJECT_CONTEXT.md`, `PROPERTY_CRITERIA.md`, `DATA_MODEL.md` e `ANALYSIS_RULES.md`. A ordem combina os rankings regionais sem pesos secretos e sem alternância automática, considerando: três quartos utilizáveis, funcionalidade futura, condição aparente, iluminação/ventilação, acessibilidade, vagas, regularidade, custo de adequação, logística familiar e qualidade da evidência. -A ordem geral combina: +No Mangueiral, planta original de três quartos é filtro obrigatório; quintal fechado só é negativo quando reduz luz/ventilação ou cria ambiente redundante e de manutenção difícil. No Cruzeiro, escadas, ausência de garagem e sistemas antigos são concessões materiais para permanência de longo prazo. A proximidade de São Sebastião é benefício logístico, não fundamento exclusivo da decisão. -1. funcionalidade de longo prazo e três quartos utilizáveis; -2. condição visual e risco de reforma; -3. iluminação, ventilação, circulação e armazenamento aparentes; -4. preço, custos conhecidos e margem para adequação; -5. acessibilidade, vagas e dependência de carro; -6. riscos específicos da tipologia; -7. qualidade e coerência da evidência fotográfica; -8. compatibilidade da localização com a rotina registrada no projeto. +A ordem regional do Mangueiral foi recalibrada após o usuário avaliar diretamente os 15 finalistas em 21/07 e, em seguida, o funil foi refeito sobre os 78 anúncios ativos. As notas pessoais prevalecem quando existem; anúncios novos entram por aderência inferida e ficam explicitamente pendentes de avaliação pessoal. Para não inventar equivalência numérica com apartamentos ainda não avaliados, as posições relativas do Cruzeiro foram mantidas. -No Cruzeiro, os principais ajustes de risco são escadas, ausência de garagem, sistemas antigos e obras do bloco. No Mangueiral, são planta original, regularização das ampliações, impermeabilização, calor/ventilação e logística. +Republicações visualmente equivalentes contam como um imóvel provável. Falta de informação é pendência, não aprovação. -## Auditoria de disponibilidade pelas fotos +## Auditoria visual de disponibilidade -A primeira foto dos 20 finalistas foi inspecionada especificamente para “vendido”, “negócio fechado” ou sinal equivalente. - -- Nenhum dos dez finalistas do Cruzeiro apresentou esse sinal. -- No Mangueiral, quatro anúncios que apareceram em versões anteriores foram removidos e colocados em quarentena visual: `1346231`, `1349519`, `1351729` e `1366801`. -- O banco ainda os tratava como ativos; para um ranking de visitas, a evidência visual prevaleceu como sinal de indisponibilidade operacional. -- A marca não comprova registro, preço ou conclusão jurídica da transação. +A primeira foto corrente dos 78 anúncios ativos do Mangueiral, dos finalistas do Cruzeiro e das quarentenas foi aberta e inspecionada. Nenhum dos 30 imóveis classificados atuais exibe “vendido”, “negócio fechado” ou equivalente. Ficam em quarentena `1263868` no Cruzeiro e `1301400`, `1346017`, `1346231`, `1349519`, `1351729`, `1352943`, `1356197`, `1362968` e `1366801` no Mangueiral. O sinal visual indica provável indisponibilidade operacional, não prova jurídica de venda. ## Ranking geral -| # | Região | Anúncio | Local | Preço | Motivo principal | Pendência decisiva | -|---:|---|---|---|---:|---|---| -| 1 | Mangueiral | [1279892](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-rua-f-1279892) | QC 10 | R$ 595.000 | Melhor equilíbrio entre preço, estado e espaço externo | Planta, áreas divergentes e regularização | -| 2 | Cruzeiro | [1345146](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1207-bloco-a-1345146) | SHCES 1207 A | R$ 670.000 | Melhor alternativa urbana; reformado e com elevador anunciado | Vaga, área da matrícula e sistemas | -| 3 | Mangueiral | [1350367](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-6-rua-c-1350367) | QC 6 | R$ 569.000 | Preço competitivo e ampliação social aparentemente funcional | Área, licenças, calor e drenagem | -| 4 | Cruzeiro | [1232283](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-bloco-b-1232283) | SHCES 309 B | R$ 600.000 | 70 m², suíte, nascente/vazado e duas vagas anunciadas | Elevador e natureza jurídica das vagas | -| 5 | Mangueiral | [1346017](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346017) | QC 10 | R$ 577.000 | Clara, pouco personalizada e com quintal majoritariamente aberto | Taxas, orientação e regularização da cobertura | -| 6 | Mangueiral | [1397378](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-8-rua-b-1397378) | QC 8 | R$ 600.000 | QC prática e implantação compatível com a planta de 68 m² | Manutenção, drenagem e galeria contaminada | -| 7 | Cruzeiro | [1357415](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-1357415) | SHCES 1109 | R$ 630.000 | Boa reforma, suíte e posição nascente/vazada | Confirmar elevador antes de agendar | -| 8 | Mangueiral | [1359960](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-7-1359960) | QC 7 | R$ 610.000 | Ampliação social flexível sem segunda cozinha completa evidente | Calor, ventilação e impermeabilização | -| 9 | Cruzeiro | [1357633](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-1357633) | SHCES 309 | R$ 600.000 | Reforma uniforme e sistemas declarados renovados | Elevador, garagem, condomínio e prova da reforma | -| 10 | Cruzeiro | [1376958](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-b-1376958) | SHCES 911 B | R$ 599.000 | Reformado, nascente, vazado e com suíte | Sem elevador e garagem não informada | -| 11 | Mangueiral | [1319996](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1319996) | QC 14 | R$ 649.000 | Reforma completa, marcenaria e placas solares | QC, cobertura, taxas e homologação solar | -| 12 | Cruzeiro | [1345903](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1303-1345903) | SHCES 1303 | R$ 565.000 | Menor preço competitivo e primeiro andar | Sem garagem; ruído e segurança do térreo | -| 13 | Mangueiral | [1324014](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-12-rua-k-1324014) | QC 12 | R$ 649.000 | Acabamento interno e marcenaria fortes | Vaga, redundância gourmet e ventilação | -| 14 | Cruzeiro | [1339324](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-703-1339324) | SHCES 703 | R$ 665.000 | Integração social, suíte e boa apresentação visual | Terceiro andar, elevador e garagem desconhecidos | -| 15 | Mangueiral | [1333569](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-6-1333569) | QC 6 | R$ 650.000 | Ampliação social em uso e QC alternativa prática | Conceito dos 90 m² e manutenção da cobertura | -| 16 | Cruzeiro | [1370889](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1370889) / [1375200](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1375200) | SHCES 207 | R$ 695.000 | Apartamento reformado com suíte | Duplicata, andar, elevador, vaga e área | -| 17 | Mangueiral | [1359480](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1359480) | QC 4 | R$ 535.000 | Referência de menor preço habitável | QC, planta, documentação e acabamento externo | -| 18 | Cruzeiro | [1372268](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-bloco-i-1372268) | SHCES 305 I | R$ 595.000 | Primeiro andar e duas vagas anunciadas | Provar vagas e orçar modernização | -| 19 | Mangueiral | [1356197](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1356197) | QC 4 | R$ 649.000 | Acabamento interno forte e casa vazia | Espaço coberto parece garagem; regularização | -| 20 | Cruzeiro | [1373131](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-1373131) | SHCES 305 | R$ 725.000 | Reforma tecnicamente bem descrita | Sem elevador, sem garagem e prêmio de preço | - -## Comparação regional - -| Dimensão | Cruzeiro Novo | Jardins Mangueiral | -|---|---|---| -| Faixa do top 10 | R$ 565 mil–R$ 725 mil | R$ 535 mil–R$ 650 mil | -| Mediana do top 10 | R$ 615 mil | R$ 610 mil | -| Espaço | 62–70 m²; conceito de área mais homogêneo | 68–90 m² informados; ampliações pouco comparáveis | -| Vagas | Frequentemente ausentes ou incertas | Uma ou duas na maioria dos finalistas | -| Acessibilidade | Depende fortemente de andar e elevador | Escada interna permanente em todos os sobrados padrão | -| Reforma | Elétrica, hidráulica e prédio antigos | Cobertura, drenagem, impermeabilização e ampliação | -| Mobilidade | Centralidade e menor dependência de carro | Maior dependência logística; proximidade de São Sebastião | -| Espaço social | Compacto | Quintal e ampliações mais flexíveis | -| Documentação crítica | Vaga, área, obras do bloco | Planta original e regularização das ampliações | - -## Sensibilidade à prioridade pessoal - -| Prioridade dominante | Região favorecida | Primeiro candidato | -|---|---|---| -| Centralidade e acesso ao Plano | Cruzeiro | 1345146 | -| Espaço, suíte, vagas e custo-benefício | Mangueiral | 1279892 | -| Menor preço habitável | Mangueiral | 1359480, com concessões | -| Apartamento próximo de R$ 600 mil | Cruzeiro | 1232283 | -| Menor risco de ampliação irregular | Cruzeiro | 1345146 ou 1357633 | -| Menor risco de sistemas prediais antigos | Mangueiral | 1346017 ou 1397378, sujeito a vistoria | -| Proximidade de São Sebastião e rede familiar | Mangueiral | 1279892 | - -## Imóveis imediatamente fora do corte - -- **Cruzeiro:** [1382466](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-101-bloco-c-1382466), [1290510](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-a-1290510) e [1371813](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-bloco-d-1371813). -- **Mangueiral:** [1368562](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-5-1368562). - -## Próxima ação recomendada - -As quatro visitas mais informativas continuam sendo: - -1. `1279892`, para testar a melhor hipótese de casa equilibrada; -2. `1345146`, para testar a melhor hipótese urbana com elevador; -3. `1350367`, para testar custo-benefício e ampliação no Mangueiral; -4. `1232283`, para testar apartamento maior, suíte e vagas anunciadas. - -Registrar após cada visita: tempo real de deslocamento, sensação de espaço, ruído, estacionamento, escadas, ventilação, manutenção esperada e vontade de permanecer por 10–20 anos. Essas quatro visitas devem definir qual região recebe a rodada seguinte. - -## Limitações - -- Preços são pedidos, não transações. -- A cobertura histórica é curta e de um único portal. -- A situação financeira do projeto precisa ser confirmada antes de proposta ou preço máximo. -- Fotos não validam estrutura, sistemas, ruído, conforto térmico ou documentação. -- A planta original das casas e vagas/elevadores dos apartamentos continuam pendentes. -- A escolha regional ainda depende de uma preferência não consolidada: centralidade versus casa, espaço e proximidade familiar. +| # | Região | Pos. regional | Anúncio | Local | Preço | Classificação e pendência decisiva | +|---:|---|---:|---|---|---:|---| +| 1 | Mangueiral | 1 | [1279892](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-rua-f-1279892) | QC 10 | R$ 595 mil | **Forte:** equilíbrio geral; confirmar planta, áreas e regularização | +| 2 | Mangueiral | 2 | [1319996](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1319996) | QC 14 | R$ 649 mil | **Forte, 4/5:** cozinha, gourmet, quintal e conservação; reverter closet se necessário | +| 3 | Cruzeiro | 1 | [1345146](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1207-bloco-a-1345146) | SHCES 1207 A | R$ 670 mil | **Forte:** melhor opção urbana; confirmar vaga, planta e sistemas | +| 4 | Mangueiral | 3 | [1377683](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1377683) | QC 11 | R$ 585 mil | **Aderência inferida:** cozinha funcional e gourmet aberto; usuário avaliar | +| 5 | Mangueiral | 4 | [1371718](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-rua-c-1371718) | QC 14 | R$ 650 mil | **Aderência inferida:** conservação, cozinha e gourmet; usuário avaliar | +| 6 | Cruzeiro | 2 | [1357415](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-1357415) | SHCES 1109 | R$ 630 mil | **Forte condicionado:** reformado e vazado; confirmar elevador | +| 7 | Mangueiral | 5 | [1140317](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-5-rua-l-1140317) | QC 5 | R$ 599 mil | **Aderência inferida:** cozinha equipada e gourmet aberto; usuário avaliar | +| 8 | Mangueiral | 6 | [1324014](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-12-rua-k-1324014) | QC 12 | R$ 649 mil | **Visitar, 3/5:** gourmet e conservação agradam; confirmar vaga e ventilação | +| 9 | Cruzeiro | 3 | [1357633](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-309-1357633) | SHCES 309 | R$ 600 mil | **Visitar:** reforma uniforme; elevador, vaga e sistemas pendentes | +| 10 | Mangueiral | 7 | [1290502](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1290502) | QC 11 | R$ 600 mil | **Aprofundar, 3/5:** reforma adequada; gourmet implementável | +| 11 | Cruzeiro | 4 | [1376958](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-b-1376958) | SHCES 911 B | R$ 599 mil | **Visitar:** reformado e nascente; sem elevador e vaga incerta | +| 12 | Mangueiral | 8 | [1352633](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-9-rua-g-1352633) | QC 9 | R$ 755 mil | **Aderência inferida:** conjunto visual forte; preço alto e usuário avaliar | +| 13 | Mangueiral | 9 | [1345745](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-quadra-qc-12-1345745) | QC 12 | R$ 670 mil | **Aderência inferida:** cozinha e gourmet amplos; testar calor | +| 14 | Cruzeiro | 5 | [1345903](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1303-1345903) | SHCES 1303 | R$ 565 mil | **Alternativa de valor:** primeiro andar, mas sem garagem | +| 15 | Mangueiral | 10 | [1333569](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-6-1333569) | QC 6 | R$ 650 mil | **Condicionado, 3/5:** testar calor do fechamento de vidro | +| 16 | Cruzeiro | 6 | [1339324](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-703-1339324) | SHCES 703 | R$ 665 mil | **Aprofundar:** boa integração; faltam andar, elevador e vaga | +| 17 | Mangueiral | 11 | [1357311](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-1-rua-a-1357311) | QC 1 | R$ 699,9 mil | **Condicionado:** gourmet bom, mas há possível dano de água no teto | +| 18 | Mangueiral | 12 | [1357912](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-3-1357912) | QC 3 | R$ 580 mil | **Condicionado, 3/5:** espaços úteis; intervenções pesam | +| 19 | Cruzeiro | 7 | [1370889](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1370889) / [1375200](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-207-1375200) | SHCES 207 | R$ 695 mil | **Aprofundar:** reformado; identificar anúncio principal e atributos | +| 20 | Cruzeiro | 8 | [1372268](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-bloco-i-1372268) | SHCES 305 I | R$ 595 mil | **Alternativa de valor:** provar duas vagas e orçar modernização | +| 21 | Mangueiral | 13 | [1259149](https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-11-1259149) | QC 11 | R$ 590 mil | **Aderência inferida fraca:** preço razoável; acabamento e área pendentes | +| 22 | Cruzeiro | 9 | [1373131](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-305-1373131) | SHCES 305 | R$ 725 mil | **Condicionado:** reforma forte; sem elevador/garagem e caro | +| 23 | Mangueiral | 14 | [348115](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-rua-c-348115) | QC 14 | R$ 550 mil | **Referência de valor:** anúncio antigo; conservação e área pendentes | +| 24 | Cruzeiro | 10 | [1381514](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-301-1381514) | SHCES 301 | R$ 690 mil | **Condicionado:** reforma forte; 4º andar, elevador/vaga e área divergente pendentes | +| 25 | Mangueiral | 15 | [1346118](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1346118) | QC 14 | R$ 550 mil | **Referência de valor:** base simples; modernização e área divergente | +| 26 | Cruzeiro | 11 | [1382466](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-101-bloco-c-1382466) | SHCES 101 C | R$ 650 mil | **Pendente:** boa reforma; galeria curta e andar/elevador ausentes | +| 27 | Cruzeiro | 12 | [1290510](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-911-bloco-a-1290510) | SHCES 911 A | R$ 640 mil | **Pendente:** suíte e reforma; 4º andar e taxas inválidas | +| 28 | Cruzeiro | 13 | [1371813](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-1109-bloco-d-1371813) | SHCES 1109 D | R$ 709 mil | **Pendente:** reforma moderna; evidência curta da planta | +| 29 | Cruzeiro | 14 | [1400223](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-609-bloco-d-1400223) | SHCES 609 D | R$ 495 mil | **Alternativa de obra:** 2º andar e boa luz; reforma ampla e vaga incerta | +| 30 | Cruzeiro | 15 | [1369327](https://www.dfimoveis.com.br/imovel/apartamento-3-quartos-venda-novo-cruzeiro-df-shces-quadra-203-bloco-g-1369327) | SHCES 203 G | R$ 490 mil | **Alternativa de obra:** vaga e desconto; 4º andar sem elevador | + +## Quase entraram + +As opções seguintes, todas com URL, estão nos relatórios regionais: [Cruzeiro Novo](../cruzeiro_top15/README.md#quase-entraram) e [Jardins Mangueiral](../mangueiral_top15/README.md#quase-entraram). + +## Próxima ação e decisão financeira + +No Mangueiral, começar a diligência por `1279892` e `1319996`; pedir planta, QC, licenças da ampliação, taxas e evidência de cobertura/drenagem antes da visita. No Cruzeiro, pedir andar, elevador, matrícula da vaga, atas e histórico de sistemas do prédio. As posições gerais entre tipologias permanecem provisórias até o usuário avaliar visualmente os apartamentos na mesma escala. + +O ranking físico não autoriza compra. Antes de negociar, calcular entrada, custos de aquisição, reforma/regularização, reserva remanescente, CET e custo mensal total. A posição deve cair se o imóvel exigir consumir a reserva ou depender de uma única circunstância afetiva/logística. ## Reprodutibilidade -- `results.csv`: ranking reconstruído. -- `parameters.json`: universo, método e fingerprint lógico do snapshot. -- `reports/cruzeiro_top10/`: fontes do ranking regional do Cruzeiro. -- `reports/mangueiral_top10/`: fontes do ranking regional do Mangueiral. +As extrações estão em `reports/cruzeiro_top15/analysis.py` e `reports/mangueiral_top15/analysis.py`. Os arquivos `results.csv` registram os 15 classificados regionais e `parameters.json` registra snapshot, contagens, hash do banco e critérios. A deduplicação e a inspeção fotográfica incluem julgamento manual documentado. diff --git a/reports/cruzeiro_vs_mangueiral/parameters.json b/reports/cruzeiro_vs_mangueiral/parameters.json index c9bc432..806f006 100644 --- a/reports/cruzeiro_vs_mangueiral/parameters.json +++ b/reports/cruzeiro_vs_mangueiral/parameters.json @@ -1,31 +1,33 @@ { - "analysis_date": "2026-07-17", - "listing_data_reference_date": "2026-07-17", + "analysis_date": "2026-07-21", + "listing_data_reference_date": "2026-07-21", "database_path": "dfimoveis_data/dfimoveis.sqlite3", - "database_main_file_sha256": "046e0a2fef3eb3c2e5ebdf07d30bd04a31db0bb1ea315ccc536c6bf8ba96577e", - "logical_snapshot_sha256": "fe604aab8af7f67c833e6de2a0fc81c7dc2950ca440ad7b23ebd03d0c2546188", + "database_main_file_sha256": "bfbbdd09e88f76afd917ebd981b848b9321aeb5fc0602f712645d33e1a5ea76c", "database_mode": "read_only_transaction_with_wal", "source_reports": [ - "reports/cruzeiro_top10/README.md", - "reports/mangueiral_top10/README.md" + "reports/cruzeiro_top15/README.md", + "reports/mangueiral_top15/README.md" ], - "cruzeiro_listing_count": 65, - "mangueiral_listing_count": 94, - "mangueiral_active_listing_count": 84, - "mangueiral_inactive_listing_count": 10, - "total_listing_count": 159, - "database_snapshot_count": 295, - "database_photo_count": 6272, - "database_crawl_run_count": 16, - "cruzeiro_photo_files_reviewed_distinct": 1289, - "mangueiral_photo_files_reviewed_distinct_cumulative": 1907, - "total_photo_files_reviewed_distinct_cumulative": 3196, - "regional_finalists_compared": 20, - "ranking_merge_method": "preserve_regional_order_then_interleave_by_cross_typology_visit_priority", - "report_rebuilt_from_regional_results": true, + "total_listing_count": 161, + "database_snapshot_count": 562, + "database_photo_count": 7363, + "database_crawl_run_count": 20, + "cruzeiro_observed_listing_count": 67, + "cruzeiro_active_listing_count": 63, + "mangueiral_observed_listing_count": 94, + "mangueiral_active_listing_count": 78, + "regional_ranking_size": 15, + "regional_finalists_compared": 30, "ranking_unit": "probable_physical_property", + "ranking_merge_method": "contextual_cross_typology_priority_with_mangueiral_as_primary_region_and_regional_order_preserved", "numeric_score_used": false, + "mangueiral_user_feedback_date": "2026-07-21", + "mangueiral_user_visual_functional_scores_used": true, + "mangueiral_full_active_universe_reprocessed": true, "cross_typology_price_per_m2_used": false, - "first_photo_sale_marker_audit_count": 20, - "visually_unavailable_listing_ids": ["1346231", "1349519", "1351729", "1366801"] + "mangueiral_first_photo_active_universe_audit_count": 78, + "cruzeiro_first_photo_finalist_and_quarantine_audit_count": 16, + "visually_unavailable_listing_ids": ["1263868", "1301400", "1346017", "1346231", "1349519", "1351729", "1352943", "1356197", "1362968", "1366801"], + "source_mismatch_listing_ids": ["1369473"], + "context_documents_reviewed": ["docs/CURRENT_STATE.md", "docs/PROJECT_CONTEXT.md", "docs/LOCATION_CRITERIA.md", "docs/PROPERTY_CRITERIA.md", "docs/FINANCIAL_STRATEGY.md", "docs/SCRAPER_OPERATIONS.md", "docs/DATA_MODEL.md", "docs/ANALYSIS_RULES.md"] } diff --git a/reports/cruzeiro_vs_mangueiral/results.csv b/reports/cruzeiro_vs_mangueiral/results.csv index 123fa30..6661e23 100644 --- a/reports/cruzeiro_vs_mangueiral/results.csv +++ b/reports/cruzeiro_vs_mangueiral/results.csv @@ -1,21 +1,31 @@ -rank,region,representative_listing_id,probable_duplicate_ids,location,price_brl,area_m2,bedrooms,suites,parking_spaces,key_condition,decision -1,mangueiral,1279892,,QC 10,595000,68,3,1,1,planta original e ampliações pendentes de documentos,candidato forte -2,cruzeiro,1345146,,SHCES 1207 bloco A,670000,65,3,1,,elevador anunciado e vaga pendente,candidato forte -3,mangueiral,1350367,,QC 6,569000,68,3,1,2,área ampliação e conforto térmico pendentes,candidato forte -4,cruzeiro,1232283,,SHCES 309 bloco B,600000,70,3,1,2,elevador e natureza das vagas pendentes,candidato forte condicionado -5,mangueiral,1346017,,QC 10,577000,68,3,1,2,taxas orientação e cobertura pendentes,candidato forte -6,mangueiral,1397378,,QC 8,600000,68,3,1,2,planta manutenção e cobertura pendentes,candidato forte -7,cruzeiro,1357415,,SHCES 1109,630000,67.2,3,1,,quarto andar e elevador desconhecido,confirmar elevador antes da visita -8,mangueiral,1359960,,QC 7,610000,68,3,1,2,conforto ambiental do quintal fechado pendente,visitar -9,cruzeiro,1357633,,SHCES 309,600000,64,3,0,,elevador garagem e condomínio pendentes,visitar -10,cruzeiro,1376958,,SHCES 911 bloco B,599000,62,3,1,,sem elevador e garagem não informada,visitar -11,mangueiral,1319996,,QC 14,649000,68,3,1,2,localização cobertura e placas solares pendentes,visitar -12,cruzeiro,1345903,,SHCES 1303,565000,64.5,3,1,0,primeiro andar e somente estacionamento público,alternativa de valor -13,mangueiral,1324014,,QC 12,649000,68,3,1,,vaga conforto da área gourmet e regularização pendentes,visitar -14,cruzeiro,1339324,,SHCES 703,665000,64,3,1,,terceiro andar elevador e garagem desconhecidos,esclarecer acessibilidade -15,mangueiral,1333569,,QC 6,650000,90,3,1,2,conceito de área e cobertura pendentes,visitar após os anteriores -16,cruzeiro,1370889,1375200,SHCES 207,695000,62,3,1,,duplicata provável elevador e garagem desconhecidos,confirmar anúncio primário -17,mangueiral,1359480,,QC 4,535000,70,3,1,2,planta documentação e acabamento externo pendentes,referência de valor -18,cruzeiro,1372268,,SHCES 305 bloco I,595000,62.61,3,0,2,vagas e custo de modernização pendentes,alternativa de valor -19,mangueiral,1356197,,QC 4,649000,68,3,1,2,uso do espaço coberto e regularização pendentes,visitar após os anteriores -20,cruzeiro,1373131,,SHCES 305,725000,65,3,0,,sem elevador e garagem não informada,visitar apenas se reforma justificar prêmio +rank,region,regional_rank,representative_listing_id,probable_duplicate_ids,location,price_brl,decision +1,mangueiral,1,1279892,,QC 10,595000,strong_candidate +2,mangueiral,2,1319996,,QC 14,649000,strong_candidate +3,cruzeiro,1,1345146,,SHCES 1207 A,670000,strong_candidate +4,mangueiral,3,1377683,,QC 11,585000,user_review_then_documents +5,mangueiral,4,1371718,,QC 14,650000,user_review_then_documents +6,cruzeiro,2,1357415,,SHCES 1109,630000,strong_candidate_conditioned +7,mangueiral,5,1140317,,QC 5,599000,user_review_then_documents +8,mangueiral,6,1324014,,QC 12,649000,visit +9,cruzeiro,3,1357633,,SHCES 309,600000,visit +10,mangueiral,7,1290502,,QC 11,600000,investigate +11,cruzeiro,4,1376958,,SHCES 911 B,599000,visit +12,mangueiral,8,1352633,,QC 9,755000,user_review_and_price_test +13,mangueiral,9,1345745,,QC 12,670000,user_review_and_heat_test +14,cruzeiro,5,1345903,,SHCES 1303,565000,value_alternative +15,mangueiral,10,1333569,,QC 6,650000,conditioned_heat_test +16,cruzeiro,6,1339324,,SHCES 703,665000,investigate +17,mangueiral,11,1357311,,QC 1,699900,inspect_possible_water_damage +18,mangueiral,12,1357912,,QC 3,580000,pending_intervention_cost +19,cruzeiro,7,1370889,1375200,SHCES 207,695000,investigate +20,cruzeiro,8,1372268,,SHCES 305 I,595000,value_alternative +21,mangueiral,13,1259149,,QC 11,590000,user_review_and_area_validation +22,cruzeiro,9,1373131,,SHCES 305,725000,conditioned +23,mangueiral,14,348115,,QC 14,550000,user_review_and_condition_validation +24,cruzeiro,10,1381514,,SHCES 301,690000,conditioned +25,mangueiral,15,1346118,,QC 14,550000,user_review_and_renovation_scope +26,cruzeiro,11,1382466,,SHCES 101 C,650000,pending_evidence +27,cruzeiro,12,1290510,,SHCES 911 A,640000,pending_cost_validation +28,cruzeiro,13,1371813,,SHCES 1109 D,709000,pending_evidence +29,cruzeiro,14,1400223,,SHCES 609 D,495000,renovation_alternative +30,cruzeiro,15,1369327,,SHCES 203 G,490000,renovation_alternative diff --git a/reports/database_assessment.md b/reports/database_assessment.md index 4039556..0b9758a 100644 --- a/reports/database_assessment.md +++ b/reports/database_assessment.md @@ -1,303 +1,169 @@ -# Avaliação inicial do banco de dados +# Avaliação do banco de dados -**Data da avaliação:** 13 de julho de 2026 -**Banco:** `dfimoveis_data/dfimoveis.sqlite3` -**Escopo:** diagnóstico estrutural e de qualidade anterior a qualquer análise dos imóveis +**Data da avaliação:** 21 de julho de 2026 -## 1. Pergunta respondida +**Banco:** `dfimoveis_data/dfimoveis.sqlite3` -O banco já representa de forma segura anúncios, snapshots históricos e imóveis físicos, com cobertura e qualidade suficientes para iniciar análises imobiliárias? +**Modo:** `mode=ro`, `PRAGMA query_only = ON` +**SHA-256 do arquivo principal:** `bfbbdd09e88f76afd917ebd981b848b9321aeb5fc0602f712645d33e1a5ea76c` -**Resposta curta:** ele representa anúncios e estados de conteúdo, mas ainda não representa imóveis físicos deduplicados. A coleta cobre apenas um dia e contém exatamente um snapshot por anúncio. Preços, áreas e quartos podem ser usados somente após validações específicas; localização e dados de anunciante estão fortemente contaminados e não devem ser usados como estão. Portanto, o banco ainda não sustenta tendência histórica, tempo de mercado, preço por m² ou comparáveis por localização. +## 1. Pergunta -## 2. Segurança e método +O snapshot atualizado sustenta a revisão dos rankings e já permite conclusões de histórico, disponibilidade e liquidez? -A análise foi feita exclusivamente com consultas agregadas ou amostras pequenas. O banco foi aberto com `sqlite3 -readonly` ou URI `mode=ro&immutable=1`, sempre com `PRAGMA query_only = ON`. Nenhum scraper foi executado e nenhuma instrução SQL de escrita foi enviada ao banco. +**Resposta curta:** sustenta a revisão operacional dos rankings, mas ainda não sustenta inferência de liquidez ou preço de transação. O banco contém quatro dias efetivos de observação entre 13 e 21/07/2026, e a maior parte dos novos snapshots é causada por campos textuais contaminados, não por mudança imobiliária real. -Identificação do arquivo analisado: +## 2. Integridade e esquema -- tamanho: 5.017.600 bytes; -- modificação: `2026-07-13 14:20:34.601285975 -03:00`; -- SHA-256 inicial: `1ce3128b488c50c993d7328ac6e24b5905f083a7e04e3f636407eea1edd02b9d`; -- sem arquivos `-wal` ou `-shm` e sem processo com o arquivo aberto na inspeção inicial; +- tamanho do arquivo principal: 9.478.144 bytes; - `PRAGMA quick_check`: `ok`; -- `PRAGMA foreign_key_check`: nenhuma violação. +- `PRAGMA foreign_key_check`: nenhuma violação; +- codificação: UTF-8; +- `journal_mode`: WAL; +- 2.314 páginas de 4.096 bytes; +- cinco tabelas da aplicação e `sqlite_sequence`; +- quatro autoíndices; nenhuma view ou trigger; +- esquema físico sem mudança material em relação a `docs/ACTUAL_SCHEMA.md`. -Na validação final, o arquivo principal continuava com o mesmo tamanho, `mtime` e SHA-256. Como seu modo persistido é WAL, o cliente SQLite havia criado dois sidecars de runtime: `dfimoveis.sqlite3-shm`, com 32.768 bytes, e `dfimoveis.sqlite3-wal`, vazio. Eles não foram removidos, conforme a regra explícita do projeto. +O banco foi consultado em transação somente leitura. Não foi usado `immutable=1`, pois este é o arquivo atualizado pelo scraper. -O esquema completo e as decisões de interpretação estão em `docs/ACTUAL_SCHEMA.md`. +## 3. Universo e cobertura -## 3. Universo, período e cobertura - -Não houve filtro ou exclusão de registros para este diagnóstico. O universo físico contém: - -| Unidade | Quantidade | +| Unidade | Total | |---|---:| -| Execuções do coletor | 4 | -| Anúncios únicos por `listing_id` | 156 | -| URLs de anúncio únicas | 156 | -| Snapshots de conteúdo | 156 | -| Vínculos anúncio–busca | 156 | -| Fotos registradas | 5.386 | -| Anúncios com pelo menos uma foto | 156 | - -### Cobertura temporal - -Todas as datas técnicas válidas estão em ISO 8601. A coleta cobre somente **13/07/2026**: - -| Evento | UTC | Horário de Brasília (UTC−3) | -|---|---|---| -| Primeira execução iniciada | 12:20:58 | 09:20:58 | -| Primeira observação de anúncio | 12:56:32 | 09:56:32 | -| Última observação de anúncio | 13:25:51 | 10:25:51 | -| Última execução concluída | 17:20:34 | 14:20:34 | - -As duas primeiras execuções terminaram com `status = ok`, mas registraram zero páginas, URLs e detalhes. As duas execuções com coleta efetiva somaram: - -- 9 páginas de busca; -- 156 URLs encontradas; -- 156 detalhes processados com sucesso e zero falhas; -- 5.383 fotos baixadas e 3 falhas; -- zero anúncios marcados como inativos. - -As buscas cobertas foram: - -1. `https://www.dfimoveis.com.br/venda/df/cruzeiro/novo/apartamento/3-quartos` — 65 anúncios; -2. `https://www.dfimoveis.com.br/venda/df/brasilia/jardins-mangueiral/casa/3,4-quartos` — 91 anúncios. - -O universo é, portanto, intencionalmente limitado a um portal e a duas consultas. A busca do Jardins Mangueiral aceita três **ou quatro** quartos: 89 casas foram extraídas com três quartos anunciados e 2 com quatro. Mesmo os 89 registros de três quartos não comprovam a planta original exigida pelo projeto. - -## 4. Como as entidades são representadas - -### Anúncio - -`listings` guarda uma linha por `listing_id` do DF Imóveis, com URL única e o estado corrente. Os 156 IDs, URLs e hashes correntes são distintos. Essa é uma identidade de publicação no portal, não de unidade residencial. - -### Snapshot ou observação histórica - -`listing_history` guarda uma linha por par único `(listing_id, content_hash)`. Isso é um histórico de **mudanças de conteúdo**: uma nova observação idêntica não cria novo snapshot. `listings.first_seen_at` e `last_seen_at` condensam a janela observada. - -No snapshot atual: - -- todos os 156 anúncios têm exatamente um registro histórico; -- o hash histórico é igual ao hash corrente em todos os casos; -- `captured_at = first_seen_at` em todos os casos; -- `first_seen_at = last_seen_at` em todos os casos. - -Assim, não há série histórica real ainda. Não é possível medir mudança de preço, republicação, duração, saída ou retorno do estoque. - -### Execução do scraper - -`crawl_runs` registra horários, estado e contadores JSON, mas não existe `run_id` em `listings`, `listing_history` ou `listing_searches`. A execução geradora só pode ser inferida por horário. Isso limita auditoria, cobertura por execução e diagnóstico de ausências. +| Execuções registradas | 20 | +| Execuções `ok` | 12 | +| Execuções `failed` | 8 | +| Anúncios acumulados | 161 | +| Observações históricas | 562 | +| Vínculos anúncio–busca | 161 | +| Fotos registradas | 7.363 | +| Hashes de foto distintos | 3.455 | +| Anúncios inativos | 20 | -### Imóvel físico +Cobertura técnica: 13–21/07/2026. Há observações de anúncios em quatro datas, janela insuficiente para sazonalidade ou liquidez. -Não existe tabela ou chave para imóvel físico, unidade, endereço normalizado ou relação de duplicidade. Anúncios de corretores diferentes permanecem independentes. A base também não contém hashes perceptuais; possui apenas SHA-256 exato das imagens. +| Região | Acumulados | Ativos | Inativos | +|---|---:|---:|---:| +| Jardins Mangueiral | 94 | 78 | 16 | +| Cruzeiro Novo | 67 | 63 | 4 | -Logo, as unidades válidas hoje são: +A execução 20, em 21/07, processou oito páginas, encontrou 141 URLs, concluiu 141 detalhes sem falha, baixou 572 fotos novas, reutilizou 4.404 e marcou 20 anúncios como inativos. Ausência da busca não comprova venda. -- anúncio único: suportado por `listing_id`; -- estado alterado do anúncio: suportado por `listing_history`; -- imóvel físico provável/confirmado: **não suportado sem derivação e revisão**. +## 4. O que os dados representam -## 5. Qualidade dos dados +- `listings`: identidade de anúncio e estado corrente; +- `listing_history`: estados distintos por `content_hash`, não cada observação; +- `listing_searches`: vínculo entre anúncio e busca; +- `photos`: mídia associada ao anúncio, sem vínculo a um snapshot específico; +- `crawl_runs`: execução técnica sem chave direta para os anúncios observados; +- imóvel físico provável: não existe como entidade persistida e depende de deduplicação derivada. -### 5.1 Integridade estrutural +Há 21 anúncios com um snapshot, 6 com dois, 7 com três e 127 com quatro. Essa distribuição não deve ser interpretada como número de mudanças reais. -Pontos positivos: +## 5. Auditoria das mudanças históricas -- arquivo íntegro segundo `quick_check`; -- nenhuma FK órfã em histórico, buscas ou fotos; -- todos os JSONs são sintaticamente válidos; -- todos os IDs de anúncio têm 6–7 caracteres numéricos; -- todas as URLs pertencem ao domínio e padrão esperados; -- todos os 156 HTMLs brutos referenciados existem; -- todos os hashes de conteúdo têm 64 caracteres; -- não há URL, hash corrente ou caminho de HTML repetido. +Foram comparadas as transições consecutivas de `data_json`: -Limitações estruturais: - -- nenhuma relação entre execução e item; -- fonte implícita, sem `source_id`; -- área armazenada em um único conceito; -- mídia ligada ao anúncio, não ao snapshot; -- ausência de entidade de imóvel físico e de localização normalizada; -- apenas autoíndices; não há índices por datas ou atributos analíticos. - -### 5.2 Completude dos principais campos - -| Campo | Ausentes | Percentual | -|---|---:|---:| -| preço | 0 | 0,0% | -| área | 0 | 0,0% | -| quartos | 0 | 0,0% | -| condomínio | 59 | 37,8% | -| IPTU | 144 | 92,3% | -| suítes | 41 | 26,3% | -| vagas | 57 | 36,5% | -| cidade | 156 | 100,0% | -| UF | 156 | 100,0% | -| nome do anunciante | 156 | 100,0% | -| latitude/longitude | 156 / 156 | 100,0% / 100,0% | -| data de publicação | 156 | 100,0% | -| data de inativação | 156 | 100,0% | - -Esses percentuais medem apenas nulos. Para campos textuais, a taxa de preenchimento é enganosa porque o parser capturou conteúdo da página em vez do valor pretendido. - -### 5.3 Contaminação de texto - -Os problemas mais graves são: - -- 146 endereços (93,6%) têm mais de 300 caracteres; -- 154 bairros (98,7%) têm mais de 100 caracteres; -- os dois valores curtos de bairro são `.` e um trecho de descrição, também inválidos; -- 98 códigos de anunciante (62,8%) têm mais de 100 caracteres; -- 153 valores de CRECI (98,1%) têm mais de 100 caracteres. - -Os textos longos incluem navegação, formulários, política de privacidade e outros fragmentos da página. Eles podem conter dados de contato exibidos pelo portal; por isso, não foram reproduzidos neste relatório. Esses campos devem ser considerados **indisponíveis**, não apenas ruidosos. - -Os títulos têm somente quatro valores distintos e funcionam como títulos genéricos por tipologia. As 156 descrições são distintas, mas têm apenas 156–161 caracteres, compatíveis com resumos/metadados truncados, não com a descrição completa. `extra_json` contém um array `json_ld` vazio nos 156 registros e não resolve a reextração. - -### 5.4 Valores estruturados e outliers - -| Tipo anunciado | N | Preço mínimo | Preço máximo | Área mínima | Área máxima | Quartos | -|---|---:|---:|---:|---:|---:|---:| -| Apartamento | 65 | R$ 480.000 | R$ 2.030.598 | 61 m² | 113 m² | 3 | -| Casa | 91 | R$ 450.000 | R$ 570.000.000 | 0,11 m² | 131,46 m² | 3–4 | - -Não há valores nulos ou não positivos em preço e área, mas isso não significa validade: +| Campo alterado | Transições | +|---|---:| +| `content_hash` | 401 | +| `creci` | 398 | +| `advertiser_code` | 66 | +| `neighborhood` | 25 | +| `description` | 11 | +| `price_brl` | 3 | +| `address` | 3 | -- anúncio `1351663`: R$ 570.000.000, forte indício de erro de escala ou parsing; -- anúncio `1370918`: 0,11 m², área fisicamente implausível. +Em 305 transições, somente `creci` e o hash mudaram. Isso indica instabilidade do parser ou conteúdo variável da página. Não há base para tratar 401 transições como mudanças de mercado. -Não foi aplicado corte nem correção silenciosa. Também não se calculou preço por m², pois `area_m2` não distingue área útil, privativa, construída, total ou terreno. +Mudanças reais de preço identificadas: -### 5.5 Fotos e arquivos brutos +- `1173275`: R$ 550.000 para R$ 549.000 em 21/07; +- `1348535`: R$ 950.000 para R$ 880.000 em 20/07, mantido em 21/07; +- `1372539`: R$ 572.500 para R$ 569.000 em 17/07, mantido depois. -Das 5.386 linhas em `photos`: +Esses valores são preços anunciados, não negociados. -- 5.383 têm arquivo local, e o tamanho em disco coincide com `bytes`; -- 3 não têm hash, MIME, tamanho ou caminho, correspondendo às três falhas registradas pelo coletor; -- há 4.514 WebP, 558 JPEG, 311 PNG e 3 sem MIME; -- os anúncios possuem de 5 a 61 fotos; -- não há caminho local nem ordinal duplicado dentro de anúncio. +## 6. Qualidade dos campos -Há 3.307 hashes distintos. Cento e quatorze hashes aparecem em mais de um anúncio, e um deles aparece nos 156 anúncios. Isso revela logos, selos, placeholders ou outros ativos comuns. Hash idêntico pode ser sinal de anúncio duplicado somente depois de excluir ativos genéricos e combinar outros sinais. +Nos 161 anúncios: -## 6. Duplicidades e imóveis físicos prováveis +- preço, área e quartos estão preenchidos em todos; +- cidade, UF, latitude e longitude estão ausentes em todos; +- 150 endereços têm mais de 300 caracteres; +- 159 bairros têm mais de 100 caracteres; +- permanece um preço de R$ 570 milhões (`1351663`); +- permanece uma área de 0,11 m² (`1370918`); +- nenhum registro de foto está sem hash ou caminho local no snapshot atual. +- a triagem perceptual com Pillow 12.3.0 foi reexecutada nos dois recortes; recuperou os grupos já documentados e não identificou nova duplicação suficientemente sustentada entre os 30 finalistas. -Não há duplicidade técnica imediata por `listing_id`, URL ou `content_hash`. Uma assinatura exploratória composta por bairro, tipo, preço, área, quartos e vagas encontrou 13 grupos, equivalentes a 18 linhas excedentes. Esse resultado **não é evidência de 18 imóveis duplicados**, porque: +O preenchimento não garante validade. Localização e anunciante continuam contaminados. Área não distingue privativa, útil, construída, total, terreno ou ampliação. -- o bairro está contaminado; -- preço, área e quartos podem coincidir entre unidades distintas; -- fotos compartilhadas incluem ativos genéricos; -- não há endereço/unidade confiável nem telefone normalizado; -- SHA-256 só detecta arquivos idênticos, não versões redimensionadas da mesma foto. +## 7. Disponibilidade e quarentena visual -Consequentemente, o método de deduplicação desta avaliação foi apenas técnico: unicidade de anúncio por `listing_id`. Nenhuma fusão de anúncios nem contagem de imóveis físicos foi produzida. +Vinte anúncios estão inativos no banco, mas retirada não prova venda. Além disso, dez anúncios ainda ativos exibem indicação explícita na primeira foto: -Uma futura camada derivada deve combinar, com confiança e revisão manual: +- Cruzeiro: `1263868`; +- Mangueiral: `1301400`, `1346017`, `1346231`, `1349519`, `1351729`, `1352943`, `1356197`, `1362968` e `1366801`. -- endereço/QC/quadra/bloco/unidade reextraídos; -- imagens após remoção de ativos comuns, idealmente com hash perceptual; -- preço, área, planta, quartos e vagas validados; -- anunciante e descrição normalizados; -- estados `unreviewed`, `probable`, `confirmed`, `rejected` e `ambiguous`. +Esses registros ficam em quarentena histórica como “vendido visualmente indicado”. O selo é evidência operacional, não prova registral. -## 7. Possíveis quebras de coleta +## 8. Atualização dos rankings -1. **Localização:** `address` e `neighborhood` capturaram grandes trechos da página; cidade, UF e coordenadas ficaram vazias. -2. **Anunciante:** `advertiser_code` e `creci` frequentemente capturaram texto extenso; `advertiser_name` ficou vazio. -3. **Dados estruturados:** o `json_ld` foi preservado como array vazio em todos os anúncios. -4. **Escala numérica:** pelo menos um preço e uma área são claramente suspeitos. -5. **Fotos:** três falhas foram registradas de modo consistente, sem arquivo local. -6. **Execuções vazias:** duas execuções `ok` não coletaram páginas. Sem motivo/configuração persistidos, não é possível diferenciar teste, filtro vazio ou falha silenciosa. -7. **Filtro do Mangueiral:** a URL coletada inclui casas de quatro quartos e não identifica planta original. +O Mangueiral não recebeu anúncio novo em 21/07. `1356197`, antes em 9º, passou a exibir **VENDIDO** na primeira foto. Depois do feedback pessoal, o funil foi refeito sobre os 78 ativos; `1377683`, `1371718` e `1140317` entraram no Top 5 por aderência inferida. A auditoria integral também identificou `1301400` com selo de vendido na primeira foto e `1369473` como solicitação de compra, não oferta. -## 8. Limitações para qualquer conclusão imobiliária +O Cruzeiro recebeu: -- Um único dia não permite análise histórica ou tendência. -- A amostra cobre apenas DF Imóveis e duas buscas específicas. -- Preço anunciado não é preço negociado. -- Não há transações, status comprovado de venda ou evidência de fechamento. -- Não há imóveis físicos deduplicados. -- Não há localização analítica confiável por QC, quadra ou bloco. -- Não há conceito comparável de área. -- O estado de reforma, planta original, elevador, garagem, andar e qualidade funcional não está modelado de forma suficiente. -- Para Jardins Mangueiral, `bedrooms = 3` não valida planta original de três quartos. +- `1381514`: SHCES 301, R$ 690 mil, 4º andar, reforma aparente forte, sem confirmação de elevador ou vaga e com divergência entre 63 e 93 m²; +- `1400223`: SHCES 609 D, R$ 495 mil, 2º andar, boa luz e necessidade de reforma ampla. -Portanto, ainda não é apropriado produzir medianas regionais, comparáveis, preço por m², tempo de anúncio, liquidez ou recomendações de visita com base apenas neste banco. +Os dois entraram no Top 15 condicionado. `1375199` saiu por inatividade; `1370891` passou para “quase entrou” pela nova evidência, não por piora comprovada. -## 9. Próximas ações recomendadas +## 9. Limitações -Antes da análise dos imóveis: +- um único portal e duas buscas; +- janela histórica curta; +- 20 inativos sem causa confirmada; +- snapshots inflados por campos textuais instáveis; +- ausência de entidade de imóvel físico; +- localização estruturada não confiável; +- área semanticamente inconsistente; +- fotos podem conter material genérico e republicações; +- planta original, sistemas, ruído e documentação exigem validação externa. -1. corrigir e testar a extração de endereço, bairro, anunciante e CRECI usando os HTMLs já salvos, sem recrawling; -2. criar uma derivação reproduzível fora do banco original e preservar os valores brutos; -3. separar áreas privativa, útil, construída, total e de terreno, deixando nulo o conceito não comprovado; -4. criar regras explícitas de validação para preço e área, mantendo os outliers identificados em quarentena; -5. filtrar o Mangueiral por planta original de três quartos, não apenas pelo número anunciado; -6. coletar múltiplas datas e auditar a cobertura por busca antes de estimar permanência, entradas ou saídas; -7. numa evolução aprovada do coletor, adicionar `source_id`, `run_id` nos snapshots, referência ao HTML bruto histórico e versão/configuração da coleta; -8. construir `property_candidates` apenas em uma camada derivada, com confiança e revisão, sem fundir registros no banco original. +Não calcular preço por m² amplo, liquidez ou tendência com este snapshot. -## 10. Consultas de reprodução +## 10. Reprodução -Todas devem ser executadas com `sqlite3 -readonly dfimoveis_data/dfimoveis.sqlite3` ou conexão Python URI em `mode=ro` e `PRAGMA query_only = ON`. +Consultas essenciais, sempre em leitura: ```sql --- Objetos do esquema +PRAGMA query_only = ON; +PRAGMA quick_check; +PRAGMA foreign_key_check; + SELECT type, name, tbl_name, sql FROM sqlite_master -ORDER BY type, name; +ORDER BY type, name +LIMIT 50; --- Contagens (executar uma tabela por vez após validar os nomes) -SELECT COUNT(*) FROM crawl_runs; SELECT COUNT(*) FROM listings; SELECT COUNT(*) FROM listing_history; SELECT COUNT(*) FROM listing_searches; SELECT COUNT(*) FROM photos; +SELECT COUNT(*) FROM crawl_runs; --- Cobertura -SELECT MIN(started_at), MAX(finished_at), COUNT(*) -FROM crawl_runs; - -SELECT MIN(first_seen_at), MAX(last_seen_at), - COUNT(DISTINCT date(first_seen_at)) -FROM listings; - --- Número de snapshots por anúncio -SELECT snapshots, COUNT(*) AS listings -FROM ( - SELECT listing_id, COUNT(*) AS snapshots - FROM listing_history - GROUP BY listing_id -) -GROUP BY snapshots; - --- Completude de campos críticos -SELECT - COUNT(*) AS total, - SUM(price_brl IS NULL) AS missing_price, - SUM(area_m2 IS NULL) AS missing_area, - SUM(condominium_brl IS NULL) AS missing_condominium, - SUM(iptu_brl IS NULL) AS missing_iptu, - SUM(latitude IS NULL OR longitude IS NULL) AS missing_coordinates -FROM listings; - --- Contaminação textual sem imprimir os textos -SELECT - SUM(length(address) > 300) AS long_address, - SUM(length(neighborhood) > 100) AS long_neighborhood, - SUM(length(advertiser_code) > 100) AS long_advertiser_code, - SUM(length(creci) > 100) AS long_creci -FROM listings; - --- Integridade referencial e física -PRAGMA quick_check; -PRAGMA foreign_key_check; +SELECT s.search_url, + COUNT(*) AS accumulated, + SUM(l.inactive_at IS NULL) AS active, + SUM(l.inactive_at IS NOT NULL) AS inactive +FROM listing_searches AS s +JOIN listings AS l USING (listing_id) +GROUP BY s.search_url +LIMIT 10; ``` -## 11. Implicação para a decisão de compra +## 11. Implicação prática -Esta avaliação reduz o risco de transformar uma coleta tecnicamente bem-sucedida em falsa precisão de mercado. O próximo ganho real não virá de comparar preços imediatamente, mas de corrigir localização e semântica de área, acumular histórico e construir uma deduplicação reversível. Até lá, os registros são úteis como inventário bruto de anúncios e arquivos, não como avaliação consolidada de imóveis físicos. +O snapshot melhora a triagem operacional, sobretudo ao revelar inativos e dois apartamentos novos. Ele ainda não justifica pressa nem conclusões de mercado. O ganho imediato vem de manter quarentenas visuais, corrigir a geração excessiva de snapshots e validar documentalmente os candidatos antes de visita ou negociação. diff --git a/reports/mangueiral_top10/README.md b/reports/mangueiral_top10/README.md deleted file mode 100644 index 06bd464..0000000 --- a/reports/mangueiral_top10/README.md +++ /dev/null @@ -1,184 +0,0 @@ -# Ranking preliminar — casas no Jardins Mangueiral - -**Data de referência do snapshot:** 17/07/2026 -**Relatório revisado:** 17/07/2026 -**Unidade principal:** imóvel físico provável, não snapshot nem anúncio isolado -**Finalidade:** priorizar visitas; não estimar preço negociado nem substituir vistoria ou validação documental - -## Pergunta - -Quais são as dez casas do Jardins Mangueiral que mais merecem atenção segundo as preferências e regras do projeto, dando às fotografias importância comparável à dos dados estruturados? - -## Universo e método - -Foram avaliados os 94 anúncios acumulados encontrados pela busca de casas no Jardins Mangueiral: 84 estavam ativos e 10 inativos no snapshot consistente de 17/07/2026. A base cobre um único portal. Há 159 anúncios nas duas regiões, 295 snapshots de conteúdo e 6.272 mídias; 133 anúncios têm dois snapshots, mas quatro mudanças foram materialmente relevantes nos campos imobiliários e apenas uma delas foi preço. - -Como o banco estava em WAL, ele foi aberto com `mode=ro`, sem `immutable=1`, dentro de uma transação de leitura consistente e com `PRAGMA query_only = ON`. Entre os anúncios ativos, 80 informam três quartos e preço plausível entre R$ 450 mil e R$ 1,15 milhão. O anúncio de R$ 1,15 milhão descreve outra implantação, no condomínio American Garden, e não a casa-padrão de 68 m² das QCs; não é comparável ao universo prioritário. Esses números descrevem anúncios, não imóveis únicos nem preços negociados. - -Não foi calculado preço por metro quadrado: os campos e descrições misturam área privativa, construída, total e de terreno. Tampouco foi criada pontuação numérica, porque os pesos não foram aprovados. A ordem é uma **prioridade de visita ajustada por risco**, considerando em conjunto: - -1. compatibilidade provável com a planta original de três quartos; -2. funcionalidade, circulação, armazenamento e flexibilidade de longo prazo; -3. qualidade real da conversão do quintal, sem penalizar fechamento automaticamente; -4. luz e ventilação aparentes, conservação e coerência dos acabamentos; -5. risco e transtorno de adequação; -6. preço, condomínio, IPTU, vagas e área anunciados; -7. QCs citadas nos relatos de moradores, apenas como evidência anedótica; -8. completude, coerência e confiabilidade das fotos. - -### Filtro da planta original - -Os dez imóveis abaixo têm fachada, posição de escada, distribuição e dormitórios fotografados compatíveis com a tipologia padrão de três quartos, e nenhum deles é descrito como conversão de dois para três quartos. Isso sustenta apenas a classificação **planta original provável**. A confirmação exige planta, matrícula ou documentação da unidade. O anúncio 1374012, por exemplo, foi retirado da disputa apesar do preço baixo porque a própria descrição informa “3 quartos sendo 1 adaptado”. - -### Revisão fotográfica - -Em 17/07/2026, a primeira foto de todos os escolhidos foi auditada especificamente para marcas de “vendido” ou “negócio fechado”. Quatro anúncios que chegaram a integrar versões do ranking foram removidos por esse critério: `1346231`, `1349519`, `1351729` e `1366801`. - -A revisão anterior havia examinado 1.892 arquivos distintos. Nesta atualização foram revisadas as imagens iniciais dos novos anúncios elegíveis e, em detalhe, 15 imagens coerentes do novo finalista `1397378`; imagens posteriores da galeria que pertencem visualmente a outros imóveis foram descartadas como contaminação. As plantas oficiais de duas e três unidades de três quartos e os mapas locais em `resources/` também foram confrontados com a galeria e a QC. - -As fotos foram usadas para avaliar coerência da planta, presença dos três dormitórios, circulação, luz, ventilação aparente, conservação, cozinha, banheiros, lavanderia, quintal, integração social e qualidade das ampliações. Elas não validam elétrica, hidráulica, impermeabilização, estrutura, calor, ruído ou regularização. - -As galerias baixadas frequentemente continuam, depois das imagens do imóvel, com recomendações do portal, fotos de outras casas, selos de “vendido”, publicidade e logos. Esses arquivos foram vistos, identificados pela quebra de coerência visual e desconsiderados. Por isso, quantidade bruta de fotos não foi tratada como qualidade de evidência, e hashes compartilhados foram usados apenas para triagem manual de duplicidade. - -## Ranking - -### 1. Anúncio 1279892 — QC 10 - -- **Anunciado:** R$ 595.000; 68 m² no campo estruturado e 107 m² na descrição; condomínio R$ 383; IPTU R$ 1.050; 3 quartos, 1 suíte; 1 vaga informada no banco. -- **Por que lidera:** oferece o melhor equilíbrio entre preço, localização considerada boa nos relatos, reforma pronta e planta visualmente coerente. A diferença para casas mais caras parece estar mais na ausência de mobiliário do que em obra obrigatória. -- **Fotos:** ambientes claros, sala ampla, cozinha planejada bem executada, banheiros atualizados e três dormitórios reconhecíveis. O fundo combina área coberta e trechos abertos, preservando luz e ventilação; não aparece uma segunda cozinha completa redundante. -- **Pendências:** esclarecer os conceitos de 68 e 107 m², a DCE citada, regularização das ampliações, número jurídico de vagas e impermeabilização da cobertura translúcida. -- **Ação:** candidato forte; visitar primeiro. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-rua-f-1279892 - -### 2. Anúncio 1350367 — QC 6 - -- **Anunciado:** R$ 569.000; 68 m² no banco, 107 m² totais e terreno de 131 m² na descrição; condomínio R$ 320; 3 quartos, 1 suíte; 2 vagas. -- **Por que está no topo:** preço baixo para o grupo final, QC considerada alternativa prática e ampliação social que parece efetivamente utilizável. -- **Fotos:** casa vazia, clara e aparentemente reformada; banheiros e pisos coerentes; três dormitórios aparentes. O quintal foi convertido em uma varanda ampla com churrasqueira e bancada, mas mantém abertura lateral e circulação, em vez de virar cômodo escuro ou segunda cozinha fechada. -- **Pendências:** confirmar área construída, licenças, ventilação e calor sob a cobertura, drenagem, elétrica da ampliação e o que falta de marcenaria/mobiliário. -- **Ação:** candidato forte; visitar. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-6-rua-c-1350367 - -### 3. Anúncio 1346017 — QC 10 - -- **Anunciado:** R$ 577.000; 68 m²; condomínio e IPTU ausentes; 3 quartos, 1 suíte; 2 vagas. -- **Por que se destaca:** casa pronta, luminosa e pouco personalizada por preço competitivo, em QC considerada boa nos relatos. -- **Fotos:** sala e jantar funcionais, cozinha planejada, três quartos claros e banheiros conservados. O quintal permanece majoritariamente aberto, com cobertura leve e uso social simples; é uma das soluções menos arriscadas para ventilação e manutenção. -- **Pendências:** confirmar proteção contra chuva, privacidade, área de serviço, taxas, orientação solar e regularização de qualquer cobertura. -- **Ação:** candidato forte; visitar. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-10-1346017 - -### 4. Anúncio 1397378 — QC 8 - -- **Anunciado:** R$ 600.000; 68 m²; condomínio R$ 316,14; IPTU R$ 1.119,67; 3 quartos, 1 suíte; 2 vagas. -- **Por que se destaca:** QC 8 tem acesso prático a comércio e serviços nos mapas fornecidos, e a casa combina preço intermediário com implantação próxima da planta original de três quartos. -- **Fotos:** sala e cozinha claras, marcenaria utilizável, três dormitórios aparentes e escada/andar superior compatíveis com a planta de 68 m². O fundo mantém trecho aberto, corredor lateral e cobertura parcial, sem transformar todo o quintal em segunda cozinha. Há sinais de acabamento simples e manutenção pontual, mas não de reforma integral imediata. -- **Pendências:** confirmar planta e documentos, origem das marcas/acabamentos, drenagem linear, impermeabilização da cobertura, orientação solar e itens de marcenaria que permanecem. Parte final da galeria está contaminada por fotos de outros imóveis e foi desconsiderada. -- **Ação:** candidato forte; visitar depois dos três primeiros. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-8-rua-b-1397378 - -### 5. Anúncio 1359960 — QC 7 - -- **Anunciado:** R$ 610.000; 68 m²; condomínio R$ 339; IPTU R$ 1.128,19; 3 quartos, 1 suíte; 2 vagas. -- **Por que entra:** QC considerada alternativa prática e conversão do quintal que cria uma sala social ampla, em vez de apenas instalar equipamentos com rótulo “gourmet”. -- **Fotos:** boa sala frontal, cozinha planejada, três quartos aparentes e grande espaço coberto nos fundos, hoje usado para descanso e convivência. Não foi identificada segunda cozinha completa; a ampliação parece flexível e mobiliável. -- **Pendências:** o fechamento é extenso; testar calor, ventilação cruzada, iluminação noturna, exaustão, acústica e impermeabilização antes de valorizá-lo. -- **Ação:** visitar com foco no conforto ambiental da ampliação. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-7-1359960 - -### 6. Anúncio 1319996 — QC 14 - -- **Anunciado:** R$ 649.000; 68 m²; condomínio e IPTU ausentes; 3 quartos, 1 suíte; 2 vagas. -- **Por que se destaca:** uma das reformas mais completas e coerentes, com placas solares e boa separação entre convivência e lavanderia. -- **Fotos:** acabamento uniforme, marcenaria abundante, três dormitórios aparentes, banheiros atualizados e área externa coberta com bancada, churrasqueira, abertura lateral e circulação suficiente para receber pessoas. -- **Pendências:** QC 14 não está entre as QCs priorizadas pelos relatos; confirmar taxas, homologação das placas, ventilação/exaustão, manutenção da cobertura e escopo técnico da reforma. -- **Ação:** visitar como referência de imóvel pronto. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-14-1319996 - -### 7. Anúncio 1324014 — QC 12 - -- **Anunciado:** R$ 649.000; 68 m²; condomínio e IPTU ausentes; 3 quartos, 1 suíte; vaga não extraída. -- **Por que entra:** acabamento interno e marcenaria fortes, com área social coberta bem executada e condição aparente de ocupação sem reforma relevante. -- **Fotos:** cozinha, sala e banheiros atualizados; a área gourmet é ampla e iluminada, mas concentra churrasqueira, bancada e apoio muito próximos da cozinha principal. -- **Pendências:** confirmar vaga, planta original, ventilação, exaustão, conforto térmico, drenagem, regularização da cobertura e se a duplicação de equipamentos melhora de fato a rotina. -- **Ação:** visitar depois dos candidatos mais baratos e das QCs prioritárias. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-12-rua-k-1324014 - -### 8. Anúncio 1333569 — QC 6 - -- **Anunciado:** R$ 650.000; 90 m²; condomínio e IPTU ausentes; 3 quartos, 1 suíte; 2 vagas. -- **Por que entra:** QC considerada alternativa prática e ampliação social já em uso, com abertura superior e lateral que pode mitigar o fechamento do quintal. -- **Fotos:** casa ocupada, bem conservada e funcional; a área externa reúne jantar, estar e churrasqueira, com cobertura aparentemente translúcida/retrátil. Os cômodos íntimos parecem utilizáveis, embora menos bem documentados que nos primeiros colocados. -- **Pendências:** confirmar se os 90 m² são comparáveis, funcionamento e manutenção da cobertura, exaustão, calor, iluminação, taxas e regularização. -- **Ação:** visitar depois dos oito primeiros. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-6-1333569 - -### 9. Anúncio 1359480 — QC 4 - -- **Anunciado:** R$ 535.000; 70 m²; condomínio R$ 330; IPTU ausente; 3 quartos, 1 suíte; 2 vagas. -- **Por que entra:** é a alternativa de menor preço entre os finalistas habitáveis e preserva margem financeira para custos de aquisição e adequações. -- **Fotos:** sala integrada utilizável, cozinha e banheiros funcionais e três dormitórios aparentes. O quintal/lavanderia têm acabamento mais simples que os líderes, mas não sugerem obra estrutural imediata nas imagens. -- **Pendências:** QC 4 precisa provar conveniência logística; confirmar planta original, área de 70 m², documentação, drenagem, impermeabilização e orçamento de acabamento externo. -- **Ação:** visitar como referência de valor e limite inferior de preço. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1359480 - -### 10. Anúncio 1356197 — QC 4 - -- **Anunciado:** R$ 649.000; 68 m²; condomínio R$ 316; IPTU ausente; 3 quartos, 1 suíte; 2 vagas. -- **Por que entra:** casa vazia com acabamento interno forte e condição aparente de ocupação sem reforma relevante. -- **Fotos:** sala, cozinha, banheiros e marcenaria bem conservados; boa parte do espaço coberto parece funcionar como garagem, com menor evidência de quintal social útil. -- **Pendências:** confirmar planta original, uso e ventilação do espaço coberto, drenagem, impermeabilização, regularização e conveniência da QC 4. -- **Ação:** visitar depois dos nove primeiros. -- **URL:** https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-4-1356197 - -## Quase entraram - -- **[1368562](https://www.dfimoveis.com.br/imovel/casa-condominio-3-quartos-venda-jardins-mangueiral-brasilia-df-qc-5-1368562), QC 5, R$ 720.000:** ampliação de aproximadamente 110 m² bem executada e ventilada por cobertura translúcida, mas o prêmio de preço e a QC menos prioritária reduzem a urgência da visita. - -## Quarentenas e exclusões relevantes - -- **1351729:** a primeira foto declara “VENDIDO” e “negócio fechado”. Isso não comprova registro ou preço da transação, mas é evidência suficiente de indisponibilidade operacional; removido do ranking de visitas por correção do usuário em 17/07/2026. -- **1349519:** a primeira foto também declara “VENDIDO” e “negócio fechado”, com o código correspondente ao anúncio. Removido do ranking de visitas por correção do usuário em 17/07/2026. -- **1366801:** a primeira foto declara “VENDIDO” e “negócio fechado”, com o código 1468 correspondente ao anúncio. Removido do ranking de visitas por correção do usuário em 17/07/2026. -- **1346231:** a primeira foto declara “VENDIDO” e “negócio fechado”, com o código 1428 correspondente ao anúncio. Removido após auditoria sistemática das primeiras fotos em 17/07/2026. -- **1350829 e 1358463:** anunciados com quatro quartos; fora do filtro. -- **1374012:** a descrição declara que um dos três quartos é adaptado; não há prova suficiente de planta original de três quartos. -- **1351663:** preço estruturado de R$ 570 milhões, erro evidente de escala; excluído até correção. -- **1369473 e 1336887:** galerias dominadas por material genérico ou de outros anúncios, sem evidência suficiente do imóvel. -- **1301400 e 1201070:** fotos com marcação de vendido/permuta; tratadas como inconsistência, não prova de venda. -- **1041635 e 1383513:** mesmo imóvel provável pelas fotos; a repetição não foi contada como duas opções. -- **1239730, 1242337, 1294027, 1359192, 1359662 e 1367341:** grupo de republicações prováveis da mesma unidade; a oferta não foi inflada. -- **1354016 e 1377683:** duplicata provável após revisão visual; seria uma única opção, não duas. -- **1356153, 1358854 e 1358920:** grupo provável de uma mesma casa vazia. -- **1348449 e 1351461:** descrições e composição praticamente idênticas na QC 13; tratar como mesmo imóvel até confirmação. - -## Qualidade e limitações - -- O banco contém anúncios e snapshots de conteúdo, não imóveis físicos confirmados. Os dois pontos observados ainda são insuficientes para tendência ou tempo real de mercado. -- Entre os 84 anúncios ativos do Mangueiral, condomínio falta em 40 e IPTU em 74; não foi possível comparar custo mensal com consistência. -- Área existe em todos os registros, mas não é semanticamente comparável. Os próprios finalistas exibem divergências entre banco e descrição. -- A busca e as galerias contêm material contaminante do portal. Duplicidades foram classificadas manualmente e permanecem probabilísticas. -- Anúncio, foto e descrição não confirmam planta original, regularização da ampliação, documentação, estrutura ou sistemas. -- As preferências por QC vêm de relatos anedóticos e precisam ser testadas em deslocamentos e visitas reais. -- A referência financeira de R$ 650 mil a R$ 750 mil não foi assumida como atual. O ranking prioriza visitas, não define capacidade de compra ou preço máximo. - -## Verificações obrigatórias nas visitas - -1. solicitar planta original, matrícula, habite-se e documentos das ampliações; -2. medir os três quartos e conferir posição da escada, fachada e implantação; -3. testar calor, ventilação, luz e ruído com portas e janelas fechadas e abertas; -4. inspecionar telhado, rufos, calhas, impermeabilização, drenagem e sinais de infiltração; -5. testar quadro elétrico, aterramento, tomadas, hidráulica, pressão e escoamento; -6. verificar se a área gourmet duplica a cozinha ou realmente melhora a rotina; -7. confirmar condomínio, IPTU, vagas, taxas extras, regras de obra e atas; -8. percorrer a QC em horários relevantes para comércio, trânsito, segurança, ruído e estacionamento; -9. só depois estimar adequação e definir preço máximo de negociação. - -## Reprodutibilidade - -- `analysis.py`: extrai banco e HTMLs em leitura, produzindo TSV. -- `photo_similarity.py`: sinaliza pares por hash visual simples; exige revisão humana e sofre com imagens genéricas do portal. -- `query.sql`: consultas principais de universo e qualidade. -- `parameters.json`: parâmetros e fingerprint do snapshot. -- `results.csv`: ranking em formato tabular. diff --git a/reports/mangueiral_top10/__pycache__/analysis.cpython-312.pyc b/reports/mangueiral_top10/__pycache__/analysis.cpython-312.pyc deleted file mode 100644 index db9baaac81544d76011e5100aa4212dadbff94eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5417 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 diff --git a/reports/mangueiral_top10/__pycache__/photo_similarity.cpython-312.pyc b/reports/mangueiral_top10/__pycache__/photo_similarity.cpython-312.pyc deleted file mode 100644 index 254cd1561790f73ffbf95f65e0c7615503afb6fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4120 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 V{a5zD7o7L!eY-5;g5)yD{V(G__e1~y diff --git a/reports/mangueiral_top10/__pycache__/photo_similarity.cpython-314.pyc b/reports/mangueiral_top10/__pycache__/photo_similarity.cpython-314.pyc deleted file mode 100644 index 4d8e4b1953a07864c50ded85925b1629f3557116..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4680 zcmahMZA=^I`90tH>ud~!&k!3Qfi!XAm^Mk9uO<~C5L}3HjWHo3xnAtoaFu;`xjT~t zRg`30=-dfQY%gU2TQAH+n+t}9cN6G zw6Ep&>-l(}ulKp9yBtlbj9P8TI;-9GU&SF)DOTVuopxxQBzckN;y(=is=T2M4Vqb0L@?& z5Je@VhD*hINtI?;QPR0rxr8j?Um=jz1ttPRXylyzMpVU$tm;}f7;W2eye=!xJl$=& zhu?OY2$oRp7=E7LHU;K+wH7!u!(St@SVj+#kE-7y-CWlu8RpNJs|Nr@rfOtDnj8M6 zYX1Zw%4i>&FtlbKM)QNC-R5Ct#lh zm@7?Rr=r`u3C}Bz@l4ca4rBGbA3us*|wQ#2IY3C$UbhiQyg|G4GG+A~a4F z;uJ^RD#_G_Q#5a!OA&A5f@V0Ug+d7%v?|{+Tyj3IVc?gQ3#Fo@5$&Ji%%&2ljoO_= z!;?g#?uT77oc{=uGTL-7&c=d&D*o|dVS;FoA0*F zx;C2k&Gr0vYPNs9sclYp_v(EA!gIfVVYTb*@A9AI|H?0pU0LnBvU+}escU?-@#-6W z4`|d9-g2V4R$%F`FAqIz0QMl#|Gw}ytTY{2^hLiyl(W&OZP=t?{I3=R6~5oz+luB- z^*Wt@ZuP+QW2djT$@_5~L*`91>09f2kJ2AUDd>0Q9&!oI3mP;{@%Y@+4Ni{GDsihy zZjQl_9HWxph)`+`3|5XY&21(Nv+E*&2Ixq}=%&tXk#06;4r(3jXW?II#LcE8dFadE znkZ6eg0X4+5QfKSk|R%UClv(}$zu})V_(|vlQ5j$is7;`9+zb7G@?)-!8fFtFq$BS zGuzJPvN@#WMTtq0tCc(mwwoD+|`RLx``PqE! z5hBtDwyH!0Xz3fgZOI7``qb|$r@s#zE8fU>ApX4>ui5+j=vcK!e{0bX{>>Uw?oO~* z82(C)h$8HaGcOkKnHkRuMLCDhsd5_X2D+q*GtJ3V&!x*f+|3eJ@9?q&Mcp}WIAJ(1 z^j(T4gg-!7vR0DixUqP8fZHi6xSkR2Pz6YkM*4>OdefX1BVPch?RYV_3*Mg^zRcM$ z4GU^^3MxEcqwPX_aX6l^(M)iG8y$%!E^&u!S}_;dMKLzem+Iq0Nz>Is7GJ)k7NXq2 z?pFjg2m42N#O^*A;m&hU@1p0pp2S725ECUKtBcc^hf*>=!X-wBhAMcGC|8oQa}N=iQu(oH@x->#x93> z%R>g`jqEYp+ts^S!$>#_#mvPEe6lA!V7T#ZoGt0t@Z_iz zv+OXu7kkn@d}??&ZMXyl%A%a%(x5KKIm4-_P{A8sER~8_g-4Fzy&>v+RxZIVT@)pQ zR%8WAAt{G%M`{fgDk&lbJXQ<_Dk05qk@5y>25T!M=8PH@7vMnyv1`5<+CdV77IT^k zRVV^+MOD+`J{n#P1HYU(6{ZDvzTGH5aRv_vav2TQLL>(mzHL-<;myIF7T^)5X(XMK z=&Wj11;!KwQBwN=+y#H_4n@B38}+U4h2II!Kl@?Jg7;qQV#k?3F)Q_b(0zN*L`&>zTmol&#J$D z&3|CYe_$Tp_eZzf=&8eBvB+0{xA{l0a_UFXbsL~RztDn*6!K(dUMO{rSDbH5L1Ag^B98&r0Kk^~Qa3H{Tt4=hBuJg}XOdRM#@s@#d==bxm`vbL{;7`SJO|1@+#+dwUm~ z&)%|514a3)_CVCRN1UoK2TC@JuQ zmvqfwNc5{m2v?W2r*@Nb_LGhT36V>Jw>bzvzUXGHXa$nzdH7D13i!MVFD~-|t!XfU zT9~4?7=~iL_9LqEa}@Xtb$*6g|Amfjxsb;{JMh|f*W95ccWBuiUUMH@avxlvR^5lo z%sLxbtnXN5I~Kc6uCgb~?sb2|TZz{bW$)K6c-Vd&gpzeLMPJF`OX{gm int: l.raw_html_path, COUNT(p.source_url) AS photo_count FROM listings AS l JOIN listing_searches AS s USING (listing_id) - LEFT JOIN photos AS p USING (listing_id) + LEFT JOIN photos AS p ON p.listing_id=l.listing_id AND p.is_primary_gallery=1 WHERE instr(s.search_url, ?) > 0 AND l.inactive_at IS NULL GROUP BY l.listing_id diff --git a/reports/mangueiral_top15/parameters.json b/reports/mangueiral_top15/parameters.json new file mode 100644 index 0000000..9dac84d --- /dev/null +++ b/reports/mangueiral_top15/parameters.json @@ -0,0 +1,38 @@ +{ + "analysis_date": "2026-07-22", + "listing_data_reference_date": "2026-07-22", + "database_path": "dfimoveis_data/dfimoveis.sqlite3", + "database_main_file_sha256": "2d02c4c15f56ae60b1ce8ee1aa00c453e61cb0559d5c429ad7e20cb58802341c", + "database_mode": "read_only_transaction_with_wal", + "search_url_fragment": "/jardins-mangueiral/", + "listing_limit": 150, + "observed_listing_count": 95, + "active_listing_count": 78, + "inactive_listing_count": 17, + "database_crawl_run_count": 22, + "database_listing_count": 162, + "database_observation_count": 638, + "database_photo_record_count": 7457, + "primary_gallery_photo_count": 3521, + "active_mangueiral_primary_gallery_photo_count": 1865, + "active_three_bedroom_at_or_below_price_ceiling_count": 73, + "full_active_universe_reprocessed_after_user_feedback": true, + "full_active_universe_size": 78, + "new_inferred_fit_listing_ids_pending_user_review": ["1377683", "1371718", "1140317", "1401074", "1345745", "1357311", "1259149", "348115", "1346118"], + "ranking_size": 15, + "ranking_unit": "probable_physical_property", + "numeric_score_used": false, + "maximum_tolerated_listing_price_brl": 750000, + "price_ceiling_is_eliminatory": true, + "price_ceiling_excludes_transaction_and_renovation_costs": true, + "user_feedback_date": "2026-07-21", + "user_visual_functional_score_scale": "1_to_5", + "user_visual_functional_scores_by_listing_id": {"1279892": 4, "1350367": 2, "1397378": 1, "1359960": 2, "1319996": 4, "1324014": 3, "1333569": 3, "1359480": 1, "1290502": 3, "1368562": 2, "1202799": 2, "1357912": 3, "1363572": 2, "1290512": 3, "1354257": 2}, + "price_per_m2_used": false, + "original_three_bedroom_requires_document_confirmation": true, + "first_photo_active_universe_audit_count": 78, + "visually_unavailable_listing_ids": ["1301400", "1346017", "1346231", "1349519", "1351729", "1352943", "1356197", "1362968", "1366801"], + "source_mismatch_listing_ids": ["1369473"], + "adapted_or_inconsistent_bedroom_listing_ids": ["1026522", "1348449", "1374012"], + "context_documents_reviewed": ["docs/CURRENT_STATE.md", "docs/PROJECT_CONTEXT.md", "docs/LOCATION_CRITERIA.md", "docs/PROPERTY_CRITERIA.md", "docs/FINANCIAL_STRATEGY.md", "docs/SCRAPER_OPERATIONS.md", "docs/DATA_MODEL.md", "docs/ANALYSIS_RULES.md"] +} diff --git a/reports/mangueiral_top10/photo_similarity.py b/reports/mangueiral_top15/photo_similarity.py similarity index 92% rename from reports/mangueiral_top10/photo_similarity.py rename to reports/mangueiral_top15/photo_similarity.py index 57cc216..40265ae 100644 --- a/reports/mangueiral_top10/photo_similarity.py +++ b/reports/mangueiral_top15/photo_similarity.py @@ -22,7 +22,7 @@ DATA_ROOT = DB_PATH.parent def difference_hash(path: Path) -> int | None: try: with Image.open(path) as image: - pixels = list(image.convert("L").resize((9, 8)).getdata()) + pixels = list(image.convert("L").resize((9, 8)).get_flattened_data()) except (OSError, UnidentifiedImageError): return None value = 0 @@ -48,7 +48,7 @@ def main() -> None: ), uncommon AS ( SELECT sha256 FROM photos - WHERE sha256 IS NOT NULL + WHERE sha256 IS NOT NULL AND is_primary_gallery=1 GROUP BY sha256 HAVING COUNT(DISTINCT listing_id) <= 5 ) @@ -56,7 +56,7 @@ def main() -> None: FROM photos AS p JOIN mangueiral AS m USING (listing_id) JOIN uncommon AS u USING (sha256) - WHERE p.local_path IS NOT NULL + WHERE p.local_path IS NOT NULL AND p.is_primary_gallery=1 ORDER BY p.listing_id, p.ordinal """ ).fetchall() diff --git a/reports/mangueiral_top10/query.sql b/reports/mangueiral_top15/query.sql similarity index 92% rename from reports/mangueiral_top10/query.sql rename to reports/mangueiral_top15/query.sql index 1113b86..f8d3b18 100644 --- a/reports/mangueiral_top10/query.sql +++ b/reports/mangueiral_top15/query.sql @@ -21,11 +21,12 @@ WHERE instr(s.search_url, '/jardins-mangueiral/') > 0 ORDER BY l.price_brl, l.listing_id LIMIT 150; --- Cardinalidade de fotos por anúncio. A contagem inclui material genérico do portal. +-- Cardinalidade de fotos da galeria primária por anúncio; recomendações legadas são excluídas. SELECT p.listing_id, COUNT(*) AS photo_count FROM photos AS p JOIN listing_searches AS s USING (listing_id) WHERE instr(s.search_url, '/jardins-mangueiral/') > 0 + AND p.is_primary_gallery = 1 AND EXISTS ( SELECT 1 FROM listings AS l WHERE l.listing_id = p.listing_id AND l.inactive_at IS NULL diff --git a/reports/mangueiral_top15/results.csv b/reports/mangueiral_top15/results.csv new file mode 100644 index 0000000..b8403e0 --- /dev/null +++ b/reports/mangueiral_top15/results.csv @@ -0,0 +1,16 @@ +rank,representative_listing_id,user_score,assessment_basis,probable_duplicate_ids,qc,price_brl,area_m2,bedrooms,suites,parking_spaces,original_three_bedroom_status,visual_status,decision +1,1279892,4,user_feedback,,10,595000,68,3,1,1,probable_pending_documents,no_sale_marker,strong_candidate +2,1319996,4,user_feedback,,14,649000,68,3,1,2,probable_pending_documents,no_sale_marker,strong_candidate +3,1377683,,inferred_from_full_database_review,,11,585000,69,3,1,2,probable_pending_documents,no_sale_marker,user_visual_review_then_documents +4,1371718,,inferred_from_full_database_review,,14,650000,100,3,1,2,probable_pending_documents,no_sale_marker,user_visual_review_then_documents +5,1140317,,inferred_from_full_database_review,,5,599000,80,3,1,1,probable_pending_documents,no_sale_marker,user_visual_review_then_documents +6,1324014,3,user_feedback,,12,649000,68,3,1,,probable_pending_documents,no_sale_marker,visit +7,1290502,3,user_feedback,,11,600000,75,3,1,2,probable_pending_documents,no_sale_marker,validate_original_plan_cover_and_price_then_visit +8,1401074,,inferred_from_full_database_review,,13,720000,110,3,1,2,probable_pending_documents,no_sale_marker,user_review_then_documents_and_heat_test +9,1345745,,inferred_from_full_database_review,,12,670000,68,3,1,2,probable_pending_documents,no_sale_marker,user_review_and_heat_test +10,1333569,3,user_feedback,,6,650000,90,3,1,2,probable_pending_documents,no_sale_marker,test_glass_enclosure_heat_then_visit +11,1357311,,inferred_from_full_database_review,,1,699900,68,3,1,2,probable_pending_documents,no_sale_marker,inspect_possible_water_damage +12,1357912,3,user_feedback,,3,580000,110,3,1,2,probable_pending_documents,no_sale_marker,validate_intervention_cost_before_visit +13,1259149,,inferred_from_full_database_review,,11,590000,115,3,1,,probable_pending_documents,no_sale_marker,user_review_and_area_validation +14,348115,,inferred_from_full_database_review,,14,550000,120,3,1,2,probable_pending_documents,no_sale_marker,user_review_and_condition_validation +15,1346118,,inferred_from_full_database_review,,14,550000,110,3,1,2,probable_pending_documents,no_sale_marker,user_review_and_renovation_scope diff --git a/requirements-dfimoveis.txt b/requirements-dfimoveis.txt index 5495687..7f78d3e 100644 --- a/requirements-dfimoveis.txt +++ b/requirements-dfimoveis.txt @@ -2,7 +2,7 @@ httpx[http2]>=0.27,<1 beautifulsoup4>=4.12,<5 lxml>=5,<7 # Used by the report photo-similarity screening scripts. -Pillow>=10,<13 +pillow>=10,<13 # Default browser automation (uses the locally installed Microsoft Edge): playwright>=1.49,<2 cloudscraper>=1.2,<2 diff --git a/run_scraper.ps1 b/run_scraper.ps1 new file mode 100644 index 0000000..9857451 --- /dev/null +++ b/run_scraper.ps1 @@ -0,0 +1,5 @@ +Set-Location E:\forge\python\house-quest + +& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="E:\forge\python\house-quest\dfimoveis_data\browser-profile" + +.\.venv-scraper\Scripts\python.exe .\dfimoveis_scraper.py --search-file .\search_list.txt --output .\dfimoveis_data --attach-chrome diff --git a/scripts/migrate_dfimoveis_schema_v2.py b/scripts/migrate_dfimoveis_schema_v2.py new file mode 100644 index 0000000..7b5737a --- /dev/null +++ b/scripts/migrate_dfimoveis_schema_v2.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Add schema-v2 quality metadata without deleting or rewriting source evidence.""" +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import sqlite3 +import sys +from pathlib import Path +from urllib.parse import urlparse + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from dfimoveis_scraper import ( + Database, + normalize_creci, + normalize_description_for_hash, + normalize_listing_address, + now_utc, +) + + +VISUALLY_SOLD = { + "1263868", "1301400", "1346017", "1346231", "1349519", + "1351729", "1352943", "1356197", "1362968", "1366801", +} + + +def stable_hash(row: sqlite3.Row) -> str: + payload = { + "url": row["url"], + "title": row["title"], + "description": normalize_description_for_hash(row["description"]), + "transaction_type": row["transaction_type"], + "property_type": row["property_type"], + "price_brl": row["price_brl"], + "condominium_brl": row["condominium_brl"], + "iptu_brl": row["iptu_brl"], + "area_m2": row["area_m2"], + "bedrooms": row["bedrooms"], + "suites": row["suites"], + "parking_spaces": row["parking_spaces"], + "address_normalized": normalize_listing_address(row["url"], row["address"]), + "neighborhood_normalized": "Jardins Mangueiral" if "jardins-mangueiral" in row["url"] else None, + "advertiser_name": row["advertiser_name"], + "creci_normalized": normalize_creci(row["creci"]), + "latitude": row["latitude"], + "longitude": row["longitude"], + "published_at": row["published_at"], + } + return hashlib.sha256(json.dumps(payload, sort_keys=True, ensure_ascii=False).encode()).hexdigest() + + +def upsert_issue(conn: sqlite3.Connection, listing_id: str, code: str, severity: str, evidence: str) -> None: + ts = now_utc() + conn.execute( + """INSERT INTO data_quality_issues(listing_id,issue_code,severity,evidence,first_seen_at,last_seen_at) + VALUES(?,?,?,?,?,?) ON CONFLICT(listing_id,issue_code) DO UPDATE SET + severity=excluded.severity,evidence=excluded.evidence,last_seen_at=excluded.last_seen_at""", + (listing_id, code, severity, evidence, ts, ts), + ) + + +def migrate(database: Path) -> None: + # Database initialization performs only additive CREATE/ALTER operations. + bootstrap = Database(database) + bootstrap.close() + conn = sqlite3.connect(database) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys=ON") + with conn: + for row in conn.execute("SELECT * FROM listings"): + normalized_address = normalize_listing_address(row["url"], row["address"]) + normalized_neighborhood = "Jardins Mangueiral" if "jardins-mangueiral" in row["url"] else None + normalized_creci = normalize_creci(row["creci"]) + conn.execute( + """UPDATE listings SET address_normalized=?,neighborhood_normalized=?, + creci_normalized=?,raw_content_hash=COALESCE(raw_content_hash,content_hash), + stable_content_hash=? WHERE listing_id=?""", + (normalized_address, normalized_neighborhood, normalized_creci, stable_hash(row), row["listing_id"]), + ) + description = (row["description"] or "").lower() + if row["price_brl"] and row["price_brl"] > 20_000_000: + upsert_issue(conn, row["listing_id"], "implausible_price", "high", str(row["price_brl"])) + if row["area_m2"] and row["area_m2"] < 10: + upsert_issue(conn, row["listing_id"], "implausible_area", "high", str(row["area_m2"])) + if "procuro casa para comprar" in description: + upsert_issue(conn, row["listing_id"], "source_mismatch_buyer_request", "high", "description declares a purchase request") + if "quarto adaptado" in description or "escritório adaptado" in description: + upsert_issue(conn, row["listing_id"], "adapted_room", "medium", "description mentions an adapted room") + + mismatch_counts: dict[str, int] = {} + for photo in conn.execute("SELECT listing_id,source_url FROM photos"): + owner = re.search(r"/fotos/(\d+)/", urlparse(photo["source_url"]).path, re.I) + is_primary = bool(owner and owner.group(1) == photo["listing_id"]) + status = "unreviewed" if is_primary else ("excluded_recommendation" if owner else "excluded_site_asset") + conn.execute( + "UPDATE photos SET discovery_source=?,is_primary_gallery=?,review_status=? WHERE listing_id=? AND source_url=?", + ("listing_id_url" if is_primary else "legacy_page_wide_scan", int(is_primary), status, photo["listing_id"], photo["source_url"]), + ) + if owner and not is_primary: + mismatch_counts[photo["listing_id"]] = mismatch_counts.get(photo["listing_id"], 0) + 1 + for listing_id, count in mismatch_counts.items(): + upsert_issue(conn, listing_id, "photo_owner_mismatch", "high", f"{count} legacy photos do not belong to this listing id") + + # Confirmed manual exceptions whose structured description is truncated. + manual_issues = [ + ("1369473", "source_mismatch_buyer_request", "high", "full captured page declares PROCURO CASA PARA COMPRAR"), + ("1348449", "adapted_room", "medium", "full description declares an adapted office/room"), + ("1374012", "adapted_room", "medium", "full description declares an adapted bedroom"), + ("1026522", "bedroom_count_conflict", "medium", "structured three bedrooms conflicts with four bedrooms in description"), + ] + for listing_id, code, severity, evidence in manual_issues: + if conn.execute("SELECT 1 FROM listings WHERE listing_id=?", (listing_id,)).fetchone(): + upsert_issue(conn, listing_id, code, severity, evidence) + + for listing_id in VISUALLY_SOLD: + exists = conn.execute( + "SELECT 1 FROM availability_observations WHERE listing_id=? AND classification='sold_visual' AND source='manual_first_photo_review'", + (listing_id,), + ).fetchone() + if not exists and conn.execute("SELECT 1 FROM listings WHERE listing_id=?", (listing_id,)).fetchone(): + active = conn.execute("SELECT inactive_at IS NULL FROM listings WHERE listing_id=?", (listing_id,)).fetchone()[0] + conn.execute( + """INSERT INTO availability_observations + (listing_id,observed_at,portal_active,classification,source,evidence) + VALUES(?,?,?,?,?,?)""", + (listing_id, now_utc(), active, "sold_visual", "manual_first_photo_review", "VENDIDO or equivalent marker on first photo"), + ) + + latest_run = conn.execute("SELECT * FROM crawl_runs ORDER BY id DESC LIMIT 1").fetchone() + if latest_run and latest_run["searches_json"] == "[]" and latest_run["finished_at"]: + searches = [ + row[0] for row in conn.execute( + """SELECT DISTINCT search_url FROM listing_searches + WHERE last_seen_at BETWEEN ? AND ? ORDER BY search_url""", + (latest_run["started_at"], latest_run["finished_at"]), + ) + ] + stats = json.loads(latest_run["stats_json"] or "{}") + conn.execute( + """UPDATE crawl_runs SET searches_json=?,config_json=?,scope_complete=? WHERE id=?""", + (json.dumps(searches, ensure_ascii=False), json.dumps({"legacy_backfill": True}), + int(latest_run["status"] == "ok" and stats.get("details_failed", 0) == 0), latest_run["id"]), + ) + result = conn.execute("PRAGMA quick_check").fetchone()[0] + counts = { + "listings": conn.execute("SELECT count(*) FROM listings").fetchone()[0], + "history": conn.execute("SELECT count(*) FROM listing_history").fetchone()[0], + "photos": conn.execute("SELECT count(*) FROM photos").fetchone()[0], + "non_gallery_photos": conn.execute("SELECT count(*) FROM photos WHERE is_primary_gallery=0").fetchone()[0], + "quality_issues": conn.execute("SELECT count(*) FROM data_quality_issues").fetchone()[0], + "availability_observations": conn.execute("SELECT count(*) FROM availability_observations").fetchone()[0], + } + conn.close() + print(json.dumps({"quick_check": result, **counts}, ensure_ascii=False, indent=2)) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--database", default="dfimoveis_data/dfimoveis.sqlite3") + args = parser.parse_args() + migrate(Path(args.database).resolve()) + + +if __name__ == "__main__": + main() diff --git a/search_list.txt b/search_list.txt index e005498..ef036e5 100644 --- a/search_list.txt +++ b/search_list.txt @@ -1,3 +1,2 @@ # One complete DFImoveis search URL per line -https://www.dfimoveis.com.br/venda/df/brasilia/jardins-mangueiral/casa/3,4-quartos -https://www.dfimoveis.com.br/venda/df/cruzeiro/novo/apartamento/3-quartos \ No newline at end of file +https://www.dfimoveis.com.br/venda/df/brasilia/jardins-mangueiral/casa/3,4-quartos \ No newline at end of file diff --git a/tests/test_dfimoveis_schema_v2.py b/tests/test_dfimoveis_schema_v2.py new file mode 100644 index 0000000..9435484 --- /dev/null +++ b/tests/test_dfimoveis_schema_v2.py @@ -0,0 +1,64 @@ +import tempfile +import unittest +from pathlib import Path + +from dfimoveis_scraper import Database, Parser, normalize_creci, normalize_listing_address + + +class SchemaV2Tests(unittest.TestCase): + def test_normalization(self): + self.assertEqual(normalize_creci("26.903 Ver mais qualquer texto"), "26903") + self.assertEqual( + normalize_listing_address( + "https://www.dfimoveis.com.br/imovel/casa-venda-jardins-mangueiral-qc-13-rua-g-123", + "texto contaminado " * 100, + ), + "Jardins Mangueiral - QC 13 - Rua G", + ) + + def test_parser_rejects_recommendation_photos(self): + page = """ + + + + + """ + data, photos = Parser.detail( + "https://www.dfimoveis.com.br/imovel/casa-3-quartos-venda-jardins-mangueiral-qc-13-1401074", + page, + ) + self.assertEqual(photos, ["https://img.dfimoveis.com.br/fotos/1401074/a.webp"]) + self.assertEqual(data["creci_normalized"], None) + self.assertEqual(data["address_normalized"], "Jardins Mangueiral - QC 13") + + def test_schema_migration_is_additive(self): + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / "test.sqlite3" + db = Database(path) + columns = {row[1] for row in db.conn.execute("PRAGMA table_info(listings)")} + self.assertIn("stable_content_hash", columns) + self.assertIn("creci_normalized", columns) + self.assertTrue(db.conn.execute("SELECT 1 FROM sqlite_master WHERE name='data_quality_issues'").fetchone()) + db.close() + + def test_raw_ui_noise_does_not_create_material_change(self): + page = '' + url = "https://www.dfimoveis.com.br/imovel/casa-venda-jardins-mangueiral-qc-13-123" + data, _ = Parser.detail(url, page) + with tempfile.TemporaryDirectory() as tmp: + db = Database(Path(tmp) / "test.sqlite3") + run_id = db.start_run([url], {"test": True}) + self.assertTrue(db.upsert_listing(data, url, run_id)) + data["creci"] = "texto instável da interface" + data["address"] = "outro bloco contaminado" + data["raw_content_hash"] = "different-raw-hash" + self.assertFalse(db.upsert_listing(data, url, run_id)) + self.assertEqual(db.conn.execute("SELECT count(*) FROM listing_history").fetchone()[0], 1) + self.assertEqual(db.conn.execute("SELECT count(*) FROM listing_change_events").fetchone()[0], 1) + self.assertEqual(db.mark_inactive(url, set(), "9999-01-01T00:00:00+00:00"), 1) + self.assertEqual(db.mark_inactive(url, set(), "9999-01-01T00:00:00+00:00"), 0) + db.close() + + +if __name__ == "__main__": + unittest.main()