Skip to content
Back to skills

Lead Scorer

ASecurity

Score raw leads as HOT/WARM/COOL based on config-driven weights from agency.config.json

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 10, 2026
developmentgo

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add ekatasingh1107/b2b-gtm-skills --skill lead-scorer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Lead Scorer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Lead Scorer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ekatasingh1107-lead-scorer/badge)](https://www.skillsdirectory.com/skills/ekatasingh1107-lead-scorer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: lead-scorer
description: Score raw leads as HOT/WARM/COOL based on config-driven weights from agency.config.json
tags: [scoring, lead-qualification, prioritization]
---

# Lead Scorer

Scores inbound or scraped leads using a multi-factor algorithm. All weights, thresholds, and bonuses are read from `agency.config.json` so the scoring adapts to any agency without code changes.

## Prerequisites

- `agency.config.json` exists at the repo root with a `scoring` section containing: `platform_weights`, `hiring_signals`, `budget_tiers`, `recency_bonuses`, `market_boosts`, `thresholds`
- `icp` section with `primary_keywords`, `secondary_keywords`, `intent_keywords`, `negative_keywords`
- Lead data in structured format (JSON object or array)

## Phase 0: Intake

1. Read `agency.config.json` from the project root.
2. Extract:
   - `scoring.platform_weights` -- map of platform name to base score
   - `scoring.hiring_signals` -- array of phrases that indicate active hiring/buying
   - `scoring.budget_tiers` -- array of `{min, points}` sorted descending by `min`
   - `scoring.recency_bonuses` -- map of time window to bonus points (e.g., `"24h": 15`)
   - `scoring.market_boosts` -- map of market/country to bonus points
   - `scoring.contact_surface_bonus` -- bonus points for multi-channel reachability
   - `scoring.thresholds` -- `{hot, warm}` cutoffs
   - `icp.primary_keywords` -- high-relevance service keywords (+10 each match)
   - `icp.secondary_keywords` -- medium-relevance keywords (+5 each match)
   - `icp.intent_keywords` -- buying-intent phrases (+10 each match)
   - `icp.negative_keywords` -- disqualifiers (score = 0, immediately reject)
   - `outreach.daily_targets` -- { company_leads: 5, gig_leads: 20, total: 25 }
   - `outreach.geo_split` -- { international_pct: 75, india_pct: 25 }
3. Accept the lead(s) to score. Each lead should have as many of these fields as available:
   - `title` -- the post/gig/job title or subject line
   - `description` -- the full text body
   - `platform` -- where the lead was found
   - `budget` -- numeric budget amount (if available)
   - `currency` -- budget currency
   - `posted_at` -- ISO timestamp or relative time string
   - `country` -- country of the lead or company
   - `url` -- source URL
   - `lead_type` -- "gig" or "company" (from signal-scanner classification)
   - `geo_bucket` -- "international" or "india" (from signal-scanner)

## Phase 1: Negative Keyword Check

For each lead, scan `title` and `description` against every entry in `icp.negative_keywords` (case-insensitive substring match).

- If ANY negative keyword matches: **score = 0**, tier = `REJECTED`, skip all remaining phases for this lead.

## Phase 1.5: Lead Type Awareness

Apply different scoring standards based on `lead_type`:

### Company Leads (target: 5/day)
- Apply FULL scoring algorithm (all phases below)
- Extremely high quality bar: only HOT leads pass
- Company leads must score >= thresholds.hot to be included

### Gig Leads (target: 20/day)
- Apply simplified scoring: platform base + keyword match + budget + recency
- Skip hiring signal bonus (gigs ARE the hiring signal)
- Higher volume: HOT threshold is sufficient
- Must have clear buying signal and budget to qualify

## Phase 2: Platform Base Score

Look up `lead.platform` in `scoring.platform_weights`.

- If the platform exists in the map: `base_score = platform_weights[platform]`
- If the platform is not in the map: `base_score = 10` (default floor)

## Phase 3: Keyword Match Scoring

Combine `title` + `description` into a single lowercase text blob. For each keyword list, count distinct keyword matches (not occurrences):

- **Primary keywords**: For each match, add +10 points. Cap at 3 matches (max +30).
- **Secondary keywords**: For each match, add +5 points. Cap at 3 matches (max +15).
- **Intent keywords**: For each match, add +10 points. Cap at 2 matches (max +20).

`keyword_score = primary_points + secondary_points + intent_points`

## Phase 4: Hiring Signal Bonus

Scan the text blob against `scoring.hiring_signals` (case-insensitive).

- If ANY hiring signal matches: add +20 points (one-time bonus, not per-match).
- **Note**: For gig leads (`lead_type == "gig"`), skip this phase. Gigs ARE the hiring signal, so the bonus is already baked into the platform base score.

## Phase 5: Budget Tier Scoring

If `lead.budget` is a number > 0:

- Iterate `scoring.budget_tiers` from highest `min` to lowest.
- The first tier where `lead.budget >= tier.min` gives `budget_points = tier.points`.
- If no budget data: `budget_points = 0`.

## Phase 6: Recency Bonus

Calculate the age of the lead from `lead.posted_at` relative to now.

- If age <= 24 hours: add `scoring.recency_bonuses["24h"]` points
- Else if age <= 48 hours: add `scoring.recency_bonuses["48h"]` points
- Else if age <= 72 hours: add `scoring.recency_bonuses["72h"]` points
- Else: 0 points

If `posted_at` is not available: 0 points.

## Phase 7: Market Boost

Look up `lead.country` in `scoring.market_boosts` (case-insensitive, also check common abbreviations like "US" for "United States").

- If found: add the boost points.
- If not found: 0 points.

## Phase 7.5: Contact Surface Bonus

If the lead includes a `contact_surfaces` object:
- `channel_count >= 3`: add `scoring.contact_surface_bonus.channels_3_plus` points (default 15)
- `channel_count == 2`: add `scoring.contact_surface_bonus.channels_2` points (default 5)
- `channel_count <= 1` or missing: 0 points

This bonus rewards leads reachable through multiple channels, enabling the full multi-channel outreach cadence.

## Phase 8: Final Score and Tier Assignment

```
total_score = base_score + keyword_score + hiring_bonus + budget_points + recency_bonus + market_boost + surface_bonus
```

Assign tier using `scoring.thresholds`:
- `total_score >= thresholds.hot` => **HOT**
- `total_score >= thresholds.warm` => **WARM**
- `total_score < thresholds.warm` => **COOL**

## Phase 8.5: Geo Split Enforcement

After scoring all leads, enforce the 75/25 geo split from config:

1. Separate scored leads into two pools: international and india
2. Target: ~75% international (~19 of 25 leads), ~25% India (~6 of 25 leads)
3. Within each pool, rank by score descending
4. Select top N from each pool to hit the target split
5. If one pool is short, allow the other pool to fill remaining slots

For company leads specifically (5/day target):
- ~4 international, ~1 India

For gig leads (20/day target):
- ~15 international, ~5 India

Flag any lead that was excluded due to geo split enforcement:
`"excluded_reason": "geo_split_cap_reached"`

## Phase 9: Output

Return a JSON array of scored leads:

```json
[
  {
    "url": "https://...",
    "platform": "Freelancer",
    "title": "Need Shopify developer for store redesign",
    "score": 75,
    "tier": "HOT",
    "lead_type": "gig",
    "geo_bucket": "international",
    "breakdown": {
      "platform_base": 45,
      "keyword_matches": {
        "primary": ["shopify developer", "shopify redesign"],
        "secondary": ["ecommerce redesign"],
        "intent": ["need a developer"]
      },
      "keyword_score": 30,
      "hiring_signal": true,
      "hiring_bonus": 20,
      "budget_points": 0,
      "recency_bonus": 0,
      "market_boost": 0,
      "surface_bonus": 15,
      "channel_count": 4
    },
    "reject_reason": null
  }
]
```

For rejected leads, include `"reject_reason": "Matched negative keyword: 'i am a shopify developer'"` and `"tier": "REJECTED"`.

## Phase 10: Log

If the CRM is configured, write scored leads to the CRM using the `crm-writer` skill. Log: url, platform, title, score, tier, lead_type, geo_bucket, scored_at timestamp.

Summary format:

```
Scored {N} leads:
- HOT: {count}, WARM: {count}, COOL: {count}, REJECTED: {count}
Geo split: X% international, Y% India (target: 75/25)
Lead mix: N company leads, N gig leads (target: 5/20)
```

## Example Usage

Trigger phrases:
- "Score these leads"
- "Qualify this lead list"
- "Run lead scoring on these results"
- "Which of these leads are hot?"

```
User: Score these leads from today's signal scan
Assistant: [reads agency.config.json, applies scoring algorithm with lead type awareness and geo split enforcement, returns sorted JSON with HOT leads first]
```

Files in this skill

  • SKILL.md8.1 KB
  • configs/example.json2.9 KB
  • skill.meta.json374 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…