Skip to content
Ricardo Brossard
Projects

Tasador

A property valuation report in one or two minutes, with every price justified.

For a real estate agency in Buenos AiresTested with real data, code published

Tasador's data sources screen: discovered and new listings from each source, the latest ingestion run and the coverage by neighborhood with the corpus's average USD/m²
per valuation report, against an hour or more of manual searching
1 to 2 min
agent steps in LangGraph, from the address to the PDF
11
nDCG@25 for lexical search, against 0.747 for the baseline, on 113 queries
0.821
of made-up figures get past the text check (down from 50.5%)
4.3%
backend tests and Playwright end-to-end tests, in CI
492 + 55
An apartment in Palermo is entered, the agent moves forward step by step and the report comes out with the suggested price, the confidence level and the listings that back it; then it answers a question with its verified citations.An apartment in Palermo is entered, the agent moves forward step by step and the report comes out with the suggested price, the confidence level and the listings that back it; then it answers a question with its verified citations.An apartment in Palermo is entered, the agent moves forward step by step and the report comes out with the suggested price, the confidence level and the listings that back it; then it answers a question with its verified citations.An apartment in Palermo is entered, the agent moves forward step by step and the report comes out with the suggested price, the confidence level and the listings that back it; then it answers a question with its verified citations.
From the address to the report with its evidence, and every answer with its citation.

The problem

To decide what price to list a property at, the real estate agent compares it by hand against similar listings: they search the portals, note them in a spreadsheet and adjust by eye. It is skilled, repetitive work, and two valuations of the same property can end up at different prices with nothing written down about why.

The owner hears a number verbally, with no document to back it. For a real estate agency in Buenos Aires, that number decides whether it wins the listing.

What I built

A system that puts together the valuation report in one or two minutes, with the comparable listings that justify the price. The agent enters the address and whatever details they have, the screen shows the progress step by step, and at the end there is a report on the web and as a PDF with the price, the range, the confidence level and the table of comparables: the ones used and the ones discarded, each with its reason.

The rule that orders everything: the AI handles language and judgment; math handles the numbers.

  • Search. Over a corpus of 93,544 listings, a lexical search in Spanish brings up the active candidates in the neighborhood that most resemble the property.
  • Reading. An 11-step agent in LangGraph reads each listing, extracts what moves the price (condition, orientation, floor, elevator, age), detects the same property listed several times and discards whatever does not compare, such as off-plan units or swaps.
  • Verification. Every fact the AI takes from a listing comes with its exact quote, and a function with no AI checks that the quote is in the original listing.
  • Price. It is calculated by an explicit formula, never by the model: a robust median of USD/m² with written adjustment coefficients. With fewer than 5 valid comparables, the system says it does not know.
  • Writing. A model explains the result in plain language, and a check reviews the text before it is delivered.

On top of the finished report there is a second RAG, “Ask the report”: the owner asks why a listing was discarded and gets an answer with verified citations, or a refusal if there is no evidence.

Around it there is a full application: a Next.js 15 frontend with React 19, sessions and roles, user management, a corpus explorer with each listing next to what the system extracted, and the health of each data source. Each report costs between 2 and 5 US cents, with the cost traced per step.

The hardest part

Measuring the RAG before switching it on, and having the simplest option win. I built the full RAG: listing chunking, local embeddings in pgvector, lexical search, fusion with Reciprocal Rank Fusion and a reranker. Before measuring, I wrote down the criteria for switching each piece on, and then I built the yardstick: 113 queries, with nDCG@25, recall, MRR and bpref, and paired bootstrap intervals against the baseline.

Lexical search on its own won: nDCG@25 of 0.821 against 0.747 for the baseline, ahead of the hybrids. It is the one switched on. The dense embeddings and the reranker are built, measured and switched off, one configuration setting away.

Making sure no figure in the text strays from the data. The model writes, and before delivery a two-phase check reviews what it wrote. The first phase uses no AI: it extracts every number from the prose and looks for it in the report’s data, and a single figure that does not appear rejects the text. The second is an adversarial model that looks for unsupported claims. If the text is rejected three times, the report goes out with the table and the range, which come from the formula, and without prose.

I audited it by injecting made-up prices into real reports: at first 50.5% got through; after three measured fixes, 4.3%.

How it is tested

  • 492 backend tests with pytest, including architecture tests that prevent assigning a model to the step that calculates the price or loosening the minimum number of comparables.
  • 55 end-to-end tests with Playwright against the real stack, with no mocks, on desktop and on mobile. They check product decisions: that a report without enough data does not read as an error, that every field has its label, that contrast meets AA in both themes.
  • CI on GitHub Actions on every change: lint, types, migrations from scratch, isolation between organizations, secret scanning and scanning of the Docker images.
  • Real market data: the system runs on the real corpus of 93,544 Buenos Aires listings. The repository also includes a synthetic corpus to bring up the whole system with Docker Compose from an empty database.

How it works

  1. 01Valuation requestaddress and details
  2. 02Listings corpus93,544 listings
  3. 02Lexical searchsimilar listings
  4. 03The agent readspicks comparables
  5. 04Checks each citationchecked without AI
  6. 05Price formulamedian USD/m²
  7. 06Writingthe model explains
  8. 07Figure checkevery figure in data
  9. 08Report and PDFwith comparables
  10. 09Ask the reportverified citations

Results

  • A formula calculates the price

    A robust median of USD/m² with written adjustment coefficients. An architecture test prevents assigning a model to that step.

  • Every fact, with its citation

    Whatever the AI extracts from a listing comes with the exact passage that backs it, and a function with no AI checks that it is in the original.

  • It knows when to say it does not know

    With fewer than 5 valid comparables it gives no price and shows why each candidate was dropped.

  • Ask the report

    A second RAG answers with verified citations, or declines in milliseconds if there is no evidence.

  • 2 to 5 US cents per report

    The cost is traced per step and per model call.

  • 93,544 listings in the corpus

    Idempotent ingestion by hash, cascading deduplication and the same property counted only once.

Stack

Application
Next.js 15React 19TypeScriptPython 3.12FastAPIPydantic v2
AI and search
LangGraphLiteLLMLexical search in PostgreSQLpgvectorfastembedReciprocal Rank Fusion
Data
PostgreSQL 16RedisSQLAlchemy 2Alembicpg_trgm
Quality and deployment
pytestPlaywrightmypyGitHub ActionsDocker Compose
A report in progress: the screen shows the agent moving forward step by step, from normalizing the address to generating the PDF, with the time each finished step took