NPS Verbatim Analysis with Claude Code, Step by Step
Date: July 6, 2026 • Author: Second Axis
Your quarterly NPS survey just closed: 1,800 scores and about 1,400 open text verbatims. Someone on your team will skim them, paste eight quotes into a deck, and declare that "onboarding friction" is the top theme. Your exec team will ask a different question: is onboarding friction worse than last quarter? Did the Q1 pricing complaints go away after the packaging change? What are detractors saying that promoters are not? A pile of quotes cannot answer any of that. Only a system that reads every verbatim the same way every wave can.
Can you build that with Claude Code? Yes, and this is the complete playbook. It is a close cousin of the conversation intelligence build: the same map-then-cluster-then-summarize machinery, so we link there for the shared parts rather than repeat them. What is different here, and it changes almost every design decision, is that survey data arrives in discrete waves, not a stream, and the entire value of the system is wave over wave comparability. If theme definitions drift between Q1 and Q2, your shift numbers are fiction, and fiction with a percent sign on it is worse than no number at all.
What you're building
When a wave closes, one command (or one scheduled trigger) produces:
- A themed read of every verbatim, assigned to a stable taxonomy: each theme with volume, share of responses, and representative quotes.
- Score to theme joins: for each theme, what fraction of detractors mention it versus promoters. A theme mentioned by 31% of detractors and 4% of promoters is your money finding; a theme everyone mentions equally is background noise.
- A shift table against the previous wave: which themes grew, shrank, or are genuinely new, each with sample sizes attached.
- Segment cuts (plan tier, tenure) where the numbers support them, and explicit suppression where they do not.
- A shareable web page with the shift table as the centerpiece. The same machine replays for churn surveys and feature surveys with a different config.
The scale math (this one is different)
Run the numbers before deciding how much architecture you need. A wave of 2,000 verbatims averaging 40 tokens each is about 80,000 tokens. That fits inside a single 200K context window with room to spare. This is not the churn playbook's 150 million tokens.
So why not just paste the wave into a session and ask for themes? Because you would get a perfectly decent one-off read, and a useless system. Next quarter's session would invent slightly different theme names, split one theme into two, merge two into one, and your shift table would be comparing categories that do not correspond. The architecture in this playbook is not primarily about scale. It is about comparability and repeatability: the same extraction schema, the same taxonomy, the same minimum sample rules, wave after wave, with every judgment call versioned in git.
The map pass (one cheap model call per verbatim, producing a structured record) still earns its place: it forces per-response consistency instead of one long impressionistic read, it makes quote verification mechanical, and it keeps working when scale does arrive. A 20,000 response enterprise wave is 800K tokens, past the window; a monthly multi-brand tracker gets there fast. Build the map pass on day one and the architecture never needs to change.
Phase 0: Prerequisites
- Claude Code installed and authenticated, Pro/Max/Team/Enterprise for the scheduling and artifact publishing steps later.
- A way to get responses out of your survey tool. Note: there is no survey tool MCP server we can point you to. In practice this is a CSV export (every major survey tool has one) or a short API script that Claude Code writes for you. Plan on the script if you want the automated wave detection in Phase 6.
- An Anthropic API key with billing if you run the map pass programmatically. At 2,000 responses this is a small line item (numbers at the end); the Batch API's roughly 50% discount applies since nothing here is latency sensitive.
- Python 3.11+ with
pandasandduckdbfor the joins and shift computations. - A dedicated git repo. The wave over wave design depends entirely on committed state.
- A privacy pass. Verbatims contain names, emails, and occasionally grievances with legal weight. Strip identifiers in the ingestion script and treat the export like customer data, because it is.
If you want scaffolding, Anthropic's open source knowledge-work-plugins include a Product Management plugin with a /synthesize-research command. It is useful for one-off reads; the taxonomy, registry, and shift machinery below are what it does not contain.
Phase 1: The repo and the wave registry
nps-verbatims/
├── CLAUDE.md
├── .claude/
│ ├── agents/
│ │ ├── theme-analyst.md
│ │ ├── synthesizer.md
│ │ └── critic.md
│ └── skills/
│ └── nps-wave/
│ └── SKILL.md
├── scripts/
│ ├── ingest_wave.py # export/CSV → clean parquet, PII stripped
│ ├── extract.py # map pass → one record per verbatim
│ └── compute_shift.py # theme shares, score joins, wave deltas
├── state/ # committed: the system's memory
│ ├── wave_registry.json # every wave: ID, dates, N, response rate
│ └── taxonomy.json # versioned theme definitions
├── data/ # gitignored: raw responses, records
└── runs/ # committed: one folder per wave
└── 2026-q2/
├── theme_summaries/
├── shift_table.json
└── report.md
In the conversation intelligence build, continuity lived in a watermark file, because data streamed in continuously. Surveys arrive in discrete waves, so the watermark becomes a wave registry: per wave, the ID, survey name, open and close dates, invitations sent, responses received, and response rate. That last field matters most: a wave with a 9% response rate against last quarter's 21% is not comparable, and the report must say so before it says anything else. The registry is how it knows.
state/taxonomy.json is the other load-bearing file, this playbook's version of the anchor post's stable cluster registry. Each theme carries a stable ID, a name, a one paragraph definition, inclusion examples, an explicit exclusion note ("billing confusion is not pricing dissatisfaction"), the wave it was born in, and a status of active or retired. The CLAUDE.md rules mirror the anchor's cluster identity rules, translated to waves:
## Taxonomy rules
- Theme IDs in state/taxonomy.json are STABLE. Never renumber, never rename
silently. Renames keep the ID and log the old name.
- New wave responses are assigned to the EXISTING taxonomy first.
- Responses that fit no theme are held out; recurring holdout patterns
become CANDIDATE themes, flagged in the report, adopted only by a
human editing taxonomy.json in a PR.
- Retired themes stay in the file with status: retired, so old waves
remain interpretable.
Phase 2: Ingestion and the extraction schema
ingest_wave.py takes the export, deduplicates, strips PII, normalizes the segment columns (plan tier, tenure band), and writes a clean parquet plus a proposed registry entry you eyeball before committing. Survey exports are messy in boring ways (renamed columns, test responses, half-finished rows); this script is where that mess dies, not in the model's context.
extract.py is the map pass. One call per verbatim, cheapest model that survives a spot check of 50 hand-graded extractions, Batch API. Each record:
{
"response_id": "r-4471",
"wave_id": "2026-q2",
"nps_score": 3,
"nps_bucket": "detractor",
"segment": {"plan": "team", "tenure_band": "12-24mo"},
"themes": ["onboarding-friction", "csv-export-gaps"],
"sentiment": -0.7,
"verbatim_quote": "took my team two weeks to get a single dashboard live",
"quote_is_verbatim": true,
"no_fit": false,
"schema_version": 3
}
The critical field is themes: the extraction prompt includes the current taxonomy definitions, and the model's job is assignment, not invention. A verbatim that matches nothing gets no_fit: true and an empty theme list. Note also that the NPS score rides along on every record; the score to theme join in the next phase exists because extraction never separates a verbatim from its score.
Force verbatim quotes and version the schema, for exactly the reasons the anchor post spells out. A paraphrased "customer quote" in a board deck is how the system loses credibility in one meeting.
Phase 3: Taxonomy assignment and shift computation
Assignment is two steps, mirroring the anchor's assign-then-detect pattern in wave-sized units:
- Existing taxonomy first. Every verbatim is matched against current themes during extraction. This is what keeps Q2's "onboarding-friction" the same category as Q1's.
- Then candidate detection. The
no_fitholdouts get clustered (at a few hundred holdouts, a session where Claude groups them directly works fine; embeddings become worth it at larger volumes). Dense groups become candidate themes with provisional IDs, a drafted definition, and a prominent section in the report. A human adopts or rejects each by editingtaxonomy.jsonin a PR. Nothing enters the taxonomy silently.
compute_shift.py then does arithmetic no model should do in its head. Per theme: share of all responses, share of detractors, share of promoters, the detractor to promoter skew, and the delta of each against the previous wave. Every number carries its n.
Which brings up the discipline that makes survey analysis different from every other text workflow: sample size discipline. A theme mentioned in 12 responses did not "surge 300%" because it had 4 mentions last wave. The script marks every theme below a minimum n (30 is a defensible default) as below_min_n, and the reporting layer is forbidden from headlining it. The same rule governs segment cuts: four plan tiers times three tenure bands is twelve cells, and a theme's mentions within one cell hit the teens immediately. A cut is published only when the theme meets minimum n inside that cell; otherwise the report says "insufficient sample to cut by tier," a sentence your dashboard vendor will never show you.
Phase 4: The agents
Same three roles as the anchor, specialized for waves:
theme-analyst.md takes one theme (the top movers plus every candidate), its summary, its shift numbers, and 5-8 full verbatims. It answers: what specifically are these customers describing, does the detractor skew suggest the theme drives the score or merely accompanies it, what changed versus last wave's verbatims, and what could not be verified at this sample size.
synthesizer.md ranks themes by detractor skew times volume (not raw mention count, which lets a lukewarm universal theme outrank a furious rare one), writes the shift narrative, and drafts the report with a "changed since last wave" section up top.
critic.md is the judge loop, and in this workflow it exists mostly to enforce statistics:
1. Every quote: grep it verbatim against the extraction records.
Not found → FAIL.
2. Every theme in the headline or top-three list: n ≥ 30 this wave.
Below → FAIL, demote to a "low sample signals" appendix.
3. Every shift claim: recompute from shift_table.json. A percentage
change on a base below min n → FAIL.
4. Every segment cut shown: verify per-cell n meets threshold.
5. Response rate vs. previous wave differs by >5 points and the
report doesn't caveat comparability → FAIL.
6. Any "insight" that restates a theme definition → FAIL.
Return PASS or FAIL with specific fixes. Max 2 revision loops.
Rules 2 through 5 are the difference between analysis and numerology. They are also the checks a tired human skips at 6pm before the board meeting, which is why a machine runs them every time.
Phase 5: The backfill
Before the first live wave, run a one-off backfill of past waves, in chronological order. Order matters because the taxonomy must grow the way it would have grown live: the oldest wave proposes the initial themes, the next assigns against them and surfaces candidates, and so on. Backfilling newest-first, or all at once, builds a taxonomy shaped by hindsight and quietly biases every baseline.
Two or three historical waves is enough. You get a stress test of the ingestion script, a taxonomy that has survived one round of candidate adoption, and a previous-wave baseline so your first live report ships with a shift table. Expect the backfill session to be interactive, opinionated work: you will rename themes, tighten definitions, and argue with the candidate list. That argument is you building the taxonomy. Have it now, not in front of the Q3 report.
Phase 6: Scheduling (event driven, not cron shaped)
The churn and conversation playbooks run on a clock because their data accumulates on a clock. A survey wave closes on a date, so this system is event driven, with two ways to wire it:
- Manual trigger. When the wave closes, you open the repo and run
/nps-wave 2026-q3. For a quarterly survey this is completely reasonable, and it keeps a human at the point of maximum judgment (is the wave really closed, is the response rate usable). - Scheduled detection. A cloud routine (via
/schedule, or claude.ai/code/routines) runs daily, calls the survey API for closed waves, checks each againstwave_registry.json, and exits quietly if nothing is new. When it finds an unregistered closed wave, it runs the full skill. The self-hosted equivalent is system cron with headless mode (claude -p "/nps-wave --detect"), or a GitHub Actions cron with the official claude-code-action.
Either way, continuity works the same as every playbook in this series: a fresh session does not remember the last one, and it does not need to, because the registry, the taxonomy, and the previous run's report are committed to the repo, and reading them is step one of the skill. The system's memory is engineered state in git.
Add a Stop hook posting run status to Slack. A wave that silently failed to process is a quarter of lost comparability, and quarters do not come back.
Phase 7: Sharing with the team
The wave over wave shift table is the centerpiece: theme, share, detractor skew, delta versus last wave, n, one verbatim quote. Lead with it; put the prose under it. Executives read the table and skim the rest, and real sample sizes are what earn it trust.
Mechanics are the same as the anchor post: publish the report as an artifact (stable URL across waves, org-scoped visibility on Team and Enterprise plans, with the caveat that artifact publishing works from the CLI and desktop app, not every automated surface, so a fully autonomous pipeline may need a human on the publish step). Post the top three shifts and any adopted candidate themes to Slack with a link. Because waves are quarterly, the report also earns a seat in the quarterly business review, where NPS numbers get argued about anyway.
Phase 8: Maintenance (what you own now)
- Taxonomy governance. The taxonomy is the system's constitution, and every change to it is a PR someone reviews. Renames keep IDs; splits and merges get logged with the wave they took effect. If nobody owns this file, themes accrete until the shift table is mush. Name the owner.
- Backfill decisions when the taxonomy changes. Splitting "pricing" into "price level" and "packaging confusion" mid-year poses a choice: re-extract old waves against the new taxonomy (real API cost, clean trend lines) or fork the trend line with an annotation. Neither is free; decide per change, in the PR.
- Wave metadata hygiene. Response rates, incentive changes, a question wording tweak: all of it belongs in the registry, because all of it changes what a comparison means.
- Segment definition changes. Marketing renames plan tiers, finance rebands tenure, and every historical cut silently stops matching. Version segment definitions in the registry the same way you version themes.
- Extraction audits. Re-grade 30-50 extractions each wave. Waves are infrequent, so drift arrives in big steps, and each wave is your only chance to catch it.
What this costs
This is the cheapest workflow in the series:
- Extraction: 2,000 verbatims at ~40 tokens each plus the taxonomy prompt is a few hundred thousand input tokens per wave through a small model on the Batch API. At current published small-model rates that is under a dollar per wave, single digits with generous assumptions. Quarterly. A rounding error.
- The analysis session: a handful of theme analysts reading summaries and a few verbatims each, a synthesizer, a critic. Comfortably inside a Pro or Max subscription's included usage.
- Where it grows: wave frequency (a monthly tracker is 12x the runs), wave size (20,000 response enterprise panels), and taxonomy-change backfills, which re-run extraction over history. Even then the token math stays modest.
- The real cost: the taxonomy owner, the candidate adoption reviews, the per-wave extraction audit, the registry hygiene. A few focused hours per wave from someone with judgment. Compare that to what your team currently spends skimming verbatims into a deck, and to the decisions made on a shift number nobody can defend.
The four practical challenges
- Setup at scale: the twist here is that a single wave often fits in context, so the discipline is architectural, not physical. The map pass, the versioned taxonomy, and the wave registry exist for comparability first, and they are also exactly what keeps working when volume grows 10x.
- Sharing with the team: artifact publish with the shift table as the centerpiece, Slack for the movers, plus the caveat that artifacts ship from CLI and desktop rather than every automated surface.
- Maintenance: taxonomy governance is the whole game. Theme definitions are the instrument, and an instrument that drifts between waves measures nothing. Someone owns taxonomy.json by name, forever.
- Cost: genuinely modest in tokens, under a dollar of extraction per typical wave and a subscription-covered analysis session. The spend is human: a few hours of governance per wave, the price of shift numbers you can defend in front of your board.
The managed alternative
The feedback-analytics platforms cover this category too: Thematic and Chattermill both process open-text survey responses at scale, and enterprise suites like Qualtrics include text analysis natively. They're strong when your volumes are big and your survey program is standardized. The trades: wave-over-wave comparability lives inside their taxonomy decisions rather than a versioned one you control, minimum-sample discipline varies by vendor and dashboards rarely refuse to show a thin cut, and the subscription is priced for enterprises running continuous programs, which is a lot of tool for four waves a year.
Doing this with Second Axis
Everything above is the do-it-yourself path. On Second Axis, survey analysis is a workflow in the marketplace: connect your survey export, choose the workflow, and set the goal ("wave-over-wave theme shift with detractor skew, published when each wave closes"). It runs per wave with the taxonomy versioning, minimum-sample rules, and shift table built in, and it monitors itself: a malformed export, a failed run, or a wave that doesn't match the registry gets caught and alerted before a broken comparison reaches your exec readout.