DEV Community

Cover image for Predictive Lead Scoring in an Afternoon: A No-PhD Guide for B2B Teams
Michael
Michael

Posted on Originally published at getmichaelai.com

Predictive Lead Scoring in an Afternoon: A No-PhD Guide for B2B Teams

Most B2B teams score leads with a spreadsheet and a gut feeling. "Opened three emails? +10 points. Job title says VP? +20 points." It's arbitrary, and worse, it's static. Nobody knows if those numbers actually predict who buys.

The good news: you don't need a data scientist to do better. You need a CSV of past leads, a laptop, and about two hours. Let's build a predictive lead scoring model from data you already have.

The core idea

Manual scoring assigns points based on what you think matters. Predictive scoring learns from what actually mattered. You feed it historical leads with a known outcome (closed-won vs. lost/no-deal), and a model figures out which signals correlate with revenue.

That's it. No neural networks, no GPU. A logistic regression model handles this beautifully and gives you interpretable weights your sales team can trust.

Step 1: Pull the data you already have

Export closed leads from your CRM for the last 12-18 months. You want features known at the time the lead entered the funnel, plus the final outcome.

Useful columns:

  • Company size (employee count)
  • Industry
  • Lead source (paid, organic, referral, event)
  • Job title / seniority
  • Number of pages visited before signup
  • Email domain type (business vs. free)
  • Whether they requested a demo

The target column is binary: won = 1, everything else = 0.

Don't overthink feature selection. Grab everything reasonable and let the model tell you what's noise.

Step 2: Clean and encode

Models want numbers, not text. Here's the whole pipeline in plain Python.

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.metrics import roc_auc_score, classification_report

df = pd.read_csv("leads.csv")

# Target
y = df["won"]
X = df.drop(columns=["won", "lead_id"])

numeric = ["employee_count", "pages_visited"]
categorical = ["industry", "lead_source", "seniority", "domain_type"]

preprocess = ColumnTransformer([
    ("num", StandardScaler(), numeric),
    ("cat", OneHotEncoder(handle_unknown="ignore"), categorical),
])

model = Pipeline([
    ("prep", preprocess),
    ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
])
Enter fullscreen mode Exit fullscreen mode

Two details that matter more than people admit:

  • class_weight="balanced" handles the fact that most leads don't close. Without it, the model just predicts "loss" every time and looks 90% accurate while being useless.
  • handle_unknown="ignore" stops your model from crashing when a new industry shows up in production.

Step 3: Train and check it's real

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

model.fit(X_train, y_train)

probs = model.predict_proba(X_test)[:, 1]
print("AUC:", round(roc_auc_score(y_test, probs), 3))
print(classification_report(y_test, model.predict(X_test)))
Enter fullscreen mode Exit fullscreen mode

Watch the AUC score. It measures how well the model ranks buyers above non-buyers.

  • 0.5 = random, throw it away.
  • 0.65-0.75 = useful, better than your gut.
  • 0.8+ = strong. Sales will notice.

If you're below 0.6, your features probably don't capture buying intent. Add behavioral data (product signups, email engagement, demo requests) before touching the model.

Step 4: Turn probabilities into scores people use

A raw probability like 0.73 means nothing to a rep. Convert it to a 0-100 score and tier it.

def score_lead(lead_dict):
    row = pd.DataFrame([lead_dict])
    prob = model.predict_proba(row)[:, 1][0]
    score = int(round(prob * 100))
    if score >= 70:
        tier = "A - call now"
    elif score >= 40:
        tier = "B - nurture"
    else:
        tier = "C - low priority"
    return {"score": score, "tier": tier}

print(score_lead({
    "employee_count": 250,
    "pages_visited": 9,
    "industry": "SaaS",
    "lead_source": "referral",
    "seniority": "VP",
    "domain_type": "business",
}))
Enter fullscreen mode Exit fullscreen mode

Now every lead gets a number and a clear action. That's the whole point of sales funnel optimization: stop reps guessing where to spend their hours.

Step 5: Ship it into your workflow

A model in a notebook is a hobby. A model wired into your funnel is leverage.

Wrap score_lead in a small API endpoint, then trigger it from your marketing automation. In n8n or a similar tool, the flow is simple:

  1. New lead hits your CRM (webhook trigger).
  2. Send the lead's fields to your scoring endpoint.
  3. Write the score and tier back to the CRM record.
  4. Route A-tier leads straight to a rep and B-tier into a nurture sequence.

No human touches a lead before it's prioritized.

What breaks, and how to keep it honest

Models drift. Your market shifts, campaigns change, and last year's patterns fade. Retrain quarterly on fresh closed-won data. Log every prediction and its eventual outcome so you can measure whether the model still earns its keep.

Also resist the urge to jump to fancier algorithms. Logistic regression stays interpretable: you can pull the coefficients and tell your VP of Sales why referrals from mid-market SaaS companies score high. That trust is worth more than a fractional AUC bump from a black-box model nobody understands.

Start simple, ship it, and let the results argue for the next iteration.


Originally published at getmichaelai.com

Top comments (0)