Reference file

Coverage and icp

coverage-and-icp.md

The coverage gate and the ICP questions

Run in this order. Coverage first, always — it decides which questions are worth asking.

Coverage

python3 scripts/build.py --coverage leads.json

Reports fill rates per field and names which criteria the data cannot support.

Field Needed for Below threshold
Job title Seniority and function detection Classification degrades; most leads land in review
Company name Company-based exclusion Exclusion is unreliable
Bio Catching people whose company and email are both empty Own-team and competitor exclusion will leak
Work email Domain-based exclusion Job-changers slip through
Location Geography filtering Geo criteria cannot be applied
Industry Industry filtering Industry criteria cannot be applied

The bio row is the one people skip. It's the only field that catches a colleague whose company field is blank and who has no email on file — and that's the exact profile of someone imported from a social export.

Enrichment

When the gate flags gaps, offer profile enrichment, quote the exact cost, and wait for an explicit yes.

The coverage output returns leads_needing_enrichment — the number of rows actually missing at least one of the fields that came back insufficient, not the size of the list. On an audience that is mostly complete those are very different numbers, and quoting the total overstates the cost of the fix.

Enrichment aimed at finding email addresses is a different, more expensive product and contributes nothing to ICP scoring. If someone asks for it here, say plainly that it serves deliverability rather than filtering, and let them decide.

If enrichment is declined, proceed — and state which criteria were dropped and that exclusion is now best-effort.

The ICP questions

Ask after coverage, and don't offer criteria the data can't support.

# Question Feeds
1 Which seniority levels qualify? Founder/C-level · VP/Head of · Manager/Lead · IC icp.seniority
2 Which functions? Sales · Growth/Marketing · RevOps/GTM · Partnerships · Product/Tech icp.functions
3 Do founders qualify regardless of stated function? founder_qualifies_regardless_of_function
4 Any geography or industry constraint? (only if coverage supports it) icp.locations, icp.industries
5 Who is excluded outright — own company, competitors, agencies? exclusions

Question 3 carries more weight than it looks. Most founder titles state no function at all: Co-Founder, Fondatrice, Founder @ Stealth. If founders don't auto-qualify, every one of them lands in review. On a real 150-lead list this single answer moved about fifteen leads. Ask it explicitly and explain the trade-off rather than assuming.

If the ICP comes back vague — "good leads", "decision makers" — push once for specifics. A vague ICP produces an enormous review bucket, which is the problem this skill exists to remove.

The spec

build.py reads one JSON object:

{
  "icp": {
    "seniority": ["founder_c", "vp_head", "manager_lead"],
    "functions": ["sales", "growth_marketing", "revops"],
    "founder_qualifies_regardless_of_function": false,
    "locations": ["France", "Paris"],
    "industries": ["saas"]
  },
  "exclusions": {
    "domains": ["yourcompany.com"],
    "companies": ["Your Company"],
    "keywords": ["competitor-a", "competitor-b"]
  },
  "leads": [
    {
      "leadId": "...", "jobTitle": "...", "companyName": "...",
      "proEmail": "...", "shortBio": "...", "location": "...", "industry": "..."
    }
  ]
}

Include locations and industries only when coverage supports them. Each lead needs an id, or both a first and last name.

Then:

python3 scripts/build.py spec.json > pass1.json

It refuses invalid input rather than emitting a best-effort sort. When it errors, fix the spec — never work around it by classifying by hand.

For pass 2, pass {"result": <pass1 output>, "overrides": [{"_key": "...", "bucket": "...", "reason": "..."}]} to --adjudicate. It re-runs the same reconciliation and rejects an override on a lead that doesn't exist, an invalid bucket, a duplicate, or a reclassification with no substantive reason.

Two spec traps

Geography matches on substring, so list the real variants. Enrichment writes "Greater Paris Metropolitan Region" and "Greater Lyon Area" — neither contains the string "France", so locations: ["France"] silently drops both. Glance at the actual location values before writing the spec and include the metros and regions that appear.

Industry expands automatically — don't hand-list variants. Pass a canonical bucket (saas, software, tech, fintech, healthtech, ecommerce) and the script expands it to the platform labels that mean the same thing, so ["saas"] also catches Software Development, Technology, Information and Internet and IT Services. This matters: real software companies routinely get labelled "Technology, Information and Internet" and would otherwise be dropped. For a bucket outside the synonym map, list the variants yourself, or let the geo/industry queue flag surface the misses in pass 2.

The buckets

Bucket Meaning
match Seniority and function both in ICP, constraints satisfied
review The engine declined to guess — unclear title, missing function, absent geo/industry data
no_match Out of ICP, or a noise title (student, intern, open-to-work, investor)
excluded Own team, competitor, or a listed exclusion

Every lead lands in exactly one, with a reason, and the counts reconcile against the input.

A review bucket around a third is normal on thin data. Say so plainly and name the cause rather than presenting it as a finding.

Self-test

python3 scripts/build.py --test

Covers seniority precedence, multi-function collection, bio fallback, each exclusion leak mode, the short-token guard, industry synonym expansion, count reconciliation, and every refusal path.