Skip to content

F-024: Rent Price Rating

Status: Proposed · Priority: P1 · Updated: Mar 6, 2026

Summary

Add an AutoTrader-style price rating badge to every listing, showing whether the rent is a great deal, fair, or overpriced compared to similar listings in the same area. Inspired by AutoTrader's "Great Price / Good Price / Fair / High" system for cars.

How AutoTrader Does It

AutoTrader compares each car's asking price against market value for the same make/model/year/mileage using millions of data points. They show a color-coded badge:

  • Great (green) — significantly below market
  • Good (light green) — slightly below market
  • Fair (yellow) — at market price
  • High (orange) — above market
  • Overpriced (red) — significantly above market

Proposed Rating System for Rentals

Comparison Dimensions

Compare each listing's pricePerMonth against the median rent for:

  1. Same neighborhood (primary)
  2. Same bedroom count (0=studio, 1, 2, 3, 4+)
  3. Optionally: same property type (apartment vs condo vs house)

Rating Tiers

RatingBadge ColorCriteria
Great DealGreen> 15% below median
Good PriceTeal/Light Green5–15% below median
Fair PriceGray/NeutralWithin 5% of median
Above AverageAmber/Orange5–15% above median
High PriceRed> 15% above median

Data Sources (Already Available)

We already compute these stats — no new data collection needed:

  1. Live neighborhood statsGET /neighborhoods/:slug returns stats.byBedroom[].avgPrice, minPrice, maxPrice per bedroom count
  2. Neighborhood stats snapshotsneighborhood_stats_snapshots table has daily per-neighborhood, per-bedroom price stats
  3. All active listings — can compute median/percentiles directly from listings table
  4. CMHC rent trendsincome_snapshots has historical market rents by CMA (broader context)

Median vs Average

Use median, not average. A few luxury listings can skew the average upward, making most listings appear "below average." Median is more representative of the typical rent.

Requirements

  • [ ] API: Compute price rating for each listing (on detail endpoint and/or listing list)
  • [ ] API: Return priceRating field with tier name + percentage difference
  • [ ] Frontend: Badge on listing cards (browse page, neighborhood page, similar listings)
  • [ ] Frontend: Badge + explanation on listing detail page ("15% below median for 2BR in Mile End")
  • [ ] Frontend: Bilingual labels (FR/EN)
  • [ ] Consider: Should we show the median rent amount? ("Median 2BR in Mile End: $1,450/mo")

Design Considerations

Where to compute?

Option A: API-side — Compute rating when returning listing data. Pro: consistent, one source of truth. Con: needs neighborhood stats loaded per request.

Option B: Precomputed column — Add price_rating column to listings, recompute daily via cron. Pro: fast reads, no per-request computation. Con: can go stale.

Option C: Frontend-side — Fetch neighborhood stats, compute client-side. Pro: no API changes. Con: extra data fetching, inconsistent if stats differ.

Edge cases

  • New neighborhoods with < 5 listings — not enough data for meaningful comparison → don't show badge
  • Studio vs 1BR overlap — some "studios" are really 1BR → may skew stats
  • Listings with $0 or absurdly high prices (data quality) → exclude outliers from median calc
  • Furnished vs unfurnished — furnished is legitimately more expensive → should we flag this?

Decisions

  1. Where to show — Both listing cards (small badge) AND detail page (badge + explanation text)
  2. Data window — Use up to 1 year of data, including archived listings. More stable median, not skewed by seasonal fluctuations or a few current outliers.
  3. Minimum sample — Hide badge if fewer than 3 listings (active + archived, last 12 months) for that bedroom/neighborhood combo
  4. Property type — Don't factor in (keeps sample sizes large). But if areaSqFt is available on both the listing and enough comparables, use price-per-sqft as a secondary signal.

Algorithm Notes

Percentile-based approach (preferred over percentage-from-median)

Instead of "15% below median," use percentile rank: "cheaper than 85% of similar rentals." Benefits:

  • More intuitive for users
  • Handles skewed distributions better (luxury outliers don't distort ratings)
  • Maps cleanly to rating tiers:
RatingPercentileMeaning
Great Deal< 20thCheaper than 80%+ of comparables
Good Price20th–40thBelow average
Fair Price40th–60thMiddle of the market
Above Average60th–80thMore expensive than average
High Price> 80thTop 20% most expensive

Precomputed medians (nightly job)

Don't compute per-request. Nightly cron job:

  • Query all listings (active + archived, last 12 months) grouped by (neighborhood_id, bedrooms)
  • Compute median, p20, p40, p60, p80, count
  • Store in a rent_benchmarks lookup table
  • API does a cheap join to get the rating

Badge wording — neutral tone

Avoid judgmental language ("Overpriced") that could alienate landlords. Use:

  • Positive tiers: enthusiastic ("Great Deal!")
  • Negative tiers: factual ("Above Average", "High Price")
  • Never "Overpriced" or "Bad Deal"

Additional Features

Sort by Best Deal

Price rating enables a new sort option: "Sort by: Best Deal" — sort listings by percentile rank. Users see the best-value listings first.

Price Distribution Histogram (detail page)

Small inline recharts chart on the listing detail page showing the price distribution for that bedroom count in that neighborhood. An arrow/line marks where this listing falls. Very visual, very convincing. We already have recharts.

Price-per-sqft secondary signal

When areaSqFt is available on both the listing and enough comparables, show a bonus insight: "This listing is $2.10/sqft — average for 2BR in Mile End is $1.85/sqft." Separate from the main badge, shown as additional context on the detail page.

Price Drop badge

Since we track listing updates (F-017), we know when prices change. "Price dropped $150" is a powerful signal — complementary to the market comparison badge. Show on both cards and detail page when a price decrease was detected.

Requirements (updated)

  • [x] DB: New rent_benchmarks table (neighborhood_id, bedrooms, median, p20, p40, p60, p80, sample_count, computed_at)
  • [x] DB: New listing_price_history table (listing_id, old_price, new_price, changed_at)
  • [x] DB: Nightly job to compute benchmarks from 12-month active+archived listings (3am daily)
  • [x] Scraper: Record price changes in importer when price diffs detected
  • [x] API: Return priceRating on listing detail and listing list endpoints (tier, percentile, median)
  • [x] API: Return benchmark details (p20, p80, sampleCount) on detail endpoint
  • [x] API: Return price-per-sqft and avg comparison on detail endpoint
  • [x] API: Return latest price drop (priceChange) on list and detail endpoints
  • [x] API: Support sort=bestDeal on listing list endpoint
  • [x] API: Support priceDrops=true filter on listing list endpoint
  • [x] Frontend: Colored rating badge on listing cards (great deal=green, good=teal, above avg=amber, high=red)
  • [x] Frontend: Rating badge + percentile explanation on detail page
  • [x] Frontend: Price drop badge on cards and detail page
  • [x] Frontend: Price insights section (median rent, price-per-sqft comparison)
  • [ ] Frontend: Price distribution histogram on detail page (recharts) — deferred
  • [x] Frontend: "Price Drops" filter toggle in filters
  • [x] Frontend: "Sort by: Best Deal" option in filters
  • [x] Frontend: Bilingual labels (FR/EN)
  • [x] Min 3 comparables (12-month active+archived) to show badge, otherwise hide

Discussion Notes

Mar 6, 2026

Initial proposal. Inspired by AutoTrader UK's price indicator system. Key advantage: we already have all the data needed — neighborhood stats by bedroom count are computed on every neighborhood detail page.

Decisions: show on both cards + detail, use 12-month active+archived data, min 3 comparables, no property type filter but use $/sqft when available. Percentile-based approach preferred. Added: sort by best deal, price histogram, price drop badge, precomputed benchmarks table, neutral badge wording.