Appearance
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:
- Same neighborhood (primary)
- Same bedroom count (0=studio, 1, 2, 3, 4+)
- Optionally: same property type (apartment vs condo vs house)
Rating Tiers
| Rating | Badge Color | Criteria |
|---|---|---|
| Great Deal | Green | > 15% below median |
| Good Price | Teal/Light Green | 5–15% below median |
| Fair Price | Gray/Neutral | Within 5% of median |
| Above Average | Amber/Orange | 5–15% above median |
| High Price | Red | > 15% above median |
Data Sources (Already Available)
We already compute these stats — no new data collection needed:
- Live neighborhood stats —
GET /neighborhoods/:slugreturnsstats.byBedroom[].avgPrice,minPrice,maxPriceper bedroom count - Neighborhood stats snapshots —
neighborhood_stats_snapshotstable has daily per-neighborhood, per-bedroom price stats - All active listings — can compute median/percentiles directly from
listingstable - CMHC rent trends —
income_snapshotshas 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
priceRatingfield 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
- Where to show — Both listing cards (small badge) AND detail page (badge + explanation text)
- 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.
- Minimum sample — Hide badge if fewer than 3 listings (active + archived, last 12 months) for that bedroom/neighborhood combo
- Property type — Don't factor in (keeps sample sizes large). But if
areaSqFtis 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:
| Rating | Percentile | Meaning |
|---|---|---|
| Great Deal | < 20th | Cheaper than 80%+ of comparables |
| Good Price | 20th–40th | Below average |
| Fair Price | 40th–60th | Middle of the market |
| Above Average | 60th–80th | More expensive than average |
| High Price | > 80th | Top 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_benchmarkslookup 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_benchmarkstable (neighborhood_id, bedrooms, median, p20, p40, p60, p80, sample_count, computed_at) - [x] DB: New
listing_price_historytable (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
priceRatingon 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=bestDealon listing list endpoint - [x] API: Support
priceDrops=truefilter 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.