CASE STUDY POSTGRESQLGOOGLE ADS SCRIPTSIN PRODUCTION

The Ads Budget Engine

An occupancy-aware system that reallocates Google Ads spend across 38 facilities, nightly, so budget flows to the markets that can actually convert it.

Role
Designed, built & deployed solo
Stack
PostgreSQL, Google Ads Scripts, CallRail, Salesforce
Scope
38 facilities, US & Canada
Status
115 nightly runs since Feb 2026
100%
Of Google Ads budget across 38 facilities is allocated by the engine, nightly
~25%
Of the budget moves on an average night as occupancy shifts
0
Manual budget overrides since launch. The loop stays closed

The Problem

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 System

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.

occupancy feed
Rentable vs. occupied sq ft, per facility, daily
lead + revenue data
CallRail calls, form fills, closed revenue from Salesforce
ads performance
Spend, clicks, conv. by campaign via Scripts export
allocation model
PostgreSQL: scores each facility on vacancy, lead velocity & efficiency; converts scores to budget shares with floors and caps
google ads scripts
Sets daily budgets and refreshes generated ad assets across 38 markets, every night
↑ next-day performance flows back into the model · the loop closes itself ↑
-- 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.

Try 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.

Budget allocator · simplified model pool: $1,000/day (illustrative)
Allocation = each facility's vacancy share of total vacancy, with a 4% presence floor and a 35% cap. The production model adds lead velocity, efficiency, seasonality, and ramp curves for new facilities.

The Bonus: It Writes the Ads, Too

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.

Ad copy generator · templated from facility data assets valid
Local anchor
Starting price / mo
Min sq ft
Max sq ft
Sponsored  · 
Illustrative template and data. The production script generates the full responsive search ad asset set per facility (15 headlines, 4 descriptions) with pinning rules and character-limit validation, and refreshes assets when the underlying data changes.

Results

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.

Blended cost per lead
weekly · indexed · first full week = 100
Budget reallocated nightly
avg % of daily budget moved, by week

Queried from the production warehouse, July 2026. Cost per lead indexed to protect confidential figures.

01

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.

02

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.

03

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 Data Layer →

The warehouse this engine runs on: occupancy, leads, revenue, and spend for 38 facilities, reconciled nightly in PostgreSQL.