Transit
Default 20% weight
How well-connected the listing is, scored two ways: how easy it is to walk to a stop, and how fast you can actually get places from there.
Walk-to-stop (60% of the score). Every TTC and GO stop within a real 1.2 km walking distance (along the street network, not as the crow flies) contributes to a pool, weighted by service quality — a subway station counts most, then streetcars, GO rail, frequent buses (10-min peak headway), and regular buses last. Closer wins; each extra ~350 m of walking roughly halves a stop's contribution.
Destination reachability (40% of the score). From each address we precompute the morning-peak transit time to every TTC subway station and every in-Toronto GO rail station — 91 places in total — using a real trip planner over TTC and GO schedules. The score uses your five fastest destinations. So a downtown listing isn't punished for being far from Pioneer Village or Pearson, and a North York listing still gets credit for the destinations it can reach quickly.
Feeds
TTC GTFS · GO Transit GTFS · OpenStreetMap (walking network) · OpenTripPlanner (transit routing)
Show the math
walk-to-stop = 100 × (1 − exp(−pool / 5))
where pool = Σ mode_w × exp(−2.0 × d_km) over stops within 1.2 km of walking. Distance comes from OSRM walking-network routing, converted to a km-equivalent at 5 km/h. The soft-cap divisor of 5 means a typical downtown listing with one nearby subway + a streetcar (pool ≈ 5) lands near 63, and two subway lines plus a streetcar (pool ≈ 10) approaches 86.
Mode weights:
- subway = 3.0
- tram / streetcar / LRT = 1.5
- GO rail = 0.8
- frequent bus (≤10 min peak) = 0.5
- regular bus = 0.2
destinations = 100 × mean(exp(−0.02 × t_min)) over the five fastest of 91 destinations. Decay calibration: t = 5 min contributes ~0.90, 20 min ~0.67, 35 min ~0.50, 60 min ~0.30.
Transit = 0.6 × walk-to-stop + 0.4 × destinations
If transit-time data isn't available for an address (outside our Toronto grid), the score falls back to walk-to-stop only — no penalty for the missing term.
Grocery
Default 11% weight
Whether you can shop your whole list without driving.
We sum up nearby supermarkets, weighted by both how close they are and what kind of store they are. A full-service supermarket (e.g., Loblaws, Metro, Sobeys) counts the most; a discount grocer (No Frills, FreshCo, Food Basics) is most of the way there; an independent grocer contributes less. Multiple stores stack but with diminishing returns, so your second nearby Loblaws boosts your NestScore less than your first.
Feeds
OpenStreetMap (via Overpass API)
Show the math
Grocery = min(100, sqrt(Σ tier × exp(−2.5 × d_km)) × 40)
Tier weights:
- full-service supermarket = 1.0
- discount supermarket = 0.6
- independent grocer = 0.3
Stores beyond 1.5 km are ignored. Calibration: a single store at 300 m ≈ 35; two at 300 m ≈ 50.
Medical
Default 7% weight
Pharmacies and walk-in clinics within walking range.
Same mathematical shape as Grocery, but for pharmacies and walk-in clinics. Pharmacies count more than clinics, because most people use them more often (prescriptions, OTC meds, allergy season). Multiple options stack with diminishing returns; anything past 1.5 km doesn't count.
Feeds
OpenStreetMap (pharmacy / clinic / doctors tags via Overpass)
Show the math
pool = Σ tier × exp(−2.5 × d_km) over locations within 1.5 km
Medical = 100 × (1 − exp(−pool / 4))
Tier weights:
- pharmacy = 1.0
- walk-in clinic / doctor's office = 0.7
Locations beyond 1.5 km are ignored. Same soft-cap shape as Transit: a pool of 4 lands near 63, a pool of 8 near 86. The smaller saturation scale (4 vs Transit's 10) reflects that pharmacies are rarer than transit stops, so even a single nearby pharmacy moves the score meaningfully.
Restaurants
Default 7% weight
Whether the streets feel alive.
This one's about density — how many places to eat, drink, and grab a coffee are within walking distance, because that's what makes a neighbourhood feel like one. We pool every restaurant, café, bar, and fast-food spot within 1 km, weighted by what kind of place it is: a sit-down restaurant counts the most, a café somewhere in between, a McDonald's the least. Closer wins — each extra ~230 m of distance roughly halves a place's contribution. The total feeds into a saturating curve that approaches 100, so going from 0 to 10 nearby places matters way more than going from 40 to 50.
Feeds
OpenStreetMap (restaurant / cafe / bar / fast_food tags)
Show the math
pool = Σ tier × exp(−3.0 × d_km) over places within 1 km
Restaurants = 100 × (1 − exp(−pool / 22))
Tier weights:
- restaurant (sit-down) = 1.0
- café = 0.6
- bar = 0.4
- fast food = 0.3
Same soft-cap shape as Transit and Medical — pool of 22 lands near 63, pool of 44 near 86. Tiering keeps every downtown block from tying at 100 (fast-food chains used to be a third of all OSM hits in Toronto, and the untiered count buried real signal).
Cycling infrastructure: stations + lanes.
Two halves, each worth up to 50 points. The first half rewards Bike Share Toronto stations nearby — close ones (≤500 m) count three times as much as further ones (500 m – 1 km). The second half rewards protected lane density, or how many sample points along Toronto's official cycling network are within 500 m of your new home. We sample lanes every ~200 m, so a long protected stretch nearby will accumulate a lot of points.
Feeds
Toronto Bike Share station feed · City of Toronto cycling network GeoJSON
Show the math
share_score = min(50, sqrt(3 × n_close + 1 × n_far) × 14)
(close = ≤500 m; far = 500 m – 1 km)
lane_score = min(50, sqrt(n_lane_samples_within_500m) × 8)
Bike = share_score + lane_score
Green space within a 10-minute walk. The big ones count more!
We measure the distance to each park's edge, not its centre. That matters: standing across the street from High Park, you're roughly 600 metres from its centroid but effectively at the front gate. Bigger parks also get a bonus — a 50 hectare ravine system contributes 3× more than a small playground at the same distance, because we know the experience is meaningfully different. Multiple parks stack with diminishing returns; anything past an 800-metre walk to the edge stops counting.
Feeds
Toronto Open Data Green Spaces | OpenStreetMap parks
Show the math
For each park, treat it as a disc with radius_km = sqrt(hectares × 10,000 / π) / 1000 and let edge_d = max(0, centroid_d − radius_km).
Parks = min(100, sqrt(Σ exp(−2 × edge_d) × size_mult) × 30)
Size multiplier:
- ≥ 50 hectares (High Park / Tommy Thompson scale) = 3.0
- 5 – 50 hectares (neighbourhood park) = 1.8
- < 5 hectares (small park / playground) = 1.0
Parks with edge_d > 0.8 km are ignored.
Schools
Default 1% weight
Nearest elementary plus nearest secondary: best-of-each, no stacking.
For schools we use a best-of-each rule: only the single nearest elementary school (worth up to 60 points) and the single nearest secondary school (worth up to 40 points) contribute. We don't reward the listing for being near five elementary schools since you only need one. The closer the school, the more points; anything past 1.5 kilometres doesn't count.
Feeds
Ontario Ministry of Education school list
Show the math
elem_score = 60 × exp(−1 × d_nearest_elem) (or 0 if none within 1.5 km)
sec_score = 40 × exp(−1 × d_nearest_sec) (or 0 if none within 1.5 km)
Schools = elem_score + sec_score
Calibration: elem 100 m + sec 300 m ≈ 71; elem 500 m + no nearby sec ≈ 50.
Square footage
Default 7% weight
Spaciousness compared to other Toronto units of the same bedroom count.
We compare the unit's reported sqft to a typical citywide range for that bedroom count. A 1-bed at the top of the typical range scores around 90; a 1-bed near the bottom scores around 20. If the listing doesn't report sqft, we estimate it from the median sqft of comparable listings in our database, so the score is still meaningful if not precise.
Feeds
Listing's own sqft + bedrooms fields (with peer-median fallback)
Show the math
Sqft = clamp(0, 100, 20 + (sqft − low) / (high − low) × 70)
So at the bottom of the range you get 20, at the top you get 90, and we extrapolate beyond. Bedroom-count benchmarks (square feet, low – high):
- studio = 300 – 550
- 1 bed = 450 – 750
- 1 bed + den = 550 – 850
- 2 bed = 700 – 1,100
- 2 bed + den = 850 – 1,300
- 3 bed = 1,000 – 1,600
If sqft is unknown, we substitute the median sqft of listings with the same bedroom count.
Building has a pool: yes or no?
Binary. If we know the building has a pool, the score is 100; if we know it doesn't, the score is 0. If we genuinely can't tell, the pool is left out of the composite rather than counted as zero. We prefer the structured amenity flag when pulling listings and only fall back to scanning the listing description when we have to. Drop this from your own NestScore if it's not important to you.
Feeds
Listing's has_pool flag → text in description (with negation guard)
Show the math
Detection priority:
- Explicit
has_pool flag from the listing — used as-is.
- Legacy
pool field — used as-is if set.
- Description regex
\bpool\b with two guards: a negation window ("no pool", "without a pool", "lacks a pool" → False) and a false-positive filter ("carpool", "pool table", "pool noodle", "pool party", "pool hall" → ignored).
Score: True → 100, False → 0, Unknown → excluded from composite.
Balcony
Default 3% weight
Unit has a balcony: yes or no?
Same shape as Pool. If we know there's a balcony, 100; if we know there isn't, 0; if we can't tell, it's left out. Structured flag first, description text second. The negation guard catches things like "no balcony".
Feeds
Listing's has_balcony flag → text in description
Show the math
Detection priority:
- Explicit
has_balcony flag — used as-is.
- Legacy
balcony field — used as-is.
- Description regex
\bbalcon(?:y|ies)\b with negation window ("no balcony", "without a balcony" → False).
Score: True → 100, False → 0, Unknown → excluded from composite.
An inverse methodology means higher score equals quieter neighbourhood.
This is the only factor where we work backwards. We start at 100 and subtract a noise-exposure penalty based on nearby highway corridors (worst), GO and other rail lines, and above-ground TTC subway stops. Noise drops off very quickly, so we use a steep distance decay. A busy road 500 metres away barely registers, but a building right on the Gardiner takes a serious hit. Anything past 800 metres doesn't contribute.
Feeds
Toronto highway corridors · GO rail · TTC subway lines (curated)
Show the math
exposure = Σ severity × exp(−5 × d_km) over noise sources within 800 m
Quiet = max(0, 100 − min(100, sqrt(exposure) × 30))
Severity by source:
- highway corridor (Gardiner, 401, DVP) = 1.0
- GO / commuter rail = 0.7
- TTC subway (partly or fully above ground) = 0.4
Calibration: quiet residential ≈ 90–95; near the Gardiner at 500 m ≈ 60–70; immediately on the 401 ≈ 20–30.
Price value
Default 10% weight
Rent compared to nearby units of the same size.
Price-value answers a single question: am I overpaying compared to similar nearby units? We find every other listing within 2 kilometres that has the same bedroom count, take the median rent, and compare. 30% below median tops out at 100; at the median scores 50; 30% above the median drops to 0. We need at least 3 nearby comparables, so if we don't have them in the listings database, this score is excluded rather than guessed. As more listings join the database, more areas become scoreable.
Feeds
All other listings in our database (same bedroom count, within 2 km)
Show the math
Let median = median price of same-bedroom listings within 2 km, where there are at least 3 comps.
Price value = clamp(0, 100, 50 − (price / median − 1) × 167)
The 167 multiplier means a ±30% swing from the median maps to the full 0–100 range. With fewer than 3 comparables, this score is left out of the composite.
Rent control
Default 10% weight
Whether the unit falls under Ontario's rent-increase cap.
Ontario exempts buildings first occupied on or after 15 November 2018 from rent control, so whether a unit is rent-controlled depends almost entirely on when its building was finished. If the listing tells us the build year, we use it directly. If it doesn't, we match the listing to the nearest building in our database of Toronto building records and read its rent-control flag from there. If we can't get either, the score is excluded rather than being guessed at.
Feeds
Listing's year_built → proprietary database of Toronto buildings
Show the math
Detection priority:
- If
year_built is set: 100 if year_built < 2018, else 0.
- Otherwise: find the nearest building in
Toronto_Buildings within 100 m and use its rent-control flag.
- If neither is available: excluded from composite.
We use the year 2018 as the cutoff (the formal date is 15 November 2018, but year-level data is what's reliably available).
Traffic
Default 1% weight
Vehicle volume, speed at the curb, and a toggle for which way you want it.
Most renters want calm streets. Some actively want a busy arterial for quick access in their cars. This factor has a direction toggle next to its slider: Less traffic (the default — calm = good) or More traffic (busy = good). The underlying number doesn't change; we just flip the sign at the point your composite is calculated.
We use Toronto's midblock vehicle counts, which are survey points where the city has measured average daily volume (AADT) and the 85th-percentile speed of vehicles passing by. For a listing, we look at every survey point within 200 metres and combine them with a steep distance decay (a count 50 metres away counts way more than one 180 metres away). Volume dominates the severity blend; speed nudges it (a fast residential street feels worse than a slow one with the same volume).
Coverage isn't perfect since the city only counts streets it has surveyed, and small residential laneways often aren't in the dataset. If there's no survey point within 200 metres, we leave the score blank rather than guess, and the factor drops out of that listing's composite.
Feeds
Toronto Open Data — Midblock Vehicle Speed, Volume and Classification Counts (most recent summary, ~14k surveyed segments)
Show the math
Let v = min(1, AADT / 30000) and s = min(1, speed_85th / 80). Speed is null on volume-only counts; in that case severity = v. Otherwise:
severity = 0.7 × v + 0.3 × s
exposure = Σ severity × exp(−8 × d_km) over surveyed points within 200 m
Calm streets = max(0, 100 − exposure × 45)
If you toggle direction to More, the composite uses 100 − Calm streets. The stored value is always the calm-streets number, so toggling doesn't trigger a re-score.
Calibration: residential side streets land near 80–95, collectors 45–60, arterials 20–35, on a major highway 0–10.