Search by taste, not by filters. Bayt reads every Toronto listing the way a buyer would, then finds the homes you would love and says why. Live at https://bayt.andalsolutions.com, open source at https://github.com/shafeeqr2/bayt.

Inspiration

Ask a realtor what they hear most. It is not "3 beds under $900k". It is "I want a home similar to this one."

No search site can answer that sentence. Filters know price, beds and baths. Taste lives in the photos, the lot, the basement with its own door, the kitchen that opens to the yard.

Netflix hit the same wall. Click counts could not say why someone liked a title, so they moved to a language model that reads what each title is about. We did the same for houses: a model reads each home, writes down why 4 different buyers would want it, and we search on that.

What it does

Bayt is real estate search powered by AI, with 2,261 live Toronto listings. Five features, all on the live site.

Taste Match. Open a home and press More like this. Bayt returns 8 homes in the same price band within 5 km, ranked for the buyer you are, with 1 reason each: "Finished basement apartment with separate entrance, two rentable doors, 8.2% gross yield." Switch buyer and the list and the reasons change. Type Like this, but a bigger backyard and a garage and the search moves with you. Five facet chips choose what counts.

Taste Profile. Save and dismiss homes as you browse. After 5 actions Nemotron writes a one-line summary of your taste, guesses your buyer type, and makes it your default when its confidence is 0.7 or higher. No login, one cookie.

Build Check. Describe a plan in words: "keep the house and add a second storey." Bayt checks it against Toronto Zoning By-law 569-2013 for that exact lot, lists the public route for each shortfall with its risk, and Nemotron writes the answer with citations. Every citation is verified against the analysis; one it cannot back is dropped.

Precedent Check. Every Committee of Adjustment decision since 2001 within 500 m: what neighbours asked for and what the committee decided. Around 39 Rhydwen Avenue that is 222 files, 188 approved, 9 refused. The approval rate counts decided files only:

$$ \text{approval rate} = \frac{\text{approved}}{\text{approved} + \text{refused}} = \frac{188}{197} \approx 95\% $$

The City publishes decisions and descriptions, not variance tables. So this is precedent, not a promise, and the page says so.

Floor Sketch. Listings rarely include a floor plan. Bayt fits the listed rooms into the City's building outline, level by level, and Nemotron adds the rooms the description names but the table leaves out.

Plus lot coverage, height limits and garden-suite potential from City of Toronto open data, a 3D lot view and photo labels.

How we built it

Every model call runs on Nebius Token Factory through its OpenAI-style API, with JSON output validated against pydantic schemas on our side.

Model on Token Factory Job in Bayt Why this model
nvidia/Nemotron-3.5-Super-VL Reads the text, the room list, up to 8 photos and the city data per home; writes the House Profile. Re-ranks the 30 nearest with 1 reason each. Rewrites the pitch for steering. Labels photos. One vision-language model that follows a strict JSON schema and reads 8 photos in about 6 s.
nvidia/nemotron-3-super-120b-a12b Writes the cited Build Check answer. Completes the room list for Floor Sketch. Writes the Taste Profile. A 120B mixture of experts that reasons over long by-law text cheaply, with thinking switchable per call.
Qwen/Qwen3-Embedding-8B Turns every pitch into a 2,048-dimension vector in pgvector. The one embedding model Token Factory serves today: 29 models listed, 1 embedder.

The House Profile holds a fact layer and 4 persona views: a score from 1 to 10 and a pitch in that buyer's words. Taste Match filters by price and distance first, then takes the 30 nearest pitches by cosine distance, the metric pgvector uses for the index:

$$ d(\mathbf{q}, \mathbf{p}) = 1 - \frac{\mathbf{q} \cdot \mathbf{p}}{\lVert \mathbf{q} \rVert \, \lVert \mathbf{p} \rVert} $$

Location and price are hard filters, never part of the vector, so "similar" means similar for the buyer's reasons, not "same street". Nemotron re-ranks the 30 into 8 with a reason each, and the result is cached for 24 hours.

The stack is FastAPI, SQLAlchemy and Alembic on Postgres 17 with PostGIS and pgvector, a Next.js 16 frontend, Docker Compose behind Caddy on one DigitalOcean droplet, and GitHub Actions deploying every push to main. The whole catalogue, 2,261 homes and 9,044 persona pitches, was profiled and embedded in 78 minutes for about $8, about $0.004 per listing.

Challenges we ran into

  • Empty answers. Nemotron on Token Factory reasons by default. Our first structured calls returned empty content with finish_reason: length after spending the whole token budget on hidden reasoning. One request field, chat_template_kwargs.enable_thinking, fixed it, but nothing in the quickstart said so.
  • Schema drift. About 1 call in 100 returned a list field as one comma-separated string in JSON mode. A validator now accepts both shapes.
  • Location leaking into the vector. Early pitches named the street and the neighbourhood, so the nearest neighbours were neighbours. The prompt forbids it and a scrubber enforces it.
  • Floor plans mostly empty. Listings skip room sizes and upper floors are smaller than the footprint. We now draw unsized rooms at a typical size and cut an upper level from the back when its outline is more than 1.3 times the expected area, \( A_{\text{expected}} = A_{\text{interior}} / n_{\text{levels}} \). Median fill went from 57% to 63% and levels under 30% full from 13% to 4%. With Nemotron's room completion, 3 Indianola Dr's main floor went from 74% to 84%.
  • The pool ran dry. Under load the API held database connections while waiting on model calls and exhausted the pool. Model calls now release the connection first.
  • The free tier. We started on a free OpenRouter tier with the same models and hit the daily quota mid-build. Token Factory removed that wall and ran the full backfill in one go.

Accomplishments that we're proud of

  • The same house gets 4 different "similar" lists, and the reasons read like a buyer wrote them.
  • Steering works on what the profiles know: "bigger backyard and a garage" moves the list and the reasons say "Bigger 36 x 125 lot, detached garage".
  • Precedent Check turns 25 years of Committee of Adjustment decisions into something a buyer reads in 10 seconds.
  • 319 backend tests, none of them touching the network. MIT licence. Push to main deploys.

What we learned

  • A model that writes down why makes a recommender you can argue with. Click data cannot do that at our scale.
  • The persona rubrics mattered more than the model. Most of our time went into what a 9 out of 10 means for a flipper.
  • A typed ask can only reach what the profiles describe. "Closer to the subway" needs a transit source first.

What's next for Bayt

  • Transit, schools and walk data in the House Profile, so location asks work.
  • Read the listing's own floor-plan photo with Nemotron VL when one exists.
  • The hand-check gates at full size: 50 profiles read, 100 pair judgments, 10 steering trials.
  • More cities. Everything but the zoning rules is data-driven.

Built by Shareq Rabbani, Shafeeq Rabbani and Khaja Kamil inside the hackathon window, first commit 13 October 2026.

Built With

Share this project:

Updates

Submission history