# Gtm Pipeline:contact Filter

> Classify and filter contacts from people-search output BEFORE enrichment. Rejects non-ICP contacts and ranks the rest using job tier, industry tier, location tier, company size, and ICP keyword scoring. Saves enrichment credits by only passing qualified contacts downstream. Input: contacts_found.csv. Output: contacts_filtered.csv. Also triggers on "filter contacts", "ICP filter", "rank contacts".

- **Type:** Skill
- **Install:** `agentstack add skill-vivekkhimani-gtm-tools-template-gtm-contact-filter`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [vivekkhimani](https://agentstack.voostack.com/s/vivekkhimani)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [vivekkhimani](https://github.com/vivekkhimani)
- **Source:** https://github.com/vivekkhimani/gtm-tools-template/tree/main/.agents/skills/gtm-contact-filter

## Install

```sh
agentstack add skill-vivekkhimani-gtm-tools-template-gtm-contact-filter
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Contact Filter

Classify and rank contacts from people-search output using a three-tier ICP scoring system. Rejects non-ICP contacts before enrichment to save credits.

**Read `.agents/skills/_shared/conventions.md` before executing.**

---

## When to Use

- After people-search, before people-enrichment
- Input: `csv/intermediate/contacts_found.csv`
- Output: `csv/intermediate/contacts_filtered.csv`
- Even small batches (10 contacts) benefit from ICP ranking

---

## Prerequisites — Company Data

Contact-filter classifies on company-level fields (`company_employee_count`, `company_hq_location`, `company_industry`, `company_specialities`) alongside each contact. These come from upstream skills.

| Source | Fields Provided | Cost |
|--------|----------------|------|
| PB LinkedIn Profile Scraper (`enrichWithCompanyData: true`) | employee_count, hq, industry, specialities | Free (PB plan + LinkedIn account) |
| PB Sales Navigator Account Scraper | employee_count, hq, industry, growth metrics | Free (PB plan + SN account) |
| PB LinkedIn Company Scraper | Full company page data | Free (PB plan + LinkedIn account) |
| FullEnrich Company Finder | employee_count, hq, industry | 0.25 credits/company |
| Pipe0 `companies:profiles:amplemarket@1` | employee_count, hq, industry | 2.00 credits/page (100 results) |
| Pipe0 `companies:profiles:crustdata@1` | employee_count, hq, industry | 2.00 credits/page (100 results) |

**Where company data typically comes from:**
- **Company-First workflow:** Already enriched in the company-enrichment step.
- **Persona workflow:** Run PB Profile Scraper with `enrichWithCompanyData: true` during/after people-search.
- **Standalone:** Run one of the API providers above before contact-filter.

**If company data is missing:** The filter falls back to job-title-only classification. It skips company-dependent dimensions (industry tier, location tier from HQ, size filter) and relies on `job_tier` and person location only.

See `_shared/conventions.md` for the canonical field name mapping (snake_case across all skills).

---

## Step 1 — Load ICP Configuration

Read `{client-slug}-gtm/context/icp.md` for client-specific tier definitions, thresholds, and sort order.

**If `icp.md` does not yet contain contact-filter configuration, ask the user:**

1. What are your target job titles / role keywords?
2. What seniority levels are you targeting? (manager, director, VP, C-level?)
3. What industries are highest priority? Lowest priority?
4. What geographies are highest priority?
5. What company size range? (min and max employee count)
6. Any ICP keyword signals to look for in company specialities?
7. Which dimensions matter most for ranking? (e.g. location first, then role, then industry)

Save the answers to `context/icp.md` under a `## Contact Filter` section.

---

## Classification System

### A. Job Tier (1–6, lower = better)

Fully parameterized — client defines role keywords. Example structure:

| Tier | Description | Example |
|------|-------------|---------|
| 1 | Primary target roles, mid-level seniority | RevOps Manager, Sales Ops Lead |
| 2 | Primary target roles, other seniority | RevOps IC, Head of RevOps |
| 3 | Leadership in target domain | VP Sales, Director of Revenue |
| 4 | General domain roles | Account Executive, Sales Manager |
| 5 | C-level (promote to tier 3 if company = hard_reject_threshold` (default: 6).

**Missing title:** tier 6 (Unknown). Default threshold rejects tier 6. To allow missing-title contacts through, set `hard_reject_threshold: 7` in your ICP config.

### B. Industry Tier (0–5, lower = better)

Client defines 5 industry tiers ranked by fit for their offering.

- Match logic: exact match first (full industry name), then keyword fallback
- Tier 0 = Unknown (`company_industry` missing) — sorted between tier 1 and tier 2
- Missing industry → tier 0, NOT rejected

### C. Location Tier (A–D, A = best)

Client defines geographic priority tiers.

- **Primary:** company HQ location (`company_hq_location` field — format: `"City, CC"`, extract country code `CC`)
- **Fallback:** person `location` field
- **Both missing:** tier D (lowest)
- Hard reject option: client can specify only tier A (or A+B) passes

### D. Company Size Filter

Client defines min and max `company_employee_count`.

- Outside range AND field is present → hard reject
- Field missing/empty → skip size filter, set `employee_count_missing: true`, deprioritize in sort

### E. ICP Keyword Scoring

Scan `company_specialities` for client-defined domain keywords.
Count matches → `icp_kw_score` (0 = no match, no penalty).
Missing specialities → `icp_kw_score = 0`.

---

## Hard Filters (Rejection Rules)

Prefer not to reject solely on missing data — use the `hard_reject_threshold` to control strictness per client. The exception is missing job title (tier 6), which is rejected at the default threshold; raise the threshold to 7 to keep them.

| Filter | Condition | Action |
|--------|-----------|--------|
| Job tier | >= hard_reject_threshold (default: 6) | Reject |
| Employee count | outside [min, max] AND field present | Reject |
| Location tier | worse than threshold AND field present | Reject (optional) |

---

## Soft Sort (Ranking)

Sort order is **configurable per client** — stored in `context/icp.md`.

**Default composite sort key** (used if no preference stated):
```
job_tier → industry_tier → company_loc_tier → person_loc_tier → employee_count_missing (penalty last) → icp_kw_score (desc) → follower_count (desc)
```

**Other valid orderings:**
- Geography-first: `company_loc_tier → job_tier → industry_tier → ...`
- Company-fit-first: `industry_tier → icp_kw_score → company_loc_tier → job_tier → ...`

Ask the user at setup which ordering fits their priorities. Store it in `context/icp.md`.

---

## Output Columns Added

The following columns are appended to all passed records:

| Column | Description |
|--------|-------------|
| `job_tier` | 1–6 (1 = best fit) |
| `persona` | Label for the role category |
| `seniority` | Detected seniority level |
| `ind_tier` | 0–5 (0 = unknown, 1 = best fit) |
| `ind_label` | Matched industry label |
| `comp_loc_tier` | A–D based on company HQ |
| `person_loc_tier` | A–D based on person location |
| `effective_loc_tier` | Best of comp_loc_tier / person_loc_tier |
| `employee_count_missing` | true/false |
| `icp_kw_score` | Count of ICP keyword matches in specialities |
| `priority_label` | Human-readable priority (e.g. "High", "Medium", "Low") |
| `sort_key` | Composite sort value used for ranking |

---

## Missing Data Handling

| Field | Missing Behavior |
|-------|-----------------|
| `job_title` | tier 6 (Unknown) — rejected at default threshold (6); raise to 7 to keep |
| `company_industry` | tier 0 (Unknown) — sorted between tier 1 and tier 2 |
| `company_hq_location` | Fall back to person `location`; if both missing → tier D |
| `company_employee_count` | Skip size filter, flag `employee_count_missing: true`, deprioritize in sort |
| `company_specialities` | `icp_kw_score = 0` (no penalty) |
| Follower count | 0 (lowest tiebreaker priority) |

**Default policy:** Don't hard-reject on missing data unless the corresponding tier is explicitly above threshold. Adjust `hard_reject_threshold` per client.

---

## Execution Protocol

### 1. Read ICP Configuration
Load from `context/icp.md`. If contact-filter section is missing, ask the setup questions above and save the answers.

### 2. Classify All Contacts
Run the classification logic over all rows in `contacts_found.csv`. Add the output columns listed above.

### 3. Apply Hard Filters
Reject rows that fail hard filter conditions. Log rejection reasons.

### 4. Sort Passed Records
Apply the client's configured sort order (or default) to rank passed contacts.

### 5. Print Run Summary
```
## Contact Filter Summary
- Input: {n_input} contacts
- Hard rejected (job tier): {n_job_rejected}
- Hard rejected (employee count): {n_size_rejected}
- Hard rejected (location): {n_loc_rejected}
- Passed: {n_passed} contacts

## Tier Distribution (passed contacts)
Job tiers: tier1={x}, tier2={y}, tier3={z}, tier4={w}
Industry tiers: tier1={x}, tier2={y}, tier3={z}, ...
Location tiers: A={x}, B={y}, C={z}, D={w}

## Top 10 by Priority
[name | company | job_title | job_tier | ind_tier | loc_tier | icp_kw_score]
```

### 6. Write Output
Save passed contacts (ranked) to `csv/intermediate/contacts_filtered.csv`.
All input columns preserved. Output columns appended.

---

## ICP Configuration Format (in context/icp.md)

```markdown
## Contact Filter

### Job Tier Definitions
- Tier 1 (Primary mid-level): keywords = [revops, sales ops, revenue operations], seniority = [manager, senior, lead]
- Tier 2 (Primary other): keywords = [revops, sales ops], seniority = [head of, director, IC]
- Tier 3 (Leadership): keywords = [sales, revenue, commercial], seniority = [VP, director, head of]
- Tier 4 (Domain): keywords = [account, sales, business dev], seniority = any
- Tier 5 (C-level): keywords = [CRO, CSO, chief revenue, chief sales]
- Hard reject (Tier 6): keywords = [student, intern, junior, coordinator]
- C-level promotion threshold: 50 employees
- Hard reject threshold: tier >= 6

### Industry Tiers
- Tier 1 (Best fit): [B2B services, manufacturing, logistics, wholesale, consulting]
- Tier 2 (Good fit): [retail, hospitality, consumer-facing with B2B sales]
- Tier 3 (Neutral): [SaaS, software, IT services]
- Tier 4 (Low fit): [deeptech, aerospace, biotech]
- Tier 5 (Hard): [finance, healthcare, pharma, legal, government]

### Location Tiers
- Tier A: [DE, AT, CH, NL, BE, LU, DK, SE, NO, FI, GB, IE]
- Tier B: [FR, ES, IT, PL, CZ, HU, RO, PT, GR, BG, HR, SK, SI]
- Tier C: [US, CA, AU, NZ]
- Tier D: all other
- Hard reject threshold: none (all tiers pass, used for sorting only)

### Company Size Filter
- Min: 10 employees
- Max: 500 employees

### ICP Keywords (company specialities)
- keywords = [automation, workflow, crm, sales enablement, revops, salestech]

### Sort Order
job_tier → industry_tier → comp_loc_tier → person_loc_tier → employee_count_missing → icp_kw_score (desc)
```

---

## Reference Patterns

The classification logic follows these patterns:
- `classify_job(title, employee_count)` → returns `(tier, persona, seniority)` based on keyword matching + seniority detection + C-level promotion rule
- Industry: exact set match first, keyword fallback
- Location: parse country code from `"City, CC"` format in HQ field
- Composite sort key as tuple: `(job_tier, ind_tier, loc_tier_ord, ...)` — Python's natural tuple ordering does the ranking

---

## What's Missing (To Document)

- Benchmark: credit savings (% reduction) from filtering before enrichment across different client ICPs
- Native integration with downstream tooling (n8n, custom scripts)

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [vivekkhimani](https://github.com/vivekkhimani)
- **Source:** [vivekkhimani/gtm-tools-template](https://github.com/vivekkhimani/gtm-tools-template)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-vivekkhimani-gtm-tools-template-gtm-contact-filter
- Seller: https://agentstack.voostack.com/s/vivekkhimani
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
