Work

Airport Parking Dynamic Pricing

Reinforcement learning that prices car parking for a major London airport — daily pricebooks, a home-made A/B test and a pricing analyst firmly in the loop

Python
Reinforcement Learning
GCP
BigQuery
Vertex AI
Terraform
A/B Testing
Bird's-eye view of a busy airport car park

Executive Summary

Problem

The client, a major London airport, earns a significant share of its commercial revenue from car parking. Prices were set through a manual planning tool and a lot of analyst intuition: effective, but slow to react, hard to test and impossible to scale across every product, terminal, entry date and length of stay. The airport wanted to know, with evidence, what the right price was — and a way to keep finding it as the market moved.

Solution

We delivered the pricing capability in layers, over several phases of work:

  1. A serverless data platform on Google Cloud (migrated from an earlier, more expensive AWS design) that ingests Google Analytics, the planning tool’s pricebooks and competitor prices daily.
  2. A reinforcement learning agent that learns the revenue-maximising price for Long Stay and Valet parking, per terminal, segment, entry date and length of stay, and publishes a daily pricebook.
  3. A business rules overwrite module, so the commercial team can constrain or veto any price before it reaches the website.
  4. An explainability report answering the analyst’s two favourite questions: what prices did the model produce, and why.
  5. A real-world A/B testing framework to measure the model against business-as-usual pricing — which we had to invent ourselves, as you’ll read below.

Outcome

  • At the end of the first delivery phase the model was producing a ~3% uplift in revenue per quote, while only covering about 40% of the airport’s parking products and prices.
  • The platform has been in production since the summer of 2021, and led to follow-on work.

The challenge

1. The platform had to come first

Before this project, the airport’s analytics ran on an AWS stack (Databricks, Microsoft SQL Server, S3) that had proven more expensive than anticipated and awkward for the SQL-heavy exploration and machine learning work the team actually wanted to do.

We migrated it all to Google Cloud: BigQuery at the centre, Cloud Functions and Pub/Sub for the pipelines, everything serverless and deployed through Terraform.

None of this is glamorous, but it is the reason the pricing model could later be trusted: daily Google Analytics loads, pricebook ingestion and competitor data all arrive on a platform that tells us immediately when they don’t.

2. A reinforcement learning model you can actually read

The pricing problem in phase one was deliberately scoped to unconstrained supply — periods when the car parks won’t sell out, so the only objective is to maximise revenue per look (a “look” being a customer viewing a price). That scoping decision mattered: no demand forecast is needed when you can’t run out of spaces, so we deferred forecasting to a later phase rather than building everything at once.

Like our other pricing work, we kept the model tabular and small enough to inspect. The state combines length of stay, lead time, customer segment, terminal, and the price gaps to the short stay product and to competitors — each binned so that every state gathers enough data to learn from. Actions are price moves in 25p increments, and the reward is revenue per look.

Binning created its own artefact: lengths of stay 8–10 and 11–14 days share a bin each, and since bins learn independently, the price-per-day curve could jump discontinuously from one bin to the next. Customers notice a 10-day stay costing more than an 11-day one. The fix was simple and elegant: keep the learned price at the first length of stay in each bin and linearly interpolate between them.

Tuning the learning rate was a similar exercise in pragmatism. We measured how many days of traffic the model needs to adapt to new market conditions: for popular states, 1–3 days at the chosen alpha; for medium-popularity states, anywhere up to 200 days if alpha was set too low. That analysis, not theory, decided the final parameters.

3. The A/B test that didn’t exist

The original statement of work assumed the client’s booking engine had a working A/B testing mechanism. It did have one, unfortunately while testing it we found, that it was heavily biased and unusable for measuring a pricing model.

Rather than letting that stall the project, we came up with a workaround with no delay to the plan: the RL model’s prices all end in .25 or .75, while business-as-usual prices don’t. Every price a customer sees in Google Analytics is therefore self-labelling — we can tell exactly which pricebook served every look and booking, and build the whole test analysis from data we already had. It is a hack, and we are fine with that: it cost nothing, required no changes to the booking engine, and gave the client their first trustworthy price experiment.

The early results (a two-week window): +2.2% revenue per quote overall, +5.8% for the leisure segment. We were upfront that not all of it was statistically significant yet, and published the sample sizes needed and the dates we expected to reach them. By the hypercare period the conversion rate improvements for the overall, business and leisure segments were all significant at p < 0.01.

4. Keeping the pricing analyst in charge

Nobody hands their pricing to an algorithm on day one. The deployment process was designed to be semi-manual on purpose:

  1. The planning tool pushes its latest pricebook to the platform automatically.
  2. The RL pipeline triggers on arrival, produces its own pricebook, and writes both to a shared Google Drive folder — the model’s file distinguishable by its name.
  3. The business rules overwrite module (a Google Sheet the commercial team edits) constrains the model’s prices, and a report shows exactly where the model and business-as-usual disagree.
  4. The pricing analyst reviews, optionally edits, and uploads the chosen pricebook to the booking engine.

The explainability report was built from the analyst’s own user stories — “why has the model produced this price?”, “how does it compare to the maximum we’ve ever charged?”, “what are competitors doing?”. It ships as BigQuery tables feeding a Tableau dashboard, showing historic looks and revenue per quote, competitor price gaps, and the model’s top 5 candidate prices with their rewards over a 30-day history. When the model prices a Tuesday in the north terminal at an odd number, the analyst can see why — and overrule it.

5. Hypercare: tuning in production, honestly

After go-live we ran a 3-month “hypercare” managed service: fortnightly sprints of model releases, hyperparameter tuning, explainability iterations and A/B analysis, transitioning towards a champion-challenger always-testing methodology.

Not everything went to plan. The airport’s constrained period (when car parks do sell out) arrived weeks earlier than expected, which cut short the data we could collect for one planned experiment — there was no longer enough unconstrained traffic per bin for statistically meaningful results. We said so, dropped that experiment, and pivoted the remaining releases to tuning exploration and the learning rate for the business segment, where the data could still support conclusions.

The service is still running years later. I led the engineering on this project end to end — scoping, architecture and delivery, with a team where four members were on their first project with us — and it produced our company’s first productionised reinforcement learning model on Vertex AI. It also did what a good first phase should: the client came back, for demand forecasting, an analytics migration, and the next phases of pricing.