log in sign up now
Score RB2B Visitors by Intent back to library
~ / library / Prospecting / score-rb2b-visitors-by-intent

Score RB2B Visitors by Intent

Turn a raw RB2B CSV export into a ranked table of visitors and companies scored 0.0-10.0 on buying intent, so you can see who's worth a follow-up at a glance.
  • Score this RB2B export for intent
  • Which of these visitors are worth following up with?
  • Any companies in here with multiple people visiting?
  • I've got a week of RB2B exports, combine them and tell me who's returning

RB2B Intent Scoring

Turns a raw RB2B CSV export into a ranked table of visitors and companies, scored 0.0-10.0 on buying intent, then — if the user wants to go further — chains into MoltSets to turn that ranked list into real contact data: emails, phone numbers, and additional contacts at the companies involved. Scoring is always step one; enrichment is optional and only happens if the user asks for it.

Why a script, not eyeballing

Scoring 60+ rows by reading them is slow and inconsistent between runs. scripts/score_visitors.py computes the score deterministically from the CSV columns, so the same export always produces the same ranking, and it scales to exports with hundreds of rows.

When to use this

Trigger this skill whenever the user:

  • Uploads an RB2B CSV export (recognizable by columns like LinkedInUrl, CompanyName, AllTimePageViews, RecentPageCount, FirstSeenAt, LastSeenAt, NewProfile, ProfileType)
  • Asks to find "hot leads", "strong signals", "who to follow up with", or similar from that data
  • Asks for an "intent score" on website visitors or companies

Before running: ask for a threshold

Always ask the user for a minimum intent score before running the script and presenting results, e.g.: "What's the minimum intent score you want results for? (e.g. 6.7)". Wait for their answer, then pass it via --min-score. Only skip asking if the user already gave a threshold in the same message (e.g. "show me everything 7+").

If the user says "show me everything" or otherwise declines a threshold, run without --min-score and return the full ranked list.

How to run it

python3 scripts/score_visitors.py /path/to/export.csv /path/to/output.csv --min-score 6.7

This writes a scored CSV sorted highest-intent first, containing only rows at or above the threshold. Read that CSV back and present it to the user as a table (in chat, or as a spreadsheet/artifact if they want a file — check the xlsx skill if they want a formatted Excel deliverable). The script prints how many rows scored above the threshold out of the total — mention that ("7 of 63 rows cleared 6.7") so the user has context even when the table itself is short.

Optional flags:

  • --config path/to/config.json — points at a config file for high-intent-page patterns and ICP criteria. If omitted, it looks for config/icp_and_pages.json next to the script. See "ICP setup" below.
  • --min-score 6.7 — filters output to that score and above. Omit to get every scored row.

If the user hasn't configured icp.industries or icp.employee_ranges and their export has no FilterMatches data, the ICP signal contributes nothing (not a penalty, just neutral) — flag this to the user rather than silently under-scoring.

If the user has multiple daily exports and wants a rolled-up view (e.g. "who showed up more than once this week"), concatenate the CSVs first (keep only one header row), then run the script on the combined file. Because the company-clustering signal counts distinct visitor rows per company within the file you pass in, combining multiple days into one file is exactly how you'd surface a company that sent different people to the site on different days.

What the score is built from

Five signals, each scored 0-10 internally, then combined:

  1. Returning visitor (multi-day)AllTimePageViews minus RecentPageCount gives page views accumulated before today's session. If NewProfile is true, this is forced to zero. More prior activity = stronger signal, log-scaled so it doesn't run away on outliers. Weight: 25%.
  2. Session depth (multi-page) — how many pages RecentPageCount shows for the current session. A single page view is the baseline (no extra credit); each additional page adds diminishing-return credit. Weight: 20%.
  3. Account clustering (multi-visitor) — how many distinct rows in the file share the same CompanyName (falling back to Website if the company name is blank). More people from the same company in the same export window is a stronger buying-committee signal. Weight: 15%.
  4. High-intent pages (what/how often) — parses RecentPageUrls and counts how many hits match a configurable list of high-intent URL patterns (pricing, demo, comparison pages, etc. — see config/icp_and_pages.json), and adds extra weight if RB2B has already tagged the row "Hot page" in its Tags column. Repeat visits to the same high-intent page count more than one hit. Weight: 20%.
  5. ICP match — uses RB2B's own FilterMatches column when populated (that's RB2B's native saved-filter matching, the most authoritative signal available). When FilterMatches is empty, falls back to a configurable check against Industry and EstimatedEmployeeCount — see "ICP setup" below. Weight: 20%.

These combine as a weighted average, then a combo bonus (up to +2.0) applies when two or more signals independently clear a "strong" threshold — a returning visitor who also hit the pricing page twice and matches your ICP is meaningfully hotter than any one of those signals alone suggests. That's the "combinations are stronger" behavior.

Final scores are capped at 10.0 and rounded to one decimal place.

ICP setup

The ICP signal only does something once it has data to check against:

  • Best case: the user has RB2B "Saved Filters" (or equivalent) configured, which populate FilterMatches directly in the export. Nothing to configure — the script uses it automatically and gives it real credit (not just presence, but how many filters matched).
  • Fallback: if FilterMatches is empty, edit config/icp_and_pages.json and fill in icp.industries and/or icp.employee_ranges with the exact values that appear in the user's own export (check a real file first — RB2B's bucket strings, like employee ranges, aren't perfectly standardized). Leave a list empty to skip that criterion.
  • If neither is populated, ICP contributes 0 to every row (neutral, not a penalty) — tell the user this explicitly rather than presenting scores as if ICP were factored in.

Output columns

IntentScore, Name, Title, CompanyName, Website, ProfileType, RecentPageCount, AllTimePageViews, HighIntentPageHits, HotPageTag, CompanyVisitorsToday, ICPMatch, Signals, Rationale, WorkEmail, LinkedInUrl, LastSeenAt, FirstSeenAt

Website is carried through from the original export specifically so the enrichment workflows below have a company domain to search against — don't drop it if customizing the output columns.

Signals is a short tag list (e.g. "Returning visitor, High-intent pages, Matches ICP config (2/2 criteria)"). Rationale is a full plain-English sentence or two built from the raw data — prior page view count and first-seen date, today's page count, named co-visitors from the same company if any, which high-intent pages were hit and how many times, and the ICP match detail. Use Rationale when the user wants to know why a specific row scored the way it did without re-deriving it themselves.

Presenting results

  • Lead with the top 10-15 rows in a markdown table in chat — don't dump all 60+ rows unless asked.
  • Call out anything with a company cluster (CompanyVisitorsToday > 1) explicitly, since that's the rarest and often most actionable signal.
  • If the user wants the full scored file, offer the CSV as a download rather than pasting everything inline.
  • Don't over-narrate the scoring formula unless the user asks — a one-line summary of why the top few rows scored highly is enough.

After scoring: offer enrichment

Once the scored table is presented, ask the user if they want to do anything further with it — don't just stop at the table and wait. Something like: "Want me to do anything with this — find contact info, or pull more contacts at these companies?" Use ask_user_input_v0 with up to four options so it's a tap rather than typing:

  • "Enrich visitors (email + phone)"
  • "Find alternate emails only"
  • "Companies → ranked contacts"
  • "Non-ICP visitor → ICP contact"

If the user declines, types something else, or just moves on, don't push further — the table stands on its own.

Each option is a full workflow documented in references/, written to consume the scored CSV you just produced (not the raw RB2B export):

Option Reference file When it fits
Enrich visitors (email + phone) references/enrich-visitors.md User wants richer contact info on the people who visited, beyond RB2B's own WorkEmail.
Alternate emails only references/alternate-emails.md Same idea, narrower — just a second email address, no phone lookup.
Companies → ranked contacts references/companies-to-contacts.md User wants contacts at the visiting companies generally — including Company rows with no person identified, or wants more than just the one person who happened to visit.
Non-ICP visitor → ICP contact references/non-icp-to-icp.md The company fits (ICPMatch = Y) but the visitor's own title doesn't look like the real buyer — find the right person at that company instead.

Read the relevant reference file in full before acting on it — each has its own steps, exact MoltSets tool names/params, and output format. These tools (MoltSets:linkedin_to_business_email, MoltSets:linkedin_to_personal_email, MoltSets:linkedin_to_mobile_phone, MoltSets:search_people, etc.) are deferred — call tool_search first if they're not already loaded in the conversation.

Tuning

The weights, log-curve constants, and combo-bonus threshold are all named variables near the top of scripts/score_visitors.py (returning_score, session_score, company_score, high_intent_score, icp_match_detail, combo_bonus). If the user wants to weight one signal more heavily, or found the scores running too hot/cold after seeing real results, adjust those constants directly rather than re-deriving the approach from scratch. The high-intent URL patterns and ICP criteria live in config/icp_and_pages.json instead, since those are data the user should own and update, not code.

Tips

  • Lead with the top 10-15 rows; call out any company cluster (CompanyVisitorsToday > 1) explicitly — it's the rarest, most actionable signal.
  • Weights, log-curve constants, and the combo-bonus threshold are named variables near the top of score_visitors.py; the high-intent URL patterns and ICP criteria live in config/icp_and_pages.json.
  • Offer the full scored file as a download rather than pasting 60+ rows inline.
01 Download the .moltsets skill file below
02 Open Claude and go to Settings to Skills
03 Click Add skill and select the downloaded file
04 Open a new chat in Claude
05 Prompt Claude using one of the example prompts or use your own
// difficultyEasy
// connectionCSV, Google Sheets, Excel
// got a skill of your own? Submit it to the library