THE SCORING ENGINE
Give sales a reason
to make the call.
Turn identified website visitors into a prioritized list. Separate potential buyers from suppliers, weigh fit against interest, and attach the reason behind every recommendation.
- Discipline
- Buyer qualification
- Context
- US executive search firm
A ranked list with a reason for every row
What organic search sends, and what it doesn't.
The firm grows on organic search, events and referrals. The problem is what organic search sends. Fractional executive is an emerging category and the keywords are all over the place, interim, fractional, part-time, and most of the people searching them are executives looking for their next thing, not companies looking to hire one. So a good share of the site's traffic looks like demand and is actually supply.
The buyers mostly come through events and referrals, and the site is where they go to check the firm out once they've been warmed up. Which means the site gets both at once: warm buyers looking around, and a steady flow of people who will never buy anything and behave exactly like people who will.
RB2B gives you a CSV. Name, title, company, pages, first seen, last seen, LinkedIn. It doesn't know which side of the market anyone is on. Basically everyone looks the same to it.
The task: separate buyers from fractional executives looking for work, from title, company and pages, without a human reading every row. Then rank whoever's left and say why.
Five stages, and every row carries its reason.
The classifier takes the export and produces a workbook sales opens and uses. Nothing in it came from a template. Each stage exists because of something the firm's traffic actually did, and under each one is the code as it runs, with my comments for you.
The gate
Before anything is scored, the engine removes what shouldn't be scored: companies over five thousand employees, universities, hospitals, real estate, the self-employed, internal staff, do-not-contact, and fractional executives, because a fractional exec on the site is supply, and supply is what this whole engine exists to keep off the call list. Each exclusion gets a row with a type and a reason.
View the implementation38 lines +
From the classifier, with my comments for you. The lists themselves live in a template the firm edits.
def check_exclusions(row, internal_team, dnc_list, competitors, fractional_execs):
first = str(row.get('FirstName', '')).strip()
last = str(row.get('LastName', '')).strip()
full_name = f"{first} {last}".strip().lower()
company = str(row.get('CompanyName', '')).strip().lower()
title = str(row.get('Title', '')).strip().lower()
emp = parse_employee_count(row.get('EstimatedEmployeeCount'))
# The order is the point. Cheap, certain exclusions first, so a row that is
# obviously not a prospect never reaches the scoring below it.
for name in internal_team:
if _name_matches(name, first, last, full_name):
return 'INTERNAL', ...
for name, reason in dnc_list:
if _name_matches(name, first, last, full_name):
return 'DO NOT CONTACT', ...
for comp in competitors:
if comp.lower() in company:
return 'COMPETITOR', ...
# This is the one the whole engine exists for. A fractional executive on the
# site is the supply side, and they look exactly like a buyer to a tool that
# only sees pages. The roster lives in a template the firm edits, not here.
for name in fractional_execs:
if _name_matches(name, first, last, full_name):
return 'FRACTIONAL EXEC', ...
# Only from an explicit word in the title or the company. Guessing would throw
# out real people.
for kw in ['self employed', 'self-employed', 'freelance', 'freelancer', 'retired']:
if kw in title:
return 'SELF-EMPLOYED', ...
for exc_type, keywords in NON_TARGET_KEYWORDS.items():
for kw in keywords:
if kw in company:
return exc_type, ...
# Five thousand people is where a company stops buying the way this firm sells.
if emp and emp > 5000:
return '5000+ EMPLOYEES', ...
return None, NoneFit, out of 30
Title, revenue band, industry, geography. The weights come from the ICP, rebuilt the same year off a voice-of-customer corpus, so what counts as a good-fit company is what clients said on calls.
View the implementation23 lines +
Title, out of ten. Revenue, industry and geography have functions of their own.
def score_title(title):
# Ten points for the people who sign. Not the same thing as the MQL title gate
# further down: this rewards seniority broadly, the gate is stricter on purpose.
if pd.isna(title) or not str(title).strip() or str(title).strip() == '(No title)':
return 0
t = str(title).lower().strip()
c_suite = ['ceo', 'cfo', 'cto', 'coo', 'cmo', 'cro', 'cpo', 'cio', 'cso', 'chief ',
'founder', 'co-founder', 'cofounder', 'president', 'owner', 'managing director']
if any(k in t for k in c_suite):
return 10
vp_dir = ['vice president', 'vp ', 'vp,', 'director', 'managing partner', 'partner',
'general partner', 'board member', 'executive director']
if any(k in t for k in vp_dir):
return 8
manager = ['manager', 'general manager', 'head of', 'head,', 'site manager']
if any(k in t for k in manager):
return 6
relevant = ['coordinator', 'project manager', 'business development', 'account manager',
'recruiter', 'consultant', 'advisor', 'strategist', 'analyst']
if any(k in t for k in relevant):
return 4
# Everyone with a title gets something. No title at all gets nothing, above.
return 2Behaviour, out of 70
Pages weigh differently by what they mean. Pricing or booking is a hand-raise. A journey across intent pages gets a bonus, showing up in more than one export gets a bonus. The apply page is a hard stop: anyone who touched it is re-labelled and pulled off the call list whatever their score, because that's where the supply side goes.
View the implementation25 lines +
Page scores sit in a dictionary by funnel stage: decision pages 10 to 12, consideration 7 to 9, interest 4 to 6, awareness 1 to 3. This is what happens to them.
def compute_behavioral(pages, all_time_views, n_exports):
if not pages:
return 0, []
# Every page has a score by what it means, not by how often it is visited.
# A pricing page is worth more than ten blog posts.
page_scores = [(p, get_page_score(p)) for p in pages]
base_sum = sum(s for _, s in page_scores)
unique_pages = list(set(pages))
max_score = max((get_page_score(p) for p in unique_pages), default=0)
n_unique = len(unique_pages)
# A journey across pages that matter is worth more than the pages alone.
# The max_score check stops three blog posts from counting as a journey.
journey_bonus = 0
if n_unique >= 3 and max_score >= 4:
journey_bonus = 15
elif n_unique >= 2 and max_score >= 4:
journey_bonus = 10
# Showing up in more than one weekly export means they came back.
repeat_bonus = 0
if n_exports >= 3:
repeat_bonus = 10
elif n_exports >= 2:
repeat_bonus = 5
total = base_sum + journey_bonus + repeat_bonus
return total, page_scoresThe decision
MQL needs fit of at least 15 and behaviour of at least 25, or a hand-raise from manager level up. Then a title gate: CEO, founder, co-founder, an actual president, an actual VP. A VP of Marketing with MQL-strength behaviour goes to the warm list, because at this firm the person who signs is the person at the top. Everyone else lands in Warm, Flagged, Stale, or Company Signals for the anonymous ones.
View the implementation50 lines +
The original client classifier: decision rules and title eligibility. These are separate from this website’s demonstration score.
def meets_mql_behavior(fit, behav, pages, title_score):
# Kept as its own function so the decision and the label in the row agree
# on exactly what "MQL-strength" means. Two places, one definition.
if fit < 15:
return False
is_hand_raiser = any(p in HAND_RAISER_PAGES for p in pages)
if behav >= 25:
return True
# A hand-raise on the pricing or contact page, from a manager or above,
# gets in a little earlier than the behaviour score alone would allow.
if is_hand_raiser and title_score >= 6 and behav >= 20:
return True
return False
def classify_lead(fit, behav, pages, title_score, visited_apply, flags, title):
# The apply page decides first, before fit and behaviour get a say.
# Someone who touched it is almost always the supply side.
if visited_apply:
non_apply = [p for p in pages if p != 'apply']
max_non_apply = max((get_page_score(p) for p in non_apply), default=0)
if max_non_apply >= 4 and fit >= 15:
return 'APPLY-PAGE-CAUTION'
return 'SUPPLY-SIDE ONLY'
if fit >= 15:
if meets_mql_behavior(fit, behav, pages, title_score):
# MQL is only for the people who sign: CEO, founder, co-founder, an
# actual president, an actual VP. A functional VP with the same
# behaviour is still a strong lead. It nurtures as Warm instead.
if is_mql_eligible_title(title):
return 'MQL'
return 'WARM'
return 'WARM'
if behav >= 20:
return 'FLAGGED'
return 'LOW'
# Founder or co-founder has to be the role, not a word in a job title.
# "Founder", "Founder & CEO", "Founder of Acme" qualify. "Founder Relations",
# "Head of Founder Programs", "Founding Engineer" do not. So: find the word,
# then look at what follows it.
fm = re.search(r'\bco-founder\b|\bfounder\b', t)
if fm:
rest = t[fm.end():].lstrip()
if rest == '' or rest[0] in ',&/|+' or rest.startswith(('of ', 'and ')):
return True
if re.match(r'(ceo|president|owner|coo|cfo|cto|cmo|cio|cro|chair|principal)\b', rest):
return True
# Otherwise "founder" is describing a department, and the checks below decide.The review
After the rules run, an LLM reads the finished workbook, not the export, so it's reading the exceptions. It catches what a rule can't see, a company that shows five thousand people because of who's affiliated with it when the actual team is ten, a "President & CEO" the parser filed as functional, and corrects them. Rules first, review on top, packaged as a skill so it runs the same next week.
View the implementation21 lines +
From the skill. What the agent is told to do, and the one rule it cannot touch.
# What the agent is told to do, after the Python has run. It reads the # workbook, not the export, so it is reading the exceptions. Every MQL row Confirm the title really is MQL-eligible: a CEO, founder, co-founder, or a literal President or VP. The Python gate enforces this, but watch for what a parser misreads: "Pres." abbreviations, titles in another language, "President & CEO" combos. Check the industry is not obviously wrong. Warm leads labelled "title not MQL-eligible" These had MQL-strength behaviour and a title the gate turned away. Skim them. If one is a top decision-maker the parser misfiled, move it up. Top 20 Warm leads Any that deserve attention on title, company and geography together? Flagged leads with high scores Any worth a human looking at? The one rule it cannot touch /apply is red, at the bottom, always. Removed from MQL and Warm.
The supply side, measured and handled.
Snapshot: the summer 2026 runs, covering visitors identified since the previous autumn. Proportions, not volumes.
The workbook lands with sales, ranked and reasoned.
The MQL tab on top, every exclusion accounted for on its own sheet, and any row on the call list traceable back to the rule that put it there. Stack: RB2B in, a Python engine for the rules, an LLM review over the output, the pair packaged as a skill, Excel out. HubSpot, Google Tag Manager and GA4 around it.
Whether the same engine fits another firm depends on what its traffic does. Finding out is the same work: look at who actually shows up, then build for that.
What’s getting in the waySpecific examples from your public website.
What it means for the buyerWhy each issue is worth your attention.
What I would fix firstA clear order of priorities for you or your team.
Free. No account access. No call required.