# Central Oregon Rental Market Report — Method (rental-beacon-v1)

A monthly rental dataset modeled on the Beacon Report (Beacon Appraisal Group's monthly MLS sales report). Published at /insights/central-oregon-rent-report from the latest version of each month (aggregates only, via the public_rent_report_metrics and public_rent_report_bands views).

## Market areas
Derived from the listing's city through the `market_area_cities` table, the only mapping (stored on each listing as `market_area`).

| Market area | Cities | Cadence |
|---|---|---|
| Bend | Bend | monthly |
| Redmond | Redmond, Terrebonne, Eagle Crest | monthly |
| Sisters | Sisters, Tumalo | quarterly |
| Sunriver | Sunriver | quarterly |
| La Pine | La Pine | quarterly |
| Jefferson County | Madras, Culver, Metolius, Warm Springs, Crooked River Ranch | quarterly |
| Crook County | Prineville, Powell Butte | quarterly |

Cities that aren't in the table (for example "Unknown") are left out.

## Eligible listings
A listing is included only when it is not a duplicate (`duplicate_of` is null), `quality_status = 'ok'` (so no suspect or excluded rows), not stale (`availability_status` ≠ 'stale'), and has a listed date, and has no `report_excluded_reason`.

## Segments
- **single_family**: Single Family, Townhouse, Manufactured.
- **multi_family**: Apartment, Condo, Multi-Family.
- **acreage**: any listing with a lot of 1 acre (43,560 sq ft) or more. It is reported as its own segment and taken out of the other two.

Bedroom breakdowns are 0 (studio), 1, 2, 3 and 4+. `bedrooms = -1` means all bedrooms.

## Periods
Monthly areas use calendar months. Quarterly areas use calendar quarters, which are computed in the March, June, September and December reports. Period end means the first moment after the period.

## Metrics (per area, segment, period, bedrooms)
- **Leased count**: listings with `lease_status = 'leased'` and `leased_at` inside the period.
- **Median asking rent at lease**: median of the last asking rent (falling back to price) across leased listings.
- **Median rent per sq ft**: median of rent ÷ square footage across leased listings with at least 200 sq ft.
- **Median days on market**: median across leased listings whose days on market is trusted (verified, derived or observed tier) and between 1 and 365. It uses Zillow's days on market when that number came from the source, otherwise removed date minus listed date.
- **Active at period end**: listed before period end, with no removed date or a removed date at or after period end.
- **Months of inventory**: active at period end ÷ (leases in the 12 months before period end ÷ 12), rounded to the nearest 0.5.
- **List-to-lease ratio**: median of final asking rent ÷ original asking rent across leased listings.
- **New listings**: listed date inside the period.
- **Sample size**: the leased count.
- **Confidence**: verified at 20 or more leased, sparse at 8–19, insufficient below 8. Insufficient cells show no medians and no ratio.

## Rent bands (per area and segment, at the report month)
The bands are: under $1,000; $250 bands from $1,000 to $3,999; and $4,000+. Each band counts:
- listings leased in the report month
- listings leased in the 12 months ending with the report month
- listings active at the end of the month (by current price)

## Versions and freezing
- On the 5th of each month at 07:00 UTC, `freeze_market_report(prior month)` saves version 1 for that month, recording `report_month`, `generated_at` and `method_version`.
- A frozen month is never recomputed. Calling the function again returns the saved version.
- A correction is `freeze_market_report(month, 'note', true)`. It saves a new version, and the note is required.
- `market_report_history` shows the latest version of each month: the last 36 months for monthly areas, and every quarter for quarterly areas.
- Months from Feb 2020 to Sep 2026 were backfilled on 2026-10-15 from the data as it stood that day. Their version note says so.

## Imported RentCast history (import_batch = 'rentcast-history-2026-10')
- Past (inactive) RentCast listings for Bend, Redmond, Terrebonne and Eagle Crest listed on or after 2022-01-01, imported 2026-10-15 by `backfill-rentcast-history`. Tagged with `import_batch` so they can be found or removed.
- An imported listing counts as leased only when its removed date exists, is on or after its listed date, and is on or after 2022-08-13. RentCast stamped its untracked backlog as removed on 2022-08-08 and 2022-08-12, so those are `lease_status = 'unknown'` (`lease_reason = 'import_unreliable_removed_date'`). The under-4-day artifact rule still applies.
- Imported rows without a usable removed date are left out of the report pool, so they never count as active inventory.
- Version 2 of every month from 2022-01 to 2026-09 includes this history; version 1 is unchanged.

## Recovered RentCast history (version 3)
RentCast resets `listedDate` to roughly when it last saw a listing; `raw_data.createdDate` is when it first saw it. For imported rows (`import_batch = 'rentcast-history-2026-10'`) we store that first-seen date as `history_start_date` when it is earlier than `listed_date` (which is never overwritten). An imported row that would otherwise be an under-4-day artifact counts as **leased** (`leased_at = removed_date`, `lease_reason = 'recovered_first_seen'`) when `removed_date` is on or after 2022-08-13 and `removed_date - history_start_date` is 4 to 90 days. These rows count toward leased counts, median rent, rent per sq ft and rent bands, but their days on market is an estimate, so they are left out of median days on market and its sample.

## Days-on-market minimum and Bend ZIP codes (version 5)
- Median days on market is shown only when at least 8 leased rows have a trusted days-on-market value (`dom_sample_size >= 8`), in addition to the 8-lease rule for the cell. The public view applies this to every version.
- Bend ZIP sub-areas `Bend 97701`, `Bend 97702`, `Bend 97703` use the listing's `zip_code`, Bend market area only, and are reported quarterly with the same confidence rules. They have no rent-band rows.
- Seasonality (`public_rent_report_seasonality`) groups leases by calendar month from 2022-08 through the latest frozen month, counting only year-months with 8 or more leases for that area and type. It is a live aggregate of the same pool, not a frozen version.

## Description-based exclusions and Crook County lot size (version 6)
- Landlord descriptions are stored from each listing's own Zillow or Apartments.com page (`description`); RentCast rows have none. The quality check reads them on every insert and update:
  - `furnished` (furnished, not "unfurnished" or "optional/negotiable furnished"), `short_term` (nightly or weekly pricing, month-to-month, vacation rental, or a lease term under 6 months; "converts to month-to-month after the lease" and "month-to-month or 12-month" do not count) and `room_only` (room for rent, shared house, roommates) set `quality_status = 'excluded'`.
  - `senior_or_income_restricted` (55+/62+, senior housing, income limits, tax-credit or other income-restricted housing) sets `quality_status = 'suspect'`.
  - Either way the listing leaves the report pool and the estimator, which use only `quality_status = 'ok'`.
- Stated features are stored but not used yet: `garage`, `fenced_yard`, `pets_allowed`, `utilities_included` (all or some of water/sewer/garbage/electric included), `ac`. Each is true when stated, false when stated as absent, and empty when not mentioned.
- Crook County (Prineville, Powell Butte) single-lot listings without a lot size take it from the county's public tax-lot layer, matched by situs address: `SUM_TOT_ACRES`, else `GIS_ACRES`, converted to square feet (`lot_size_source` records which). A situs address that maps to lots of different sizes is not used. This can move homes into the acreage segment.

## History import limited to months before our own tracking (version 7)
- LivWell's own listing tracking began 2026-03-03. From then on, imported RentCast history (`import_batch = 'rentcast-history-2026-10'`) double-counted leases we already track, mostly recovered first-seen cycles of individual units in large apartment complexes.
- Any imported row with `leased_at` on or after 2026-03-03 now carries `report_excluded_reason = 'covered_by_own_tracking'` (computed automatically from the row) and is left out of the report pool, the same way duplicates are. The row stays in the database.
- Imported rows leased before 2026-03-03 count exactly as before.

## Small-cell fallback (3-mo / 6-mo)

When a monthly cell has fewer than 8 leases (or fewer than 8 trusted days-on-market values for days to lease), the report shows the median of the last 3 months combined instead, labelled "3-mo" with the 3-month lease count, if those 3 months together have at least 8 leases (8 trusted days values for days to lease). Quarterly cells (Bend ZIPs, smaller areas) use the last 2 quarters combined, labelled "6-mo". Cells still under 8 stay as dashes. Fallback values are computed from the report pool at freeze time and stored with the version (fb_* columns), so a locked month always shows the same values. The month's own medians, counts and confidence are unchanged, and year-over-year comparisons never use fallback values.
