andersson.consulting
LIVE 0:00 · p(book) 4%

A propensity model example: the p(book) score on my home page, explained

My site scores whoever reads it with a small propensity model, on every page, and the home page prints the arithmetic as it goes. This note takes it apart: the signals, the hand-set weights, one score worked by hand, and what a production model does differently.

ALEXANDER ANDERSSON ·

MSG §Triggerpage_viewChannelwebSent—Variant—Rule—Statusarmed

What a propensity model does

A propensity model estimates how likely someone is to do one thing: buy, renew, churn, book. My site runs one on whoever reads it, on every page, this one included. It scores p(book), the chance you book a call, and the home page shows the maths signal by signal while you read. This note uses it as a propensity model example: what goes in, how the weights were set, one score worked by hand, and what a production propensity to buy model does differently.

A logistic propensity model, the simplest kind, has three parts: signals about what a person has done, a weight for how much each counts, and a function that turns the weighted sum into a probability between 0 and 1. A propensity to buy score is that probability for a purchase, and CRM teams use it to decide who gets which message, offer or channel, and who gets left alone.

The home page’s model has the shape of logistic regression, with the weights set by hand instead of fitted. Each signal is multiplied by its weight, the products are added to an intercept, and the total (the logit, or log-odds) goes through the logistic function, σ(z) = 1 / (1 + e^−z).

The intercept is −3.2. σ(−3.2) = 1 / (1 + 24.533) = 0.039, or 4% before any signal counts. The first screen already puts scroll_depth above zero, so a new visitor starts a little higher.

Why the weights are set by hand

A trained model learns its weights from outcomes: past visitors, each labelled booked or didn’t. This one has none. The tab keeps no record of who read what and then booked, and nothing from the analytics feeds back into the weights. Each score belongs to one visitor in one tab. The comment above the model in the code puts it in one line: “Same shape as a trained model; nothing from the analytics feeds back into it, so there is nothing to train it on.”

So the weights are judgement, printed from the code in the table below. Read as a set:

Three exceptions sit outside the sum: the score shows at most 92% until the calendar confirms a booking, 100% after that, and 0% with tracking off. Clicking Book doesn’t jump the score; it adds +0.6, like any other book_intent click.

The twelve signals

All are computed in the browser, from this tab alone, across every page of the site you open in it:

Each value is clipped to 0…its cap and rounded to two decimals before weighting.

A propensity model example, worked by hand

Take a reader three minutes in. They have opened two receipts, then the program flagged as their match. A card has been open for 90 seconds in total, they’ve been active all three minutes, and 80% of the page has been on screen. No clicks toward booking, no returns, nothing copied, and they touched the page in the last 30 seconds, so idle_min is 0.

Weight × signal, in log-odds:

The contributions sum to 4.12. With the intercept, the logit is −3.20 + 4.12 = 0.92. Then σ(0.92) = 1 / (1 + e^−0.92) = 1 / (1 + 0.3985) = 0.715. The scoring card prints p = σ(0.92) = 0.715 → 72%.

Now the pointer heads for the tab bar. exit_intent becomes 1, the logit drops by 0.60 to 0.32, and σ(0.32) = 1 / (1 + 0.7261) = 0.579, or 58%. The home page’s exit message, a draft win-back email, reads p from just before the exit counted, 0.72, and uses its band to pick the segment and the template’s second line: cold under 0.2, warm from 0.2, hot from 0.5.

A 72% chance of booking after three minutes is far too high, and the model can’t know that. Nobody has checked these weights against real bookings. The score puts readers in order of how much they’ve done; it doesn’t mean 72 in 100 such readers book. Calibration, further down, is how a production model closes that gap.

How to build a propensity model that runs in production

The shape survives, signals in and a probability out. Almost everything around it changes.

I build propensity scoring with Databricks, XGBoost and MLflow; the home page’s formula is the toy version of that. If you’re weighing whether a score is worth building into your CRM, start with the AI marketing audit. If it should decide who gets what inside one program, see lifecycle marketing.

Propensity model vs uplift model

A propensity model asks who is likely to act. An uplift model asks whose behaviour a message changes: the difference between a person’s chance of acting if they get the message and if they don’t.

The top of a propensity list includes people who would have bought anyway, and messaging them costs a send, or a discount, for nothing. An uplift model looks for the people a message moves, and flags the ones it puts off.

Uplift needs data a propensity model can do without: outcomes from people randomly held out of the message, set against people who got it. The home page’s model is propensity only; a website that remembers nothing has no control group.

See it score you

The model runs on every page of the site, this one included, and the home page shows it. Open How this page works and the scoring card lists every signal with its weight, your value and its log-odds, then the logit and σ, updating as you read. The site stores nothing in the browser and sets no cookies. Only three things about you can leave the tab. One is a batch of events for my own analytics, sent while tracking is on: what you open, click and how far you scroll. Another is a note request: the numbers on your scoring card and the names of the cards you opened go to Claude, which rewrites one message. The third is a booking you make.

The weights, from the code.

Read at build time from MODEL in src/lib/program.ts, the same object the home page scores you with.

SignalWeight (log-odds per unit)Counted up to
intercept−3.2—
receipts_opened+0.554
programs_viewed+0.853
pages_read+0.354
opened_match+0.41
book_intent+0.62
read_min+0.33
active_min+0.25
scroll_depth+0.91
came_back+0.352
copied_text+0.31
idle_min−0.254
exit_intent−0.61

p(book) = σ(intercept + Σ weight × signal), each signal clipped to 0…its cap. Shown at most 92% until a booking is confirmed, then 100%; 0% with tracking off.

Questions.

What is a propensity model?

A propensity model estimates the probability that a person takes a specific action, such as buying, renewing, churning or booking, from signals about what they have done. CRM teams use the score to decide who gets which message, and who gets none. The home page of this site runs a small one on its reader.

What is a propensity to buy score?

It's the output of a propensity to buy model: a number between 0 and 1 for how likely a customer is to purchase within a set window. In production it's usually recomputed on a schedule and written to each customer profile in the CRM, where segments and customer journeys can read it.

How do you build a propensity model?

Define the outcome and its window. Build a table of customers at past points in time, with their signals as they were then and what happened afterwards. Train on it: logistic regression as a baseline, gradient-boosted trees such as XGBoost as the usual next step. Check calibration on data the model hasn't seen, score on a schedule into the CRM, and measure with a holdout.

What is the difference between a propensity model and an uplift model?

A propensity model predicts who is likely to act. An uplift model predicts whose behaviour a message changes: the difference between their chance of acting with the message and without it. Uplift needs outcomes from a randomly held-out group to learn from.

Is the model on the home page trained on data?

No. It's a logistic model with hand-set weights. The site stores nothing in the browser and sets no cookies, so there is no history of visitors and bookings to train on. The weights are printed on this page, read from the same code the home page runs.

Book 20 minutes on your CRM.

If one flow leaks, we look at that. If it’s the whole program, we go from onboarding. You leave with what I’d fix first. — Alex

20 MIN · FREE · MONDAY TO FRIDAY · 09:00–17:00 STOCKHOLM TIME · GOOGLE MEET

WB1TRIGGERexit_intent at 0:00CHANNELemailSEGMENTcold · p 0.04SUPPRESS IFbooking_confirmed
WOULD SEND +24 h · NOT SENT — no address on file
COPY · template
FROMAlexander AnderssonTOProspect #1SUBJECTYou left at 0:00. Fair.

Hi,

You opened 0 of 4 receipts, looked inside 0 of 3 programs, got 0% of the way down the page and headed for the door. Leaving is why win-back emails exist, and why this one is short.

If a flow of yours leaks, reply. I’ll tell you what I’d change first.

— Alex

P.S. One win-back, then silence. That’s the rule.

Book 20 minutes instead