Skip to content

Properties & Rental History

Persistent property entities separate from listings — a physical unit that exists whether or not it's currently for rent.

Problem

Today, "property" and "listing" are the same thing. All physical attributes (address, bedrooms, parking) and temporal attributes (price, availability, lease type) live on one listings row. This creates several issues:

  • Re-listing friction — when a landlord wants to re-list the same apartment, they create a new listing from scratch. Address, bedrooms, photos — all re-entered.
  • Lost history — when a listing is archived, there's no record that this unit was ever for rent. Price history, tenant turnover, time-on-market — all gone.
  • No portfolio view — landlords can only see active/draft/archived listings. There's no view of "my properties" including ones not currently listed.
  • Scraper duplicates — the same apartment appears as separate, unrelated listings each time it's scraped across weeks or platforms.
  • No property database — we can't build a comprehensive database of Montreal rental units because everything is tied to transient listings.

Entity Hierarchy

Building (optional, groups units at same address)
  └── Property (persistent physical unit — apartment, condo, house)
       └── Listing (time-bound rental offer, 0 or many per property)

A property is a specific rentable unit: "Apt 4B at 123 Rue Saint-Denis". It persists regardless of whether it's currently listed. A listing is a rental advertisement: "Spacious 3½ in Plateau, $1,400/mo, available July 1st". Listings come and go. A property can have many listings over time, but typically only one active listing at a time.

What Belongs Where

Property (persistent)Listing (temporal)
Address, unit #, coordinatesTitle, description
NeighborhoodPrice per month
Property type (apartment/condo/house)Lease type, available date
Bedrooms, bathrooms, areaContact email/phone
Building features (parking, laundry)Status (draft/active/rented/archived)
Pet policy, smoking policyPublished/archived dates
Landlord ownershipRental tracking (tenant, actual rent, lease dates)
Property-level images (the unit itself)Listing-specific marketing images
Notes (CRM, persistent across listings)Inquiries (per listing campaign)

Rather than a big-bang refactor that touches every file, introduce properties incrementally in 4 steps.

Step 1: Thin properties table + FK on listings

Create a minimal properties table:

FieldTypeDescription
iduuidPrimary key
addressStreetstringStreet address
addressUnitstringUnit/apartment number
addressCitystringCity
addressPostalCodestringPostal code
latitudedecimalGeolocation
longitudedecimalGeolocation
neighborhoodIduuid FKLinked neighborhood
propertyTypeenumapartment, condo, house, etc.
bedroomsintegerNumber of bedrooms
bathroomsintegerNumber of bathrooms
areaSqFtintegerSquare footage
landlordIduuid FKOwner (user or scraper system user)
createdAttimestampWhen the property was first seen

Add property_id (nullable FK) to listings.

Migration: backfill properties from existing listings — group by address + unit hash, create one property per unique unit, link listings back.

Step 2: Landlord Portfolio View

New dashboard section: "My Properties"

  • Lists all properties, including ones with no active listing
  • Each property shows: address, unit, bedrooms, current listing status (or "Not listed")
  • Property detail page: listing history timeline, notes, current status
  • "Re-list" action: create new listing pre-filled from property data + previous listing details
  • Notes move from listing-level to property-level (persistent across listing cycles)

Step 3: Scraper Integration

When importing scraped listings:

  1. Normalize the address (strip accents, standardize street types, etc.)
  2. Match against existing properties by normalized address + unit
  3. If match found: link new listing to existing property
  4. If no match: create new property, then link

This enables:

  • Detecting when a previously-seen unit comes back on the market
  • Cross-platform deduplication at the property level (Phase 13)
  • Building a growing database of Montreal rental units

Step 4: Move Persistent Fields Up

Gradually migrate physical attributes from listings to properties:

  • New listings inherit defaults from their property (bedrooms, parking, etc.)
  • Listings can still override property-level attributes (e.g., after renovation)
  • Images split: property gallery (the unit) vs. listing photos (staging/marketing)
  • Reduces data duplication across listing cycles

Relationship to Other Phases

Building Profiles (Phase 11)

Properties are the foundation for Building Profiles. A building groups properties at the same street address (minus unit #). Building-level amenities (gym, pool, doorman) apply to all properties within. Building Profiles should be built on top of the property layer, not before it.

Market Analytics (Phase 12)

Property-level history directly enables:

  • Price history per unit (not just per listing)
  • Turnover rate tracking (how often does this unit go back on market?)
  • Days-to-rent analysis per property
  • Neighborhood price trends based on stable property data

Multi-Scraper Dedup (Phase 13)

Property matching is the natural deduplication layer. Instead of comparing listing text across platforms, match at the property level: same address + unit = same property, regardless of which platform it was scraped from.

Open Design Decisions

These need to be resolved during implementation:

  1. Address normalization — Montreal addresses vary widely (123 Rue St-Denis vs 123 Saint-Denis St vs 123 rue Saint-Denis). Strategy: normalize on insert + fuzzy matching by lat/lng proximity + unit number.

  2. Ownership — Can a property change owners? (e.g., property manager changes). Probably yes — support ownership transfer.

  3. Scraped properties — Initially owned by the scraper system user. Claimable by landlords (extends the existing claims system).

  4. Notes migration — Move notes from listing-level to property-level? Probably yes — notes are about the unit, not the listing campaign. But keep inquiry notes at listing level.

  5. Image ownership — Property images (the physical unit) vs. listing images (marketing photos for this campaign). How to split? Start simple: all images stay on listings, add property-level gallery later.

  6. One active listing rule — Should we enforce max one active listing per property? Probably yes, with a warning UI.