Build a project

Build a Property Research Dashboard with Python

Build a local dashboard for a property shortlist using an API snapshot. Compare returned addresses, listing prices, estimates, errors, and collection times.

By PropertiesAPI5 min read

A dashboard for the properties you are researching

A spreadsheet is convenient for export, but a small dashboard makes it easier to inspect a property shortlist without hiding errors or confusing estimates with asking prices. This project displays the properties you supplied, their returned identities, available fields, and collection times in a filterable table.

The finished application runs locally in your browser. A separate enrichment script fetches the data; the dashboard reads its saved snapshot. Page views and filters make no API requests, and the dashboard never needs your API key. This keeps data collection separate from display and makes the example useful for inspecting a completed research run.

Create the property snapshot first

Follow the address enrichment guide to produce output/properties.json. That file retains the requested address, returned address, listing status, price, estimates, and metadata for every row. Failed lookups stay in the snapshot rather than disappearing from the comparison.

Check the input list before collecting data. This application supports a known shortlist, not a map search or a feed of every home in an area. A dashboard can display only the properties you requested and the fields the source returned.

Start the local server

Save property_dashboard.py in the same directory as your output folder. It uses the Python standard library, so it adds no packages beyond those needed by the enrichment script. Start it with the snapshot path and open the printed local URL in a browser.

The server binds to the loopback interface. Keep it local: this example does not include user authentication or tenant isolation for a shared website. Stop it from the terminal when you finish reviewing the data.

Run the dashboard
python property_dashboard.py output/properties.json
# Open http://127.0.0.1:8080 in your browser.
# Press Ctrl+C in the terminal to stop.

Read the table in the right order

Start with the submitted row count and review flags. Then compare requested and returned addresses, including unit numbers. A successful HTTP response does not mean every field exists, and the absence of a warning does not remove the need to check the property you intend to analyze.

Filter by listing status before interpreting prices. FOR_RENT rows carry monthly asking rent; FOR_SALE rows carry listing prices. The dashboard deliberately does not add these together or display a mixed average. Currency remains a separate visible column.

Unavailable values are labeled explicitly. The renderer preserves a numeric zero when it is actually present, rather than treating every false-like value as missing. Zestimate and rent estimate columns remain separate because they measure different concepts.

QuestionColumns to inspect
Did I receive the intended property?Requested address, returned address, and unit.
What does the price represent?Listing status and currency.
How old is this observation?Collected at and cached response.
What needs intervention?Lookup status, review flag, and error.

Render property text as text

Addresses and other returned strings are data, not HTML. The program escapes them before rendering. It also avoids converting returned values into links or executable markup. The display helper below is used for table cells and the export timestamp.

The server serves only its dashboard route. It does not expose the working directory or provide an endpoint for fetching arbitrary URLs. The local snapshot stays the only input to the renderer.

Safe cell rendering from the downloadable program
from html import escape

def cell(value):
    if value is None:
        return "Unavailable"
    if isinstance(value, bool):
        return "Yes" if value else "No"
    return escape(str(value))

Refresh the data deliberately

Reloading the page rereads the local JSON file. It does not fetch the properties again. To collect another snapshot, rerun the enrichment script into a new output directory, review its results, and start the dashboard against that file.

The export timestamp describes when your program wrote the file. fetched_at describes the underlying collection time for each response. A cache hit can return an earlier observation even when the export itself is new. Preserve both times if you later compare snapshots.

If the snapshot is missing or malformed, the server returns an explicit unavailable response. It does not show an empty table that could be mistaken for a successful research run.

Choose the next useful extension

For rental research, use the rent-comparison script on the same snapshot and review unit and lease differences before ranking results. For an internal tool, the next additions might be notes, a manually reviewed identity flag, and a record of why each property is on the shortlist.

For a hosted product, replace the local file with authorized server-side storage, add authentication and access controls, and schedule collection separately from page rendering. Keep API keys out of browser code. Decide what each user is permitted to view before sharing property snapshots.

Validation scope: local tests cover escaped HTML, missing data, listing-status filters, unavailable files, and unknown routes. Browser checks cover desktop and narrow layouts. The API request contract is checked separately in the enrichment guide; this dashboard is not a measured property-coverage report.

Sources and documentation

Product details checked against the linked documentation on October 21, 2026. Code and sample inputs are illustrative unless stated otherwise.

Written by PropertiesAPI, an independent property data service. We are not affiliated with Zillow. Source data may be incomplete or change over time.