An occupancy-aware system that reallocates Google Ads spend across 38 facilities, nightly, so budget flows to the markets that can actually convert it.
ReadySpaces isn’t one business. It’s 38 mini-markets, each with its own inventory, demand curve, and competitive set. A facility in Carson behaves nothing like one in Toronto. But Google Ads budgets don’t know that. Set them once and they sit still while reality moves.
The failure mode is expensive in both directions. A facility at 98% occupancy keeps buying leads it can’t convert. There’s nothing left to rent. Meanwhile a newly opened facility at 40% occupancy, the one that desperately needs demand, gets the same static budget it always had. At 10 facilities you can manage this by hand. At 38, across two countries and four time zones, you can’t. And every month of lag between occupancy shifting and budgets catching up is wasted spend.
The marginal value of a lead isn’t constant. It depends entirely on how much space the facility has left to sell. Spend should follow vacancy, automatically.
The engine is deliberately boring: a nightly job in PostgreSQL that scores every facility, computes a target daily budget, and pushes it to Google Ads via the Scripts API. No dashboard to check, no meeting to run. Occupancy moves; budgets follow the next morning.
-- nightly allocation, simplified for illustration with facility_scores as ( select facility_id, -- vacancy is the demand signal: full buildings buy fewer leads greatest(1 - occupied_sqft::numeric / rentable_sqft, 0.02) as vacancy, -- recent lead velocity keeps us from flooding cold markets leads_30d::numeric / nullif(spend_30d, 0) as efficiency from facility_daily_snapshot where snapshot_date = current_date ) select facility_id, -- floors + caps: every market keeps a presence, none runs away total_budget * least(greatest(score / sum(score) over (), 0.01), 0.12) as daily_budget from facility_scores;
The production model also weights seasonality, unit mix, and new-facility ramp curves. This is the shape of it, not the whole of it.
Here’s the core idea as a toy. Six facilities, one fixed budget pool. Drag the occupancy sliders and watch spend move away from full buildings toward the ones with space to sell. It’s the same reallocation the real system does nightly across 38 markets.
Budgets were half the problem. The other half was the ads themselves. Thirty-eight facilities need copy that names the right city, the right local anchors, the real starting price, and the unit sizes actually available. Hand-written copy goes stale the day any of that changes.
So the same nightly job generates the creative. An asset script assembles responsive search ads from warehouse data: facility and local-area terms for relevance, an anchored starting price that qualifies the click before it costs anything, and live unit-size ranges so an ad never promises inventory that sold out. Prices move, copy follows, every market at once. The warehouse currently tracks 6,600 distinct generated assets across 58 campaigns covering all 38 facilities.
The portfolio runs near full, so the engine’s job is not a plunging cost curve. It is pointing the marginal dollar at vacancy and holding blended cost per lead steady while nobody touches a budget. Since launch: 115 nightly runs, roughly a quarter of the budget reallocated on an average night, and zero manual overrides.
Queried from the production warehouse, July 2026. Cost per lead indexed to protect confidential figures.
Floors matter more than the model. The first version zeroed out full facilities entirely, and their organic lead flow dropped too. Paid presence supports brand queries even when you can't convert every lead. The 4% floor fixed it.
Boring infrastructure beats clever dashboards. An earlier concept surfaced recommendations for a human to approve. Nobody approved them on time. Removing the human from the nightly loop, with caps as the safety rail, is what made it actually work.
The data layer was the real project. The allocation query is 40 lines. Getting trustworthy daily occupancy, lead, and spend data into one warehouse took months, and every growth system I've built since sits on that same foundation.
Code samples are simplified from production systems. Performance figures are queried from the warehouse or indexed to protect confidential data; demo inputs are illustrative.
The warehouse this engine runs on: occupancy, leads, revenue, and spend for 38 facilities, reconciled nightly in PostgreSQL.