← Back to Skills Marketplace
maverick-software

Lead Scorer

by maverick-software · GitHub ↗ · v1.0.0
cross-platform ⚠ suspicious
372
Downloads
0
Stars
0
Active Installs
1
Versions
Install in OpenClaw
/install lead-scorer-pro
Description
Score a website lead 0–100 based on audit signals from the website-auditor skill, assign a priority tier (Hot/Warm/Lukewarm/Cold), and write the result to a...
README (SKILL.md)

Lead Scorer Skill

Takes a completed audit dict → applies scoring rubric → assigns tier → writes row to Google Sheet.

Scoring Rubric (0–100 scale)

Higher score = hotter lead = more likely to need a new website.

SCORING_RUBRIC = [
    # Signal                            Condition                           Points
    ("dead_site",        lambda a: a.get("status_code") in ("DEAD","TIMEOUT","SSL_ERROR"), 40),
    ("copyright_5plus",  lambda a: (a.get("years_outdated") or 0) >= 5,    35),
    ("pagespeed_low",    lambda a: 0 \x3C (a.get("pagespeed_mobile") or 101) \x3C 50, 30),
    ("copyright_3to4",   lambda a: 3 \x3C= (a.get("years_outdated") or 0) \x3C 5, 25),
    ("no_ssl",           lambda a: not a.get("has_ssl"),                    25),
    ("not_mobile",       lambda a: not a.get("is_mobile_friendly"),         20),
    ("outdated_cms",     lambda a: a.get("has_outdated_cms"),               20),
    ("no_contact_found", lambda a: not a.get("primary_email") and not a.get("primary_phone"), 20),
    ("pagespeed_mid",    lambda a: 50 \x3C= (a.get("pagespeed_mobile") or 101) \x3C 70, 15),
    ("table_layout",     lambda a: "table_layout" in (a.get("design_signals") or []), 15),
    ("copyright_1to2",   lambda a: 1 \x3C= (a.get("years_outdated") or 0) \x3C 3, 10),
    ("no_open_graph",    lambda a: "no_open_graph" in (a.get("design_signals") or []), 10),
    ("flash_detected",   lambda a: "flash_detected" in (a.get("design_signals") or []), 20),
    ("uses_frames",      lambda a: "uses_frames" in (a.get("design_signals") or []), 20),
    ("no_meta_desc",     lambda a: "no_meta_description" in (a.get("design_signals") or []), 5),
    ("no_favicon",       lambda a: "no_favicon" in (a.get("design_signals") or []), 5),
    ("font_tags",        lambda a: "font_tags" in (a.get("design_signals") or []), 10),
    ("heavy_inline",     lambda a: "heavy_inline_styles" in (a.get("design_signals") or []), 8),
]

Scoring Function

def score_lead(audit: dict) -> dict:
    """
    Apply rubric to audit dict.
    Returns audit dict + score + tier + issues list + tier emoji.
    """
    score = 0
    issues = []
    matched_rules = []
    
    for rule_name, condition, points in SCORING_RUBRIC:
        try:
            if condition(audit):
                score += points
                matched_rules.append(f"{rule_name} (+{points})")
                issues.append(ISSUE_LABELS.get(rule_name, rule_name))
        except Exception:
            pass
    
    # Cap at 100
    score = min(score, 100)
    
    # Assign tier
    if score >= 80:
        tier = "🔥 Hot"
        tier_code = "hot"
    elif score >= 50:
        tier = "🟡 Warm"
        tier_code = "warm"
    elif score >= 25:
        tier = "🔵 Lukewarm"
        tier_code = "lukewarm"
    else:
        tier = "⚪ Cold"
        tier_code = "cold"
    
    return {
        **audit,
        "lead_score": score,
        "tier": tier,
        "tier_code": tier_code,
        "issues": issues,
        "score_breakdown": matched_rules
    }

# Human-readable issue labels
ISSUE_LABELS = {
    "dead_site":        "Site is dead/unreachable",
    "copyright_5plus":  f"Copyright {'{years}'} years old",
    "copyright_3to4":   "Copyright 3–4 years old",
    "copyright_1to2":   "Copyright 1–2 years old",
    "pagespeed_low":    "PageSpeed score under 50 (mobile)",
    "pagespeed_mid":    "PageSpeed score 50–69 (mobile)",
    "no_ssl":           "No SSL / HTTPS",
    "not_mobile":       "Not mobile responsive",
    "outdated_cms":     "Outdated CMS or tech stack",
    "no_contact_found": "No contact info found on site",
    "table_layout":     "Table-based layout (pre-2010 design)",
    "no_open_graph":    "No social meta tags",
    "flash_detected":   "Flash / Silverlight detected",
    "uses_frames":      "Uses HTML frames",
    "no_meta_desc":     "Missing meta description",
    "no_favicon":       "No favicon",
    "font_tags":        "Font tags detected (ancient HTML)",
    "heavy_inline":     "Heavy inline CSS styles",
}

Google Sheets Integration

import gspread
from google.oauth2.service_account import Credentials
from datetime import datetime
import os

SHEET_COLUMNS = [
    "Date Found", "Business Name", "URL", "Lead Score", "Tier",
    "Primary Email", "All Emails", "Phone", "Copyright Year",
    "Tech Stack", "PageSpeed (Mobile)", "Has SSL", "Mobile Friendly",
    "Issues Found", "Score Breakdown", "Status", "Notes"
]

def get_sheet(sheet_name: str = None, creds_file: str = None):
    """Authenticate and return the target Google Sheet."""
    sheet_name = sheet_name or os.environ.get("GOOGLE_SHEET_NAME", "Website Leads")
    creds_file = creds_file or os.environ.get("GOOGLE_CREDS_FILE", "credentials.json")
    
    scopes = [
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/drive"
    ]
    creds = Credentials.from_service_account_file(creds_file, scopes=scopes)
    client = gspread.authorize(creds)
    
    try:
        sheet = client.open(sheet_name).sheet1
    except gspread.SpreadsheetNotFound:
        # Create the sheet if it doesn't exist
        spreadsheet = client.create(sheet_name)
        spreadsheet.share(None, perm_type="anyone", role="writer")
        sheet = spreadsheet.sheet1
        # Write headers
        sheet.append_row(SHEET_COLUMNS)
        print(f"Created new sheet: {sheet_name}")
    
    return sheet

def ensure_headers(sheet):
    """Make sure the header row exists."""
    first_row = sheet.row_values(1)
    if not first_row or first_row[0] != "Date Found":
        sheet.insert_row(SHEET_COLUMNS, index=1)

def lead_to_row(lead: dict) -> list:
    """Convert a scored lead dict to a sheet row."""
    return [
        datetime.now().strftime("%Y-%m-%d"),
        lead.get("business_name", ""),
        lead.get("url", ""),
        lead.get("lead_score", 0),
        lead.get("tier", ""),
        lead.get("primary_email", ""),
        ", ".join(lead.get("emails", [])),
        lead.get("primary_phone", ""),
        lead.get("copyright_year", ""),
        ", ".join(lead.get("tech_stack", [])[:3]),          # Top 3 techs
        lead.get("pagespeed_mobile", ""),
        "✅" if lead.get("has_ssl") else "❌",
        "✅" if lead.get("is_mobile_friendly") else "❌",
        " | ".join(lead.get("issues", [])[:5]),             # Top 5 issues
        " | ".join(lead.get("score_breakdown", [])[:5]),
        "New",
        ""
    ]

def write_lead_to_sheet(lead: dict, sheet=None) -> bool:
    """Score the lead and write it to the Google Sheet."""
    if sheet is None:
        sheet = get_sheet()
    ensure_headers(sheet)
    
    scored = score_lead(lead)
    row = lead_to_row(scored)
    
    try:
        sheet.append_row(row, value_input_option="USER_ENTERED")
        return True
    except Exception as e:
        print(f"Sheet write error: {e}")
        return False

def write_leads_batch(leads: list[dict], sheet=None) -> int:
    """Write multiple leads at once. Returns count written."""
    if sheet is None:
        sheet = get_sheet()
    ensure_headers(sheet)
    
    rows = []
    for lead in leads:
        scored = score_lead(lead)
        if scored.get("lead_score", 0) >= 0:  # Always write (filter upstream)
            rows.append(lead_to_row(scored))
    
    if rows:
        sheet.append_rows(rows, value_input_option="USER_ENTERED")
    
    return len(rows)

Apply Conditional Formatting (Color by Tier)

def apply_conditional_formatting(spreadsheet_id: str, creds_file: str = None):
    """
    Apply color-coding to the sheet based on Lead Score in column D.
    Requires google-api-python-client (pip install google-api-python-client).
    """
    from googleapiclient.discovery import build
    from google.oauth2.service_account import Credentials
    
    creds_file = creds_file or os.environ.get("GOOGLE_CREDS_FILE", "credentials.json")
    scopes = ["https://www.googleapis.com/auth/spreadsheets"]
    creds = Credentials.from_service_account_file(creds_file, scopes=scopes)
    service = build("sheets", "v4", credentials=creds)
    
    rules = [
        # 🔥 Hot (≥80) — red background
        {"ranges": [{"sheetId": 0, "startRowIndex": 1, "endRowIndex": 1000,
                     "startColumnIndex": 0, "endColumnIndex": 17}],
         "booleanRule": {
             "condition": {"type": "NUMBER_GREATER_THAN_EQ",
                           "values": [{"userEnteredValue": "80"}]},
             "format": {"backgroundColor": {"red": 1.0, "green": 0.8, "blue": 0.8}}
         }},
        # 🟡 Warm (50–79) — yellow background
        {"ranges": [{"sheetId": 0, "startRowIndex": 1, "endRowIndex": 1000,
                     "startColumnIndex": 0, "endColumnIndex": 17}],
         "booleanRule": {
             "condition": {"type": "NUMBER_BETWEEN",
                           "values": [{"userEnteredValue": "50"}, {"userEnteredValue": "79"}]},
             "format": {"backgroundColor": {"red": 1.0, "green": 0.95, "blue": 0.7}}
         }},
        # 🔵 Lukewarm (25–49) — blue background
        {"ranges": [{"sheetId": 0, "startRowIndex": 1, "endRowIndex": 1000,
                     "startColumnIndex": 0, "endColumnIndex": 17}],
         "booleanRule": {
             "condition": {"type": "NUMBER_BETWEEN",
                           "values": [{"userEnteredValue": "25"}, {"userEnteredValue": "49"}]},
             "format": {"backgroundColor": {"red": 0.8, "green": 0.9, "blue": 1.0}}
         }},
    ]
    
    # The "D" column (index 3) is used for conditional formatting range
    body = {"requests": [{"addConditionalFormatRule": {"rule": r, "index": i}}
                         for i, r in enumerate(rules)]}
    service.spreadsheets().batchUpdate(spreadsheetId=spreadsheet_id, body=body).execute()
    print("Conditional formatting applied ✅")

Full Pipeline Usage Example

# Putting it all together
from lead_list_builder import run_pipeline

results = run_pipeline(
    niche="landscaping",
    city="Portland OR",
    limit=25,
    min_score=30,
    sheet_name="Portland Landscaping Leads"
)

# Output:
# ✅ Scan complete.
# URLs scanned: 38
# Leads written: 25
# 🔥 Hot: 8 | 🟡 Warm: 11 | 🔵 Lukewarm: 6 | ⚪ Cold: 13

Score Interpretation Guide

Score Tier Meaning Action
80–100 🔥 Hot Site is dead, ancient, or totally broken Call or email today
50–79 🟡 Warm Multiple serious issues, clear need Follow up this week
25–49 🔵 Lukewarm Some issues, may be open to upgrade Low-priority outreach
0–24 ⚪ Cold Site is decent, not a strong prospect Skip or archive
Usage Guidance
This skill mostly does what it says (score audits and write to Google Sheets) but has some red flags you should address before installing: - Resolve the metadata mismatch: SKILL.md expects GOOGLE_SHEET_NAME and GOOGLE_CREDS_FILE but registry metadata lists none—confirm which env vars the runtime will actually need. - Do not give the skill a broad Google service-account file. Create a minimal service account with only the Drive/Sheets scopes required, and avoid using a project owner or broad credentials. - Remove or change spreadsheet.share(None, perm_type='anyone', role='writer'): making the sheet writable by anyone risks exposing and allowing modification of your lead data. Prefer sharing with a specific service account email or an internal group, or omit auto-sharing entirely. - Because this is instruction-only, ensure the runtime environment installs gspread and google-auth from official PyPI packages in a controlled environment. - Audit the rest of the (truncated) SKILL.md/code before use — the provided file was truncated, so there may be additional behavior not visible. If possible, ask the publisher for the full SKILL.md and confirm no other external endpoints or credential accesses are present. Given these issues, do not provide production credentials or sensitive data to this skill until the above items are addressed.
Capability Analysis
Type: OpenClaw Skill Name: lead-scorer-pro Version: 1.0.0 The skill is classified as suspicious due to a critical vulnerability in the `get_sheet` function within `SKILL.md`. If the specified Google Sheet does not exist, the code creates a new spreadsheet and immediately shares it with `perm_type="anyone", role="writer"`, making it publicly writable. This could lead to unauthorized data modification or leakage of the lead scoring data, which may contain sensitive business information. While the core functionality of scoring leads and writing to Google Sheets is aligned with the stated purpose, this default public sharing poses a significant security risk.
Capability Assessment
Purpose & Capability
The skill's logic (applying a scoring rubric to an audit dict and writing results to Google Sheets) matches the name/description. However the registry-level metadata earlier said no required env vars while SKILL.md both states the skill 'Requires GOOGLE_SHEET_NAME and GOOGLE_CREDS_FILE' and lists them as optionalEnv — that's an inconsistency the user should resolve before trusting the skill.
Instruction Scope
SKILL.md instructs the agent to read a Google service-account credentials file (via Credentials.from_service_account_file) and to create/open a spreadsheet. The code calls spreadsheet.share(None, perm_type='anyone', role='writer'), which makes the sheet publicly writable — this is unexpected for a lead-scoring tool and risks data exposure and tampering. The skill also accesses environment variables (GOOGLE_SHEET_NAME/GOOGLE_CREDS_FILE) that the registry metadata did not mark as required; the SKILL.md is the authoritative runtime instruction but the mismatch is concerning. Some SKILL.md content is truncated in the provided file, so there may be additional instructions not visible.
Install Mechanism
This is instruction-only (no install spec, no code files). SKILL.md declares Python packages (gspread, google-auth) as required, but no automated install step is provided. That is low risk from an automatic-install perspective, but operators must ensure the runtime environment has those packages installed from trusted sources.
Credentials
Requesting a Google service-account credential file and sheet name is proportional to the stated goal (writing to Google Sheets), but the credential is highly sensitive. The skill as-written will require a service-account JSON on disk or an env var path; users must not supply broad-scope credentials. Also the code's auto-sharing behavior elevates the sensitivity risk because supplying credentials plus this code could make private lead data publicly writable.
Persistence & Privilege
The skill does not request always:true or any elevated platform privileges. It is user-invocable and allows autonomous invocation (platform default). It does not request persistent installation or modification of other skills in the provided content.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install lead-scorer-pro
  3. After installation, invoke the skill by name or use /lead-scorer-pro
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.0
Scores website leads 0-100 with an 18-point weighted rubric, assigns Hot/Warm/Lukewarm/Cold tiers, and writes color-coded rows to Google Sheets via gspread. Final step in the lead list building pipeline.
Metadata
Slug lead-scorer-pro
Version 1.0.0
License
All-time Installs 0
Active Installs 0
Total Versions 1
Frequently Asked Questions

What is Lead Scorer?

Score a website lead 0–100 based on audit signals from the website-auditor skill, assign a priority tier (Hot/Warm/Lukewarm/Cold), and write the result to a... It is an AI Agent Skill for Claude Code / OpenClaw, with 372 downloads so far.

How do I install Lead Scorer?

Run "/install lead-scorer-pro" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Lead Scorer free?

Yes, Lead Scorer is completely free (open-source). You can download, install and use it at no cost.

Which platforms does Lead Scorer support?

Lead Scorer is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created Lead Scorer?

It is built and maintained by maverick-software (@maverick-software); the current version is v1.0.0.

💬 Comments