AI experts sharing free tutorials to accelerate your business.
Back to Roofing toolkit

Predictive Lead Scorer

Rank a batch of leads or prospects by likelihood-to-close and expected ticket size — using a transparent, weighted composite that a sales rep can audit — so the call-list, door-knock list, and mailer list all start with the homeowners most likely to need (and buy) roof work right now. Built for storm-season triage, aging-roof mailers, purchased-list qualification, and weekly pipeline review.

Saves ~30 min/batchintermediate Claude · ChatGPT · Gemini

🎯 Predictive Lead Scorer

Purpose

Rank a batch of leads or prospects by likelihood-to-close and expected ticket size — using a transparent, weighted composite that a sales rep can audit — so the call-list, door-knock list, and mailer list all start with the homeowners most likely to need (and buy) roof work right now. Built for storm-season triage, aging-roof mailers, purchased-list qualification, and weekly pipeline review.

When to Use

  • Within 24–72 hours of a storm event, to triage CRM leads and canvassing targets by damage probability × buying signal
  • Weekly Monday-morning pipeline review to re-rank open leads against fresh trigger events (new storms, expiring insurance deadlines, neighborhood conversions)
  • To qualify a purchased lead list before investing call-bank time
  • During seasonal pushes (spring / fall), to rank door-knock or mailer targets
  • When onboarding a new sales rep and handing them a prioritized starter list

Required Input

Provide the following:

  1. Lead list — Names, addresses, and any known property details: age of roof, last service date, prior estimate amount, prior claim history, CRM notes. Accepts CSV, pasted table, or unstructured list — the skill will normalize
  2. Weather / trigger context — Recent storms with date, hail size, peak wind, affected ZIPs. Include both named events ("2026-04-18 hail event, 1.5-inch, zips 75070/75074/75075") and rolling 90-day summaries if available
  3. Historical data (optional) — Completed jobs in the same neighborhood (addresses or block-level), active estimates, referral chains, prior-year canvassing results for the same area
  4. Scoring priorities — Primary objective this batch: storm-damage response, aging-roof replacement, maintenance upsell, insurance-claim-active, or general new-customer acquisition. Determines weight profile (see below)
  5. Batch size — Number of leads in this run. Scoring profiles auto-adjust: under 50 = full rationale per lead; 50–500 = condensed rationale; 500+ = tier-only output with top-50 detailed

Instructions

You are a roofing sales strategist's AI assistant. Your job is to produce a ranked lead list with auditable scores — so a rep can trust the order and a manager can defend it.

Before you start:

  • Load config.yml — specifically these fields:
    • service_area.zip_codes[] — valid ZIPs for the shop (reject any lead outside this list or flag as out-of-area)
    • service_area.hail_zones[] — ZIPs historically flagged as hail-prone; auto-boost on matching weather triggers
    • canvassing.territories[] — named canvassing clusters (e.g., "Maple Ridge", "Sunset Grove") with associated ZIPs and street lists; used for neighborhood-density scoring
    • target_profile.roof_age_floor (default 15), target_profile.roof_age_premium (default 20) — age bands for scoring
    • target_profile.ticket_minimum (default $8,000) — minimum revenue threshold below which a lead is auto-tiered to Nurture
    • rates.average_job_value — for estimating revenue potential where roof size is known
    • past_jobs.completed_addresses[] — for neighborhood-density and social-proof scoring (street-level adjacency)
    • voice — communication tone for the recommended-action column
  • Reference knowledge-base/terminology/ for damage and condition terms used in rationales
  • Reference knowledge-base/industry-overview.md for the 72-hour post-storm first-responder window
  • If a config field is missing, use the default and flag it in the output as an assumption

Scoring weights by priority profile (sum to 100):

CriterionStorm-ResponseAging-RoofMaintenance-UpsellNew-Customer
Weather exposure35101015
Property age signal20352025
Neighborhood density15202025
Interaction recency15153020
Revenue potential15202015

Default profile is Storm-Response during a live trigger window; fall back to Aging-Roof outside active storm periods.

Scoring formula (per lead):

composite = Σ (criterion_weight × criterion_score_0_to_10) / 10
tier = Hot if composite ≥ 80, Warm if 50–79, Nurture if < 50

The divisor of 10 scales back to 0–100 so tiers line up with the weight totals.

Criterion scoring rules:

  1. Weather exposure (0–10)

    • 10 = address in hail swath with peak stone ≥ 1.5" within last 30 days
    • 8 = address in hail swath 1.0"–1.5" OR wind swath ≥ 70 mph within last 30 days
    • 6 = address within service_area.hail_zones[] with rolling 12-month hail event
    • 4 = address within service area with weather activity outside target thresholds
    • 2 = address within service area, no recent weather
    • 0 = address outside service area
  2. Property age signal (0–10)

    • 10 = roof age ≥ target_profile.roof_age_premium (default 20 yrs)
    • 8 = roof age ≥ target_profile.roof_age_floor (default 15 yrs)
    • 6 = age unknown BUT neighborhood median build year places likely age ≥ floor
    • 4 = roof age 10–15 yrs
    • 2 = roof age under 10 yrs
    • If age is unknown AND neighborhood context is absent, score 5 and flag for field verification
  3. Neighborhood density (0–10)

    • 10 = one or more entries in past_jobs.completed_addresses[] on same street OR within named canvassing.territories[] cluster with active jobs
    • 7 = completed jobs within same ZIP within last 12 months
    • 4 = completed jobs within service area, not in same ZIP
    • 1 = no nearby completed work
  4. Interaction recency (0–10)

    • 10 = inbound inquiry within last 7 days OR referred lead
    • 8 = inbound inquiry 7–30 days
    • 6 = active-estimate-not-signed 30–90 days
    • 4 = cold lead 90–180 days
    • 2 = cold lead 180–365 days
    • 0 = no prior interaction / purchased list / canvassing target
    • Trigger refresh rule: if a new weather event has occurred after the last-interaction date, add +3 to the recency score (cap 10) and note "trigger refresh" in the rationale
  5. Revenue potential (0–10)

    • Map (estimated squares or known roof size) × rates.average_job_value per square to ticket size, then:
    • 10 = ticket ≥ 3× target_profile.ticket_minimum
    • 8 = 2× ticket_minimum
    • 6 = 1–2× ticket_minimum
    • 4 = at ticket_minimum
    • 2 = below ticket_minimum (repair-only, partial replacement)
    • If size is unknown, estimate from ZIP median home size and flag as "size estimated"

Process:

  1. Normalize the input list into rows: lead_id, name, address, ZIP, known_age, known_size, last_interaction, notes
  2. Enrich each row with weather overlay, hail-zone flag, territory flag, completed-jobs adjacency
  3. Score each criterion with the rules above; compute composite and tier
  4. Sort descending; apply batch-size output rules
  5. Generate the recommended-action column in the voice from config.yml
  6. Produce the top-5 summary narrative

Output artifacts:

1. Ranked Table

RankLeadAddressZIPAgeSize (sq)WeatherAgeNbrRecencyRevenueCompositeTierRecommended Action
1Jane Doe123 Maple Ridge Dr7507022281010106888🔥 HotCall today — 1.5" hail, 22-yr roof, 4 completed jobs on block
2…

Columns for criterion scores are 0–10; composite is 0–100.

2. Tier Summary

  • 🔥 Hot (composite ≥ 80): count, dispatch plan (calls today, doors tomorrow)
  • 🟡 Warm (50–79): count, drip-sequence assignment
  • ⚪ Nurture (< 50): count, move to long-term nurture list

3. Top-5 Narrative

For each of the top 5 leads, a 60–80 word paragraph covering: why this lead, the single strongest trigger, the suggested opening line in the config voice, and the fallback CTA if the primary ask is declined.

4. Out-of-Profile Flags

  • Leads outside service_area.zip_codes[] — listed separately with "out of area" reason
  • Leads where ticket estimate < target_profile.ticket_minimum but weather score is maxed — flag as "storm repair, low ticket" for a separate repair-crew queue
  • Leads with age-unknown AND no neighborhood context — flagged for field verification before call-bank dispatch

Output requirements:

  • Every composite score must decompose to the five criterion scores × weights (no black-box numbers)
  • Recommended-action column uses the voice from config.yml, not generic AI prose
  • Saved to outputs/lead-scoring/{YYYY-MM-DD}-{batch-label}-scored.md if user confirms, with a companion CSV at outputs/lead-scoring/{YYYY-MM-DD}-{batch-label}.csv for CRM import
  • Batch size > 500: table is truncated to top 50; full CSV still written

Efficiency notes:

  • If the user provides neither weather context nor a scoring priority, default to Aging-Roof profile and note the assumption
  • If a config field is missing, default and flag — never block on config gaps
  • Cross-reference: route 🔥 tier leads into lead-response-automator (instant outreach) and 🔥 storm leads into storm-canvassing-prioritizer (route clustering); 🟡 tier into follow-up-sequence (drip cadence); 🥉 Nurture into maintenance-plan-generator (subscription upsell)

Example Output — 25-lead Storm-Response batch (2026-04-18 DFW hail, mixed 75070/75074/75075)

Scenario: Sales manager Sam Rivera pulls a 25-lead batch on the morning of 2026-04-19 (12 hours after the 4/18 hail event). Sources: 9 inbound web/phone inquiries from 4/18–4/19, 8 cold canvassing targets pulled from Eagleview Horizon (matching age + ZIP), 5 prior-season cold leads in affected ZIPs, 3 referrals (Tom @ 1318 Oak Ridge). Profile = Storm-Response (active hail event in service area).

Config fields resolved from config.yml [config] vs. supplied as batch overlay [batch]:

  • [config] service_area.zip_codes[] = [75033, 75034, 75035, 75070, 75074, 75075, 75093, 75013, 75002]; [config] service_area.hail_zones[] = [75033, 75034, 75070, 75074]
  • [config] voice = "Direct, plain-spoken, no jargon" (Storm-Response opener tone)
  • [batch] canvassing.territories[] → Maple Ridge (75070), Sunset Grove (75070), Riverside (75074), Willow Creek (75035) — supplied with the batch; defaulted to ZIP clusters when config carries no territory list
  • [batch] target_profile → roof_age_floor 15 / roof_age_premium 20 / ticket_minimum $8,000 (skill defaults; overridable in config but absent from this shop's config.yml)
  • [batch] rates.average_job_value → $620/square installed (asphalt-arch baseline; defaulted — this shop's config carries per-line rates.* but no blended job value, so the skill computes Revenue from squares × $620 and flags the default)
  • [batch] past_jobs.completed_addresses[] → 1318 Oak Ridge (75033), 1248 Maple Ridge (75070), 234 Elm (75074), + 11 others (adjacency source; absent from base config, supplied from CRM export)

Weather overlay (Storm-Response weights apply): 4/18 hail swath, 1.5" peak in 75070 core / 1.25" tail in 75074 / 1.0" edge in 75035 / clipping 75075. Wind 61 mph peak. (75070 and 75074 are both in service_area.hail_zones[], so Weather scores hold ≥6 for these ZIPs even on a rolling-12-month basis.)


1. Ranked Table (top 10 of 25 — full CSV at outputs/lead-scoring/2026-04-19-dfw-hail-scored.csv)

Weights (Storm-Response profile): Weather 35 / Age 20 / Neighborhood 15 / Recency 15 / Revenue 15. Composite = Σ(score × weight) / 10. Rows are sorted by composite descending; ties broken by Weather then Recency.

RankLeadAddressZIPAgeSize (sq)WANR$CompositeTierRecommended Action
1Janelle Doe1248 Maple Ridge Dr75070222810101010897🔥 HotCall today 9 AM — 1.5" hail, 22-yr roof, you've already done Tom's roof on the block; inbound 4/18
2Marcus Bell234 Elm St7507421281010106891🔥 HotCall today — completed neighbor; 6-recency (estimate 30-day refresh + storm trigger)
3Pat Nguyen4221 Cedar Hollow Ln750741830108710888🔥 HotCall today — 1.25" hail, 18-yr roof, active leak inbound 4/18 21:47; route to lead-response-automator
4Lisa Fortuna655 Cedar Ct750741622108104884🔥 HotPhone callback + storm-trigger SMS; 4-recency from old cold list, refreshed by 4/18 pushes her over the Hot line
5Cold lead 75074 #1(canv 75074 target)75074est 19est 28108100878🟡 WarmDoor knock under Riverside cluster Day 1; flag age-est + size-est for field verify
6Diana Chu456 Maple Ave75070920104106676🟡 WarmEmail Maple Ridge proof + spring window angle; 9-yr roof low age score offset by hail + adjacency
7Cold lead 75070 #1(canv 75070 target)75070est 16est 24108100675🟡 WarmDoor knock under Maple Ridge cluster Day 1; flag age-est + size-est for field verify
8Greg Novak789 Oak Dr75070171610874473🟡 WarmDrop scope-difference visual + 72-hr window text; cold 90-180d with trigger refresh
9T. Maddox referral1318 Oak Ridge Dr75033—26451010866🟡 WarmReferral — call today, low weather but 10-recency + adjacency carries; flag size-est
10Sara Odom321 Pine Blvd750021462448236⚪ NurtureRepair-queue route — out-of-storm-zone, repair-only ticket; flag for separate repair-crew queue

Full composite decomposition (every row audits — no black-box numbers):

RankLeadW×35A×20N×15R×15$×15Sum÷10Composite
1Janelle Doe35020015015012097097.097
2Marcus Bell3502001509012091091.091
3Pat Nguyen35016010515012088588.588
4Lisa Fortuna3501601506012084084.084
5Cold lead 75074 #1350160150012078078.078
6Diana Chu35080150909076076.076
7Cold lead 75070 #135016015009075075.075
8Greg Novak350160105606073573.573
9T. Maddox referral14010015015012066066.066
10Sara Odom7080601203036036.036

Non-integer composites round down per convention (Pat Nguyen 88.5 → 88; Greg Novak 73.5 → 73). Note the tier line falls between rank 4 (Lisa, 84 — Hot) and rank 5 (78 — Warm): Lisa clears the ≥80 Hot threshold, so she is dispatched to a live call, not a drip sequence.

2. Tier Summary

  • 🔥 Hot (composite ≥ 80): 4 leads — Janelle Doe (97), Marcus Bell (91), Pat Nguyen (88), Lisa Fortuna (84). Dispatch plan: Sam calls all four by noon today 2026-04-19; the on-call estimator (see lead-response-automator routing) takes Pat Nguyen's active leak first. Lisa Fortuna is a Hot lead, not a drip — her 4/18 storm trigger lifts an old cold-list contact over the ≥80 line, so she gets a live call today, not a Warm-tier sequence.
  • 🟡 Warm (composite 50–79): 13 leads — Cold lead 75074 #1 (78), Diana Chu (76), Cold lead 75070 #1 (75), Greg Novak (73), Tom Maddox referral (66), plus 8 more cold-canvassing / prior-season targets with trigger refresh. Drip assignment: route to follow-up-sequence Warm-tier cadence (storm-context Day 1 opener).
  • ⚪ Nurture (composite < 50): 8 leads — out-of-hail-zone, repair-only ticket, or recency too cold even after refresh. Move to long-term nurture list; re-score next Monday's pipeline review.

3. Narrative — the 4 Hot leads + the notable Warm cross-skill case

1. Janelle Doe — 1248 Maple Ridge Dr (composite 97, Hot): 22-yr roof in 1.5" hail core, four completed Acme jobs within 800 ft (including Tom Maddox at 1318 Oak Ridge), inbound web form 4/18 at 21:47 with photos showing granule loss and gutter dents. Opening line: "Janelle — Sam at Acme Roofing. Got your photos from 4/18 — those are textbook hail signals. William, our HAAG-certified inspector, has a 2 PM slot at 1248 Maple Ridge today, and we've already worked 9 jobs in 75070 since the storm." Fallback CTA: drop the 1-page condition snapshot tonight if she's not home for the call.

2. Marcus Bell — 234 Elm St (composite 91, Hot): 21-yr roof in 1.25" hail tail, existing estimate not signed 30 days, four completed Acme jobs in adjacent 75074 streets. The trigger-refresh lifted his standing (active estimate + storm) and his 10/10/10 on Weather/Age/Neighborhood puts him second only to Janelle. Opening line: "Marcus — Sam at Acme. Your 234 Elm estimate is still good but the 4/18 hail means your insurance may now cover it. Want me to swing by and walk it with you Thursday morning?" Fallback CTA: text the 1-page insurance-supplement-writer angle.

3. Pat Nguyen — 4221 Cedar Hollow Ln (composite 88, Hot): 18-yr roof in 1.25" hail tail, active interior leak inbound 21:47 the night of the storm during the tornado warning. Open emergency tier — lead-response-automator already paged the on-call estimator; tonight's tarp + tomorrow's full inspection booked. Ranked third by composite, but worked FIRST operationally because of the active leak — the score sets priority, the emergency SLA sets sequence.

4. Lisa Fortuna — 655 Cedar Ct (composite 84, Hot): 16-yr roof in 1.25" hail tail, cold lead from prior-season canvassing (Recency was 4, and the 4/18 storm trigger lifts her composite to 84 — over the ≥80 Hot line). This is the row the previous version mis-scored as a 78 Warm drip; she is a live call today. Opening line: "Lisa — Sam at Acme Roofing. Don't know if you remember me from the spring 2025 canvassing — your roof was on our 'worth a check in a year' list. 4/18 hail in your ZIP was 1.25". 30-min walk + a written report on the house, no charge. Friday morning OK?" Fallback CTA: email + voicemail with the Riverside drone footage.

Notable Warm — Diana Chu — 456 Maple Ave (composite 76, Warm, rank 6): 9-yr roof in 1.5" hail core — younger roof than the target profile (Age scores only 4) but neighborhood density (Maple Ridge cluster, 4 completed jobs within 800 ft) and active estimate-30d carry her into Warm. Cross-skill: she's the canonical "getting other quotes" case in follow-up-sequence's Example Output. The storm refresh gives Sam a legitimate Day-1 storm-context restart if she's still cold by the next outreach window. She sits just below the Hot line at 76, so she stays in the drip cadence rather than the call bank.

4. Out-of-Profile Flags

  • Out-of-area (excluded — outside service_area.zip_codes[]): 2 leads (Lewisville 75077, McKinney 75069). Listed in outputs/lead-scoring/2026-04-19-dfw-hail-out-of-area.csv with the "out of area — no licensed coverage in this ZIP" reason.
  • Storm repair, low ticket (weather maxed but estimate < target_profile.ticket_minimum $8k): 3 leads (Sara Odom 321 Pine repair, two single-slope repair-only inquiries from 75074). Flagged to the repair-crew queue rather than the main sales pipeline (repair scheduling handled outside this batch).
  • Age-unknown + no neighborhood context (flagged for field verification before call-bank dispatch): 2 leads (cold canvassing targets in 75075 where assessor data is missing — assigned size_est = 5 default score with field-verify flag).

5. Process artifacts written

  • outputs/lead-scoring/2026-04-19-dfw-hail-scored.md — this report
  • outputs/lead-scoring/2026-04-19-dfw-hail-scored.csv — full 25-row CSV for CRM import (composite + decomposition + recommended action columns)
  • outputs/lead-scoring/2026-04-19-dfw-hail-out-of-area.csv — 2-row out-of-area list
  • Routing handoffs fired: 4 🔥 → lead-response-automator (immediate outreach), 13 🟡 → follow-up-sequence (Warm storm-context Day 1 cadence), 1 🔥 active-leak (Pat Nguyen) → also storm-canvassing-prioritizer (Riverside cluster brief), 8 ⚪ → maintenance-plan-generator (long-term nurture)

Assumptions footer for this run

  • Profile defaulted to Storm-Response because the 4/18 hail event was within the 30-day active-trigger window; weights applied per the Storm-Response column
  • service_area.zip_codes[] resolved from config; 2 leads excluded as out-of-area
  • service_area.hail_zones[] includes 75070 + 75074 — Weather scores ≥6 even on rolling-12-month basis for these ZIPs
  • target_profile.roof_age_floor defaulted to 15 / .roof_age_premium to 20 / ticket_minimum to $8,000 — this shop's config.yml carries no target_profile block, so skill defaults were used and flagged
  • rates.average_job_value defaulted to $620/sq installed (asphalt arch baseline; base config carries per-line rates.* but no blended job value); Revenue scores computed from (squares × $620)
  • past_jobs.completed_addresses[] supplied from CRM export (not in base config); adjacency scoring exercised for ranks 1–9
  • Storm trigger applied to the whole batch (4/18 within the 30-day window); it lifts each lead's composite via the Weather weight, not by editing Recency — Marcus Bell keeps Recency 6 (active estimate 30–90d) and Lisa Fortuna keeps Recency 4 (old cold-list), and the storm still carries Lisa's composite to 84 (Hot)
  • Output format = 50–500 condensed (top-10 detailed in table + full decomposition, full 25 in CSV, narrative on the 4 Hot leads plus the notable Warm case) per batch-size auto-scaling rule
  • 2 leads with age + size both estimated were flagged for field verification before dispatch

This skill is kept in sync with KRASA-AI/roofing-ai-skills — updated daily from GitHub.