# Voc Profiler

> Enrich a raw Voice of Customer signal (a quote, complaint, request or praise from a call, chat message, meeting note, review, survey or ticket) with CRM context — ICP fit score, segment, revenue tier, lifecycle stage. Flags missing metadata explicitly so weighted decisions never run on anonymous noise. Trigger on /voc-profiler, "profile this quote", "who said this and do they matter".

- Skill: `aatirs-vault/voc-profiler` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aatirs-vault/voc-profiler`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aatirs-vault/voc-profiler/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- Author: Aatirs-Vault (https://skillmd.com/u/aatirs-vault)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aatirs-vault/voc-profiler

---


# /voc-profiler — Attach Weight to a Signal

A passing complaint from a customer paying you $500K is not the same input as the same sentence
from a free-tier trial that churned in week two. Most VoC tooling flattens that distinction and
then produces a ranked list of "top customer pains" that is really a list of *loudest* pains.

This skill attaches the context that makes weighting possible, and refuses to guess when the
context is missing.

## Requires

| What | Value |
|---|---|
| CRM | Any CRM with an MCP server or API. HubSpot is the reference implementation; see "Other CRMs" below. |
| Config | `config/thresholds.json` (tiers, ICP rubric), `context/product-context.md` (your CRM property names) |
| Cost | CRM API calls only. Free on every major plan. |

**Works without a CRM.** Every signal comes back tier `U` (unattributed), the pipeline still
runs, and the synthesis step tells you honestly that nothing was weighted. That is a legitimate
way to start.

---

## Input

One of:
- A quote plus its source (`"the export takes forever" — chat, #wins, 2026-04-22`)
- A path to a call transcript
- A path to a meeting note
- A review record from `outputs/voc/sources/reviews/`
- A survey response or support ticket excerpt

If the source channel is missing, ask once. If the company is missing, set
`attribution: anonymous` and continue — do not stall the pipeline on one unattributable quote.

---

## Execution

### Step 1 — Extract, do not paraphrase

Pull from the source:

| Field | Rule |
|---|---|
| `verbatim` | **Exact.** No cleanup, no grammar fixes, no trimming for length. This is the artifact everything downstream depends on. |
| `company` | From file metadata, note title, participant domain, or an explicit mention |
| `speaker_role` | Title if available |
| `speaker_affiliation` | **Must be external.** If the speaker is internal or unlabelled, stop and flag it. |
| `date` | ISO |
| `channel` | `call` \| `chat` \| `notes` \| `review` \| `crm_email` \| `survey` \| `ticket` |

**The affiliation check is not optional.** The most common silent failure in VoC work is a
pipeline that confidently reports your own sales team's framing back to you as customer voice,
because a transcript had no speaker labels and everything got treated as signal.

### Step 2 — Resolve to CRM

Search by company name, then by the speaker's email domain if the name misses. On multiple
matches, prefer an open deal or an active subscription over a stale record.

**Verify before you trust the match.** CRM records get mis-associated constantly — deals attached
to the wrong company, duplicate records from two import sources. If the matched record's domain
does not correspond to the speaker's, treat it as no match rather than accepting it. A wrong
match is worse than no match: it produces a confident weighting built on another account's data.

Pull the properties named in `context/product-context.md`. If no match: `crm_match: false`,
tier `U`, continue.

### Step 3 — Score ICP fit (0-100)

Apply the rubric in `config/thresholds.json`. Defaults sum to 100 across vertical fit, company
size, maturity, geography, and product activity.

**A dimension you cannot determine scores zero and goes into `data_gaps`.** Never substitute an
average, a midpoint, or a plausible guess. A 40/100 with three gaps listed is honest and useful;
a 65/100 built on two invented values is neither, and it will be repeated downstream as fact.

### Step 4 — Assign weight tier

From `weight_tiers` in the config. Defaults:

| Tier | Meaning |
|---|---|
| **A** | High ICP fit, meaningful revenue, active or expanding. Counts double toward confirmation. |
| **B** | Standard. The bulk of healthy signal. |
| **C** | Low ICP fit, free tier, or churned. Surfaces only when corroborated. |
| **U** | Unattributed. **Quarantined. Never counts toward confirmation** until a human resolves it. |

Tier U is the important one. It exists so that anonymous noise cannot quietly manufacture a
three-source pattern.

### Step 5 — Write output

JSON to `outputs/voc/profiled/<date>-<slug>.json`, or
`outputs/voc/profiled/_quarantine/` for tier U.

```json
{
  "verbatim": "...",
  "channel": "call",
  "date": "2026-04-22",
  "speaker": {"role": "...", "affiliation": "external"},
  "company": "...",
  "crm_match": true,
  "crm_record_id": "...",
  "segment": {"industry": "...", "employees": "...", "geo": "..."},
  "revenue_tier": "...",
  "lifecycle": "customer",
  "icp_fit": 78,
  "weight_tier": "A",
  "data_gaps": ["product_activity"],
  "sentiment": "complaint",
  "source_file": "..."
}
```

Then print a compact card:

```
Signal   "the export takes forever on anything over 2000 rows"
Source   call · 2026-04-22 · external speaker (Ops Director)
Account  Northwind Trading · Logistics · 500-1000 · EMEA
CRM      matched (deal 40122) · customer · renewal in 94 days
Fit      78/100    Tier A
Gaps     product_activity not available
```

---

## Other CRMs

The skill needs four things: search a company, read its properties, read lifecycle, read revenue.
Any CRM can supply them.

| CRM | How |
|---|---|
| HubSpot | Official MCP server. Declared in this skill's `allowed-tools`. |
| Salesforce | Add a Salesforce MCP server, swap the tool names in `allowed-tools`, map property names in `context/product-context.md` |
| Attio / Pipedrive / Close | Same pattern |
| None | Everything is tier U. Documented, supported, honest. |

Property names are **never hardcoded in this file.** They live in `context/product-context.md`
under `crm_properties` because every instance is customized differently.

---

## What this gets wrong

- **ICP fit is a rubric, not a measurement.** It encodes your assumptions about who a good
  customer is. If that model is wrong, this scores confidently in the wrong direction.
- **CRM data decays.** Employee counts and revenue tiers are often years stale. A tier-A weight
  can rest on a number nobody has updated since the deal closed.
- **Revenue is not the only weight that matters.** A small account that is a design partner, a
  logo, or a bellwether for a segment may deserve more weight than its ARR implies. The config
  cannot know that. Override by hand and leave a note.
- **Quarantine grows.** If most signals land in tier U, the answer is better speaker labelling
  upstream, not a lower bar here.

