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
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.
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.
Trigger this skill whenever the user:
LinkedInUrl, CompanyName, AllTimePageViews, RecentPageCount, FirstSeenAt, LastSeenAt, NewProfile, ProfileType)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.
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.
Five signals, each scored 0-10 internally, then combined:
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%.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%.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%.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%.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.
The ICP signal only does something once it has data to check against:
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).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.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.
CompanyVisitorsToday > 1) explicitly, since that's the rarest and often most actionable signal.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:
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.
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.
CompanyVisitorsToday > 1) explicitly — it's the rarest, most actionable signal.score_visitors.py; the high-intent URL patterns and ICP criteria live in config/icp_and_pages.json.Get the Score RB2B Visitors by Intent skill file. We'll send occasional updates - no spam.