">
VICTOR SAAD
All buildsTodos os projetos
Python · PostgreSQL · FastAPI · Leaflet

São Paulo housing market radar

Radar imobiliário de São Paulo

Listing sites show you today. I wanted the film: every home for sale and for rent in the city, every price change, and the ones that quietly left the market.

Os portais de anúncios mostram o hoje. Eu queria o filme: cada imóvel à venda e para alugar na cidade, cada mudança de preço e os que saíram do mercado sem alarde.

PythonPostgreSQL 16FastAPIChart.jsLeafletcronDiscord webhooks
See it live: neighbourhood prices, updated dailyVer ao vivo: preços por bairro, atualizado todo dia →

The problem

O problema

A listing page is a snapshot. It doesn't tell you the asking price was cut twice, that the same flat was listed for rent last month, or that a whole street's prices moved. For a decision this size, the history is the information.

Um anúncio é uma foto do momento. Ele não conta que o preço pedido já caiu duas vezes, que o mesmo apartamento estava para alugar no mês passado ou que os preços de uma rua inteira se mexeram. Numa decisão desse tamanho, o histórico é a informação.

What it does

O que ele faz

Every morning at 05:30 a collector reads a public listings API for the whole city, once for sales and once for rentals, and writes it into PostgreSQL with full history: each price change, each status change (active, off market, back again). It began as an automated spreadsheet and outgrew it within weeks.

Toda manhã, às 05:30, um coletor lê uma API pública de anúncios da cidade inteira, uma vez para venda e outra para aluguel, e grava tudo no PostgreSQL com o histórico completo: cada mudança de preço, cada mudança de status (ativo, fora do mercado, de volta). Começou como uma planilha automatizada e ficou grande demais para ela em poucas semanas.

  • 343,000 homes tracked since May 2025, about 186,000 of them active today.
  • 731,000 price changes recorded across 219 daily runs.
  • Total monthly cost (rent plus condo fees plus property tax) as a first-class filter, because asking rent alone is how people get surprised.
  • 343 mil imóveis acompanhados desde maio de 2025, cerca de 186 mil deles ativos hoje.
  • 731 mil mudanças de preço registradas em 219 execuções diárias.
  • Custo mensal total (aluguel mais condomínio mais IPTU) como filtro de primeira classe, porque olhar só o aluguel pedido é como as pessoas são pegas de surpresa.
The explorer: filters for neighbourhood, region, bedrooms, price and total cost, a list of listing cards and a city map with one dot per home coloured by price per square metre.
Explore: filters on the left, the city on the right, each dot coloured by price per m². Photos blurred and prices masked here.
Explorar: filtros à esquerda, a cidade à direita, cada ponto colorido pelo preço por m². Aqui, fotos borradas e preços mascarados.

The market view

A visão de mercado

The same data, aggregated: active listings, how many left the market, median price and price per m², the twenty busiest neighbourhoods ranked by price per m², and the distribution across the city. Useful on its own, and the baseline every single listing is compared against.

Os mesmos dados, agregados: anúncios ativos, quantos saíram do mercado, preço mediano e preço por m², os vinte bairros com mais anúncios ordenados por preço por m² e a distribuição pela cidade. Útil por si só, e a régua contra a qual cada anúncio é comparado.

Market tab: cards for active listings, listings that left the market, median price per square metre, median price and price drops in 30 days; a bar chart of average price per square metre in the 20 neighbourhoods with most listings; and a histogram of price per square metre.
Market: the whole city, recomputed after every run.
Mercado: a cidade inteira, recalculada a cada execução.

Drops, favourites and alerts

Quedas, favoritos e alertas

A dedicated view lists every active home whose price just changed, with the percentage. Star a listing and it becomes a favourite with a target price and a note; when its price falls below the last price already alerted, a message goes to Discord, once per new low. Hide the ones you've ruled out, save a search, come back tomorrow.

Uma visão própria lista todo imóvel ativo cujo preço acabou de mudar, com o percentual. Marque um anúncio com estrela e ele vira favorito, com preço-alvo e uma anotação; quando o preço cai abaixo do último já alertado, sai uma mensagem no Discord, uma vez a cada nova mínima. Esconda os que você já descartou, salve uma busca, volte amanhã.

Price drops tab: a grid of listing cards, each with the percentage change badge, the date it changed, area and bedrooms.
Price drops, newest first. Photos blurred and prices masked here; these are other people's homes.
Quedas de preço, das mais recentes para as mais antigas. Aqui, fotos borradas e preços mascarados; são casas de outras pessoas.

Details that mattered

Detalhes que fizeram diferença

  • Neighbourhood names normalised (accents, case, stray spaces), because “Tatuapé”, “Tatuape” and “Tatuapé ” were three neighbourhoods and the filter silently missed two of them.
  • Sale and rent share one model, keyed by listing and kind, so the same home can be followed in both markets.
  • Favourites enriched daily with extra detail the list endpoint doesn't return.
  • Nomes de bairro normalizados (acentos, maiúsculas, espaços sobrando), porque “Tatuapé”, “Tatuape” e “Tatuapé ” eram três bairros e o filtro deixava dois de fora sem avisar.
  • Venda e aluguel dividem um só modelo, com chave por anúncio e tipo, para dar para acompanhar o mesmo imóvel nos dois mercados.
  • Favoritos enriquecidos todo dia com detalhes extras que o endpoint de listagem não traz.

How it relates to the home decision engine

Como ele se liga ao motor de decisão de moradia

This is the wide lens: the whole city, every day. The home decision engine is the close-up: one candidate home, its plan, its street and its financing. The radar finds the candidates; the engine decides between them.

Esta é a lente aberta: a cidade inteira, todo dia. O motor de decisão de moradia é o close: um imóvel candidato, sua planta, sua rua e seu financiamento. O radar encontra os candidatos; o motor decide entre eles.

Privacy: the screens are real but anonymised. Listing photos are blurred, prices masked and street names removed.
Privacidade: as telas são reais, mas anonimizadas. Fotos dos anúncios borradas, preços mascarados e nomes de rua removidos.

Want to talk about this? I'm happy to walk through how it was built.Quer conversar sobre isso? Mostro com prazer como foi construído.