Can payment behavior tell you who is about to cancel? A churn project

Can payment behavior tell you who is about to cancel? A churn project

Another project done, and this one is a bit different from the last few. No API, no frontend. Just data, a model, and an Excel file at the end.

The idea: people rarely cancel an insurance policy out of nowhere. Before that happens, their payments usually get worse. A missed invoice, a card that fails, payments landing a few more days late each month. That signal is already sitting in the data, but nobody does much with it until the customer is gone.

And a model that spits out “62% churn” doesn’t really help either. A retention team can’t do anything with that number. They need to know who, why, and what to try.

So the pipeline goes like this:

  • Build behavior features from raw invoice and payment records (missed, failed and late ratios, how the days-late are trending, tenure)
  • Cluster the policies into risk groups without ever showing the model the churn label, then check those groups against a hidden ground truth. I liked this part, it’s a nice sanity check.
  • Train a logistic regression and a random forest and compare them
  • Use SHAP to break each score down into the behaviors that pushed it up or down
  • Map each policy’s top driver to a plain suggested action, like “prompt a payment-method update” for repeated failed payments
  • Dump everything into one Excel report

That last point was on purpose. The report is a file you open, color-coded, filterable, with a couple of charts. No login, no dashboard, nothing to learn.

Two things I’d point out. First, I didn’t just use the default 0.5 cutoff. A false alarm costs a wasted phone call, a miss costs a customer who leaves with zero warning, and those aren’t the same price, so the suggested threshold comes from weighing both. Second, only active policies get scored. A cancelled policy’s “risk” already happened, so scoring it is pointless.

The data is synthetic, a small script that makes up customers who pay fine, customers who slip a bit, and customers who get worse and worse until they cancel. So the numbers say more about the pipeline than about real insurance customers. Plugging in real payment history is the obvious next step. I’d also like a trend view per policy, since the scores are already stored per batch, and maybe try gradient boosting.

Stack is Python, PostgreSQL in Docker, pandas, scikit-learn, SHAP and openpyxl, with 60 pytest tests over the whole thing. It runs locally for now, nothing hosted.

The code, the README and an interactive architecture diagram are on GitHub: pacordev/insurance_payment_churn.