fullstackgtm 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +7 -3
- package/dist/callTypes.d.ts +72 -0
- package/dist/callTypes.js +690 -0
- package/dist/cli.js +93 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/llm.d.ts +50 -5
- package/dist/llm.js +60 -2
- package/dist/market.d.ts +8 -0
- package/dist/marketReport.js +45 -10
- package/package.json +1 -1
- package/src/callTypes.ts +752 -0
- package/src/cli.ts +109 -11
- package/src/index.ts +16 -0
- package/src/llm.ts +100 -3
- package/src/market.ts +8 -0
- package/src/marketReport.ts +47 -9
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
export const CALL_TYPE_IDS = [
|
|
2
|
+
"prospecting",
|
|
3
|
+
"discovery",
|
|
4
|
+
"demo",
|
|
5
|
+
"technical_validation",
|
|
6
|
+
"negotiation",
|
|
7
|
+
"closing",
|
|
8
|
+
"onboarding",
|
|
9
|
+
"check_in",
|
|
10
|
+
"renewal_expansion",
|
|
11
|
+
"qbr",
|
|
12
|
+
"internal",
|
|
13
|
+
"other",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Our own taxonomy. Signals are authored phrases, not lifted from any third
|
|
17
|
+
* party; they are intentionally conservative (high-precision verbs and nouns
|
|
18
|
+
* that rarely appear off-topic) so the deterministic pass is trustworthy and
|
|
19
|
+
* the ambiguous remainder is handed to the LLM tiebreak rather than guessed.
|
|
20
|
+
*/
|
|
21
|
+
export const CALL_TYPES = [
|
|
22
|
+
{
|
|
23
|
+
id: "prospecting",
|
|
24
|
+
name: "Prospecting / Cold Call",
|
|
25
|
+
phase: "pre_sale",
|
|
26
|
+
definition: "First-touch outbound; the goal is to earn a next meeting, not to sell.",
|
|
27
|
+
signals: [
|
|
28
|
+
"cold call",
|
|
29
|
+
"reaching out",
|
|
30
|
+
"caught you at a bad time",
|
|
31
|
+
"do you have a minute",
|
|
32
|
+
"reason for my call",
|
|
33
|
+
"book some time",
|
|
34
|
+
"grab 15 minutes",
|
|
35
|
+
"worth a conversation",
|
|
36
|
+
],
|
|
37
|
+
confusedWith: ["discovery"],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "discovery",
|
|
41
|
+
name: "Discovery",
|
|
42
|
+
phase: "pre_sale",
|
|
43
|
+
definition: "Scheduled conversation to uncover pain, current process, decision criteria, and the buying group.",
|
|
44
|
+
signals: [
|
|
45
|
+
"walk me through",
|
|
46
|
+
"current process",
|
|
47
|
+
"how are you handling",
|
|
48
|
+
"what's the impact",
|
|
49
|
+
"cost of doing nothing",
|
|
50
|
+
"who else is involved",
|
|
51
|
+
"decision process",
|
|
52
|
+
"pain point",
|
|
53
|
+
"what does success look like",
|
|
54
|
+
"tell me more about",
|
|
55
|
+
],
|
|
56
|
+
negativeSignals: ["share my screen", "let me show you", "pull up the demo"],
|
|
57
|
+
confusedWith: ["prospecting", "demo"],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "demo",
|
|
61
|
+
name: "Demo / Solution Walkthrough",
|
|
62
|
+
phase: "pre_sale",
|
|
63
|
+
definition: "Product walkthrough mapped to the buyer's stated priorities.",
|
|
64
|
+
signals: [
|
|
65
|
+
"share my screen",
|
|
66
|
+
"let me show you",
|
|
67
|
+
"pull up the demo",
|
|
68
|
+
"as you can see here",
|
|
69
|
+
"this is where you would",
|
|
70
|
+
"let me walk you through the product",
|
|
71
|
+
"click into",
|
|
72
|
+
"this dashboard",
|
|
73
|
+
],
|
|
74
|
+
confusedWith: ["discovery", "technical_validation"],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "technical_validation",
|
|
78
|
+
name: "Technical Validation / POC",
|
|
79
|
+
phase: "pre_sale",
|
|
80
|
+
definition: "Buyer tests the solution against technical success criteria; security, integration, scale.",
|
|
81
|
+
signals: [
|
|
82
|
+
"proof of concept",
|
|
83
|
+
"poc",
|
|
84
|
+
"success criteria",
|
|
85
|
+
"security review",
|
|
86
|
+
"sso",
|
|
87
|
+
"api",
|
|
88
|
+
"integration",
|
|
89
|
+
"data residency",
|
|
90
|
+
"sandbox",
|
|
91
|
+
"pilot",
|
|
92
|
+
"test environment",
|
|
93
|
+
],
|
|
94
|
+
confusedWith: ["demo"],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "negotiation",
|
|
98
|
+
name: "Negotiation / Pricing",
|
|
99
|
+
phase: "pre_sale",
|
|
100
|
+
definition: "Commercial terms, pricing, and concessions.",
|
|
101
|
+
signals: [
|
|
102
|
+
"discount",
|
|
103
|
+
"pricing",
|
|
104
|
+
"budget",
|
|
105
|
+
"per seat",
|
|
106
|
+
"annual contract",
|
|
107
|
+
"procurement",
|
|
108
|
+
"redlines",
|
|
109
|
+
"payment terms",
|
|
110
|
+
"come down on",
|
|
111
|
+
"what can you do on price",
|
|
112
|
+
"msa",
|
|
113
|
+
],
|
|
114
|
+
confusedWith: ["closing"],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "closing",
|
|
118
|
+
name: "Closing / Commit",
|
|
119
|
+
phase: "pre_sale",
|
|
120
|
+
definition: "Final steps to signature; mutual action plan toward a close date.",
|
|
121
|
+
signals: [
|
|
122
|
+
"send the contract",
|
|
123
|
+
"docusign",
|
|
124
|
+
"signature",
|
|
125
|
+
"sign by",
|
|
126
|
+
"get this over the line",
|
|
127
|
+
"close date",
|
|
128
|
+
"ready to move forward",
|
|
129
|
+
"final steps",
|
|
130
|
+
"countersign",
|
|
131
|
+
],
|
|
132
|
+
confusedWith: ["negotiation"],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "onboarding",
|
|
136
|
+
name: "Onboarding / Kickoff",
|
|
137
|
+
phase: "post_sale",
|
|
138
|
+
definition: "First post-sale working session; align on goals, owners, and timeline.",
|
|
139
|
+
signals: [
|
|
140
|
+
"kickoff",
|
|
141
|
+
"onboarding",
|
|
142
|
+
"welcome aboard",
|
|
143
|
+
"implementation plan",
|
|
144
|
+
"go live",
|
|
145
|
+
"get you set up",
|
|
146
|
+
"first 30 days",
|
|
147
|
+
"rollout plan",
|
|
148
|
+
],
|
|
149
|
+
confusedWith: ["check_in"],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "check_in",
|
|
153
|
+
name: "Customer Check-in / Success",
|
|
154
|
+
phase: "post_sale",
|
|
155
|
+
definition: "Ongoing adoption and risk review with an existing customer.",
|
|
156
|
+
signals: [
|
|
157
|
+
"how's it going",
|
|
158
|
+
"adoption",
|
|
159
|
+
"since we last spoke",
|
|
160
|
+
"any blockers",
|
|
161
|
+
"how are the team finding",
|
|
162
|
+
"usage",
|
|
163
|
+
"rolled out to",
|
|
164
|
+
"health check",
|
|
165
|
+
],
|
|
166
|
+
confusedWith: ["qbr", "renewal_expansion"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "renewal_expansion",
|
|
170
|
+
name: "Renewal / Expansion",
|
|
171
|
+
phase: "post_sale",
|
|
172
|
+
definition: "Contract renewal and growth conversation.",
|
|
173
|
+
signals: [
|
|
174
|
+
"renewal",
|
|
175
|
+
"renew",
|
|
176
|
+
"up for renewal",
|
|
177
|
+
"expansion",
|
|
178
|
+
"add seats",
|
|
179
|
+
"upsell",
|
|
180
|
+
"additional licenses",
|
|
181
|
+
"contract is ending",
|
|
182
|
+
"grow the account",
|
|
183
|
+
],
|
|
184
|
+
confusedWith: ["check_in", "negotiation"],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "qbr",
|
|
188
|
+
name: "Quarterly Business Review",
|
|
189
|
+
phase: "post_sale",
|
|
190
|
+
definition: "Strategic results review and joint planning with a customer.",
|
|
191
|
+
signals: [
|
|
192
|
+
"quarterly business review",
|
|
193
|
+
"qbr",
|
|
194
|
+
"results this quarter",
|
|
195
|
+
"roi",
|
|
196
|
+
"value realized",
|
|
197
|
+
"executive sponsor",
|
|
198
|
+
"next quarter we",
|
|
199
|
+
"business review",
|
|
200
|
+
],
|
|
201
|
+
confusedWith: ["check_in"],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "internal",
|
|
205
|
+
name: "Internal",
|
|
206
|
+
phase: "neither",
|
|
207
|
+
definition: "Same-organization teammates; not a customer conversation.",
|
|
208
|
+
signals: [
|
|
209
|
+
"deal review",
|
|
210
|
+
"forecast call",
|
|
211
|
+
"our pipeline",
|
|
212
|
+
"internal sync",
|
|
213
|
+
"standup",
|
|
214
|
+
"let's align internally",
|
|
215
|
+
"loop in our",
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "other",
|
|
220
|
+
name: "Other / Unclassified",
|
|
221
|
+
phase: "neither",
|
|
222
|
+
definition: "No call type carried enough signal to classify confidently.",
|
|
223
|
+
signals: [],
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
/**
|
|
227
|
+
* Classify a call from its transcript using authored phrase signals. Pure,
|
|
228
|
+
* deterministic, key-free. Score = distinct positive signals matched minus
|
|
229
|
+
* distinct negative signals; the strongest type wins. Confidence is `high`
|
|
230
|
+
* only when one type clearly leads (>=2 signals and a >=2 margin over the
|
|
231
|
+
* runner-up); a single weak signal yields `low`; no signal yields `other`/none.
|
|
232
|
+
*/
|
|
233
|
+
export function classifyCall(transcript) {
|
|
234
|
+
const haystack = transcript.toLowerCase();
|
|
235
|
+
const scored = [];
|
|
236
|
+
for (const def of CALL_TYPES) {
|
|
237
|
+
if (def.signals.length === 0)
|
|
238
|
+
continue;
|
|
239
|
+
const matched = def.signals.filter((signal) => haystack.includes(signal));
|
|
240
|
+
if (matched.length === 0)
|
|
241
|
+
continue;
|
|
242
|
+
const penalties = (def.negativeSignals ?? []).filter((signal) => haystack.includes(signal)).length;
|
|
243
|
+
const score = matched.length - penalties;
|
|
244
|
+
if (score <= 0)
|
|
245
|
+
continue;
|
|
246
|
+
scored.push({ type: def.id, score, matched });
|
|
247
|
+
}
|
|
248
|
+
scored.sort((a, b) => b.score - a.score || CALL_TYPE_IDS.indexOf(a.type) - CALL_TYPE_IDS.indexOf(b.type));
|
|
249
|
+
if (scored.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
type: "other",
|
|
252
|
+
confidence: "none",
|
|
253
|
+
reason: "No call-type signals matched the transcript. Classify by hand with --call-type, or run --llm for a model tiebreak.",
|
|
254
|
+
candidates: [],
|
|
255
|
+
method: "deterministic",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const top = scored[0];
|
|
259
|
+
const runnerUp = scored[1];
|
|
260
|
+
const margin = top.score - (runnerUp?.score ?? 0);
|
|
261
|
+
const def = CALL_TYPES.find((d) => d.id === top.type);
|
|
262
|
+
const matchedList = top.matched.slice(0, 4).map((m) => `"${m}"`).join(", ");
|
|
263
|
+
if (top.score >= 2 && margin >= 2) {
|
|
264
|
+
return {
|
|
265
|
+
type: top.type,
|
|
266
|
+
confidence: "high",
|
|
267
|
+
reason: `${def.name}: matched ${top.score} signals (${matchedList})${runnerUp ? `; clear of ${runnerUp.type} by ${margin}` : ""}.`,
|
|
268
|
+
candidates: scored,
|
|
269
|
+
method: "deterministic",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const confused = def.confusedWith?.length ? ` Easily confused with ${def.confusedWith.join("/")}.` : "";
|
|
273
|
+
return {
|
|
274
|
+
type: top.type,
|
|
275
|
+
confidence: "low",
|
|
276
|
+
reason: `${def.name} is the leading guess (signals: ${matchedList})${runnerUp ? `, but ${runnerUp.type} also matched (${runnerUp.score})` : ""}.${confused} Confirm with --call-type or --llm.`,
|
|
277
|
+
candidates: scored,
|
|
278
|
+
method: "deterministic",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// ── Type-specific rubric presets ───────────────────────────────────────────
|
|
282
|
+
/** Shared qualitative bands over a 1-5 weighted overall. */
|
|
283
|
+
export const BANDS_5 = [
|
|
284
|
+
{ label: "strong", min: 4, meaning: "Coach-the-team example; replicate what worked." },
|
|
285
|
+
{ label: "solid", min: 3, meaning: "Competent call with one or two clear gaps to close." },
|
|
286
|
+
{ label: "developing", min: 2, meaning: "Real gaps in the moments that move the deal." },
|
|
287
|
+
{ label: "weak", min: 0, meaning: "Missed the core job of this call type — prioritize coaching here." },
|
|
288
|
+
];
|
|
289
|
+
/**
|
|
290
|
+
* Pick the qualitative band for a weighted overall: the highest band whose
|
|
291
|
+
* `min` the score reaches. Deterministic, client-side — same input, same band.
|
|
292
|
+
*/
|
|
293
|
+
export function bandForScore(score, bands) {
|
|
294
|
+
return [...bands].sort((a, b) => b.min - a.min).find((band) => score >= band.min);
|
|
295
|
+
}
|
|
296
|
+
const RUBRICS = {
|
|
297
|
+
prospecting: {
|
|
298
|
+
scale: 5,
|
|
299
|
+
name: "Prospecting / Cold Call",
|
|
300
|
+
callType: "prospecting",
|
|
301
|
+
bands: BANDS_5,
|
|
302
|
+
dimensions: [
|
|
303
|
+
{
|
|
304
|
+
name: "Opener & Permission",
|
|
305
|
+
weight: 1.2,
|
|
306
|
+
rubric: "Did the rep earn attention with a relevant reason for the call and a permission-based opener?",
|
|
307
|
+
anchorsHigh: ["States a specific, researched reason tied to the prospect's role/company", "Asks for and gets permission to continue"],
|
|
308
|
+
anchorsLow: ["Generic pitch with no relevance", "Plows ahead without acknowledging the interruption"],
|
|
309
|
+
evidenceCues: ["reason for my call", "do you have a minute", "noticed that you"],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "Relevance & Hook",
|
|
313
|
+
weight: 1.1,
|
|
314
|
+
rubric: "Was the value framed around a problem the prospect plausibly has, not a feature list?",
|
|
315
|
+
anchorsHigh: ["Names a concrete pain common to the prospect's segment"],
|
|
316
|
+
anchorsLow: ["Leads with product features and company history"],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "Objection Handling",
|
|
320
|
+
weight: 1,
|
|
321
|
+
rubric: "Were brush-offs ('not interested', 'send an email') acknowledged and turned into a micro-conversation?",
|
|
322
|
+
anchorsHigh: ["Acknowledges the objection, asks one sharp question, keeps it human"],
|
|
323
|
+
anchorsLow: ["Argues, ignores, or immediately gives up"],
|
|
324
|
+
evidenceCues: ["not interested", "send me an email", "we already use"],
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "Meeting Ask",
|
|
328
|
+
weight: 1.2,
|
|
329
|
+
rubric: "Did the rep ask for a specific, low-friction next meeting with a concrete time?",
|
|
330
|
+
anchorsHigh: ["Proposes two concrete times for a short next call"],
|
|
331
|
+
anchorsLow: ["Vague 'I'll follow up' with no commitment"],
|
|
332
|
+
evidenceCues: ["grab 15 minutes", "does Tuesday", "book some time"],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
discovery: {
|
|
337
|
+
scale: 5,
|
|
338
|
+
name: "Discovery",
|
|
339
|
+
callType: "discovery",
|
|
340
|
+
bands: BANDS_5,
|
|
341
|
+
dimensions: [
|
|
342
|
+
{
|
|
343
|
+
name: "Depth of Pain",
|
|
344
|
+
weight: 1.3,
|
|
345
|
+
rubric: "Did the rep uncover concrete pain, the current process, and the cost of inaction with specifics?",
|
|
346
|
+
anchorsHigh: ["Quantified pain (time/money/risk)", "Mapped the current workflow end to end"],
|
|
347
|
+
anchorsLow: ["Surface-level 'it's a bit manual' with no numbers or process detail"],
|
|
348
|
+
evidenceCues: ["how are you handling", "what's the impact", "cost of doing nothing", "walk me through"],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "Decision Process & Criteria",
|
|
352
|
+
weight: 1.2,
|
|
353
|
+
rubric: "Were the decision criteria, timeline, and approval path surfaced?",
|
|
354
|
+
anchorsHigh: ["Knows the criteria, the steps, and who signs"],
|
|
355
|
+
anchorsLow: ["No idea how or when a decision gets made"],
|
|
356
|
+
evidenceCues: ["decision process", "what does success look like", "by when"],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "Buying Group",
|
|
360
|
+
weight: 1.1,
|
|
361
|
+
rubric: "Were the other stakeholders and the economic buyer identified — is the rep multi-threaded?",
|
|
362
|
+
anchorsHigh: ["Names the buying group and plans to reach the economic buyer"],
|
|
363
|
+
anchorsLow: ["Single-threaded with one champion, buyer unknown"],
|
|
364
|
+
evidenceCues: ["who else is involved", "report to", "loop in"],
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "Listening Ratio",
|
|
368
|
+
weight: 1,
|
|
369
|
+
rubric: "Did the prospect do most of the talking, with the rep asking open questions and following threads?",
|
|
370
|
+
anchorsHigh: ["Rep asks open questions and digs into answers"],
|
|
371
|
+
anchorsLow: ["Rep monologues; questions are closed or leading"],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "Next Steps & Commitment",
|
|
375
|
+
weight: 1.1,
|
|
376
|
+
rubric: "Did the call end with a specific, time-bound, mutually agreed next step?",
|
|
377
|
+
anchorsHigh: ["Concrete next meeting booked with a purpose"],
|
|
378
|
+
anchorsLow: ["'I'll send some info' with no date"],
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
demo: {
|
|
383
|
+
scale: 5,
|
|
384
|
+
name: "Demo / Solution Walkthrough",
|
|
385
|
+
callType: "demo",
|
|
386
|
+
bands: BANDS_5,
|
|
387
|
+
dimensions: [
|
|
388
|
+
{
|
|
389
|
+
name: "Tailoring to Stated Needs",
|
|
390
|
+
weight: 1.3,
|
|
391
|
+
rubric: "Was the demo mapped to the prospect's own priorities, or a generic feature tour?",
|
|
392
|
+
anchorsHigh: ["Each section ties back to a pain the buyer named"],
|
|
393
|
+
anchorsLow: ["Linear feature dump unrelated to their goals"],
|
|
394
|
+
evidenceCues: ["you mentioned", "back to your point about", "this solves the"],
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "Engagement & Interaction",
|
|
398
|
+
weight: 1.1,
|
|
399
|
+
rubric: "Did the rep check for reactions and let the buyer drive, vs. talk over a one-way screen-share?",
|
|
400
|
+
anchorsHigh: ["Pauses for reactions, asks what resonates, hands over control"],
|
|
401
|
+
anchorsLow: ["Uninterrupted monologue, no temperature checks"],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: "Value & Outcome Framing",
|
|
405
|
+
weight: 1.2,
|
|
406
|
+
rubric: "Was each capability framed as a business outcome with the buyer's numbers, not a feature?",
|
|
407
|
+
anchorsHigh: ["'This saves your team X hours' tied to their stated volume"],
|
|
408
|
+
anchorsLow: ["'And here we have…' feature narration"],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "Advancing the Deal",
|
|
412
|
+
weight: 1.1,
|
|
413
|
+
rubric: "Did the demo end with a clear, agreed next step toward a decision?",
|
|
414
|
+
anchorsHigh: ["Concrete next step (POC, stakeholder demo, proposal) booked"],
|
|
415
|
+
anchorsLow: ["'Let me know what you think'"],
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
technical_validation: {
|
|
420
|
+
scale: 5,
|
|
421
|
+
name: "Technical Validation / POC",
|
|
422
|
+
callType: "technical_validation",
|
|
423
|
+
bands: BANDS_5,
|
|
424
|
+
dimensions: [
|
|
425
|
+
{
|
|
426
|
+
name: "Success Criteria Defined",
|
|
427
|
+
weight: 1.3,
|
|
428
|
+
rubric: "Were explicit, measurable success criteria agreed before the evaluation?",
|
|
429
|
+
anchorsHigh: ["Written criteria with thresholds and an owner"],
|
|
430
|
+
anchorsLow: ["Open-ended 'let's try it out'"],
|
|
431
|
+
evidenceCues: ["success criteria", "what would prove", "pass if"],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "Technical Fit & Objections",
|
|
435
|
+
weight: 1.2,
|
|
436
|
+
rubric: "Were integration, security, and scale questions surfaced and addressed with specifics?",
|
|
437
|
+
anchorsHigh: ["Concrete answers on SSO/API/data handling, risks named"],
|
|
438
|
+
anchorsLow: ["Hand-waves technical concerns"],
|
|
439
|
+
evidenceCues: ["sso", "api", "security review", "data residency"],
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "Scope & Timeline",
|
|
443
|
+
weight: 1.1,
|
|
444
|
+
rubric: "Was the POC scoped to a bounded timeline with clear exit conditions?",
|
|
445
|
+
anchorsHigh: ["Fixed window, defined scope, go/no-go date"],
|
|
446
|
+
anchorsLow: ["Unbounded pilot with no end"],
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: "Stakeholder & Sign-off Path",
|
|
450
|
+
weight: 1,
|
|
451
|
+
rubric: "Is it clear who validates the result and what a pass unlocks commercially?",
|
|
452
|
+
anchorsHigh: ["Names the technical approver and the next commercial step"],
|
|
453
|
+
anchorsLow: ["Unknown who decides or what happens after"],
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
negotiation: {
|
|
458
|
+
scale: 5,
|
|
459
|
+
name: "Negotiation / Pricing",
|
|
460
|
+
callType: "negotiation",
|
|
461
|
+
bands: BANDS_5,
|
|
462
|
+
dimensions: [
|
|
463
|
+
{
|
|
464
|
+
name: "Value Reanchoring",
|
|
465
|
+
weight: 1.3,
|
|
466
|
+
rubric: "When price came up, did the rep re-anchor on quantified value before discussing concessions?",
|
|
467
|
+
anchorsHigh: ["Reframes cost against the ROI the buyer already agreed to"],
|
|
468
|
+
anchorsLow: ["Jumps straight to discounting"],
|
|
469
|
+
evidenceCues: ["come down on", "what can you do on price", "budget"],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "Concession Discipline",
|
|
473
|
+
weight: 1.2,
|
|
474
|
+
rubric: "Were concessions traded for something (term, volume, timing), never given for free?",
|
|
475
|
+
anchorsHigh: ["Every give has a get: 'if X then Y'"],
|
|
476
|
+
anchorsLow: ["Unilateral discounts to keep the peace"],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "Terms & Procurement Clarity",
|
|
480
|
+
weight: 1.1,
|
|
481
|
+
rubric: "Were the real commercial blockers (terms, procurement, legal) surfaced and sequenced?",
|
|
482
|
+
anchorsHigh: ["Knows the procurement steps and owns the redline path"],
|
|
483
|
+
anchorsLow: ["Surprised by process late in the deal"],
|
|
484
|
+
evidenceCues: ["procurement", "redlines", "payment terms", "msa"],
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "Close Path",
|
|
488
|
+
weight: 1.1,
|
|
489
|
+
rubric: "Did the call end with a concrete path and date to signature?",
|
|
490
|
+
anchorsHigh: ["Mutual action plan to a close date"],
|
|
491
|
+
anchorsLow: ["Open-ended 'let me check internally'"],
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
closing: {
|
|
496
|
+
scale: 5,
|
|
497
|
+
name: "Closing / Commit",
|
|
498
|
+
callType: "closing",
|
|
499
|
+
bands: BANDS_5,
|
|
500
|
+
dimensions: [
|
|
501
|
+
{
|
|
502
|
+
name: "Mutual Action Plan",
|
|
503
|
+
weight: 1.3,
|
|
504
|
+
rubric: "Is there a step-by-step plan to signature with owners and dates on both sides?",
|
|
505
|
+
anchorsHigh: ["Each step has an owner and a date, buyer agreed"],
|
|
506
|
+
anchorsLow: ["No plan; relies on hope"],
|
|
507
|
+
evidenceCues: ["final steps", "sign by", "close date"],
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: "Blocker Surfacing",
|
|
511
|
+
weight: 1.2,
|
|
512
|
+
rubric: "Were remaining risks (legal, security, budget approval) named and owned?",
|
|
513
|
+
anchorsHigh: ["Open risks explicit with mitigation owners"],
|
|
514
|
+
anchorsLow: ["Assumes it's done; risks unspoken"],
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: "Commitment Test",
|
|
518
|
+
weight: 1.2,
|
|
519
|
+
rubric: "Did the rep test genuine commitment vs. accept a soft 'looks good'?",
|
|
520
|
+
anchorsHigh: ["Confirms signature authority and timing directly"],
|
|
521
|
+
anchorsLow: ["Takes enthusiasm as commitment"],
|
|
522
|
+
evidenceCues: ["ready to move forward", "get this over the line"],
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
},
|
|
526
|
+
onboarding: {
|
|
527
|
+
scale: 5,
|
|
528
|
+
name: "Onboarding / Kickoff",
|
|
529
|
+
callType: "onboarding",
|
|
530
|
+
bands: BANDS_5,
|
|
531
|
+
dimensions: [
|
|
532
|
+
{
|
|
533
|
+
name: "Goals & Success Definition",
|
|
534
|
+
weight: 1.3,
|
|
535
|
+
rubric: "Were the customer's desired outcomes and a definition of success captured?",
|
|
536
|
+
anchorsHigh: ["Outcomes documented with a measurable success metric"],
|
|
537
|
+
anchorsLow: ["Jumps to setup with no goal alignment"],
|
|
538
|
+
evidenceCues: ["what does success look like", "first 30 days", "go live"],
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "Plan, Owners & Timeline",
|
|
542
|
+
weight: 1.2,
|
|
543
|
+
rubric: "Is there an implementation plan with owners and dates on both sides?",
|
|
544
|
+
anchorsHigh: ["Clear milestones, named owners, go-live date"],
|
|
545
|
+
anchorsLow: ["Vague 'we'll get started'"],
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "Stakeholder Alignment",
|
|
549
|
+
weight: 1,
|
|
550
|
+
rubric: "Are the right people engaged and roles clear (admin, end users, exec sponsor)?",
|
|
551
|
+
anchorsHigh: ["Roles assigned, sponsor engaged"],
|
|
552
|
+
anchorsLow: ["Single contact, unclear who does what"],
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "Risk & Adoption Setup",
|
|
556
|
+
weight: 1,
|
|
557
|
+
rubric: "Were early adoption risks and the path to first value identified?",
|
|
558
|
+
anchorsHigh: ["Names the fastest path to first value and likely blockers"],
|
|
559
|
+
anchorsLow: ["No view on adoption risk"],
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
},
|
|
563
|
+
check_in: {
|
|
564
|
+
scale: 5,
|
|
565
|
+
name: "Customer Check-in / Success",
|
|
566
|
+
callType: "check_in",
|
|
567
|
+
bands: BANDS_5,
|
|
568
|
+
dimensions: [
|
|
569
|
+
{
|
|
570
|
+
name: "Adoption & Usage Read",
|
|
571
|
+
weight: 1.2,
|
|
572
|
+
rubric: "Did the rep get a concrete read on usage and adoption vs. a polite 'all good'?",
|
|
573
|
+
anchorsHigh: ["Specifics on who uses what and how often"],
|
|
574
|
+
anchorsLow: ["Accepts 'fine' with no detail"],
|
|
575
|
+
evidenceCues: ["adoption", "usage", "rolled out to", "any blockers"],
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "Risk & Sentiment",
|
|
579
|
+
weight: 1.3,
|
|
580
|
+
rubric: "Were risks, blockers, and sentiment surfaced honestly?",
|
|
581
|
+
anchorsHigh: ["Names friction and unhappy stakeholders openly"],
|
|
582
|
+
anchorsLow: ["Misses or glosses over warning signs"],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "Value Reinforcement",
|
|
586
|
+
weight: 1,
|
|
587
|
+
rubric: "Did the rep connect usage back to the customer's goals and realized value?",
|
|
588
|
+
anchorsHigh: ["Ties activity to outcomes the customer cares about"],
|
|
589
|
+
anchorsLow: ["No link to value"],
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: "Next Steps & Expansion Signal",
|
|
593
|
+
weight: 1,
|
|
594
|
+
rubric: "Were follow-ups agreed and any expansion or renewal signal noted?",
|
|
595
|
+
anchorsHigh: ["Clear next step; flags growth/renewal signals"],
|
|
596
|
+
anchorsLow: ["Ends with no plan"],
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
renewal_expansion: {
|
|
601
|
+
scale: 5,
|
|
602
|
+
name: "Renewal / Expansion",
|
|
603
|
+
callType: "renewal_expansion",
|
|
604
|
+
bands: BANDS_5,
|
|
605
|
+
dimensions: [
|
|
606
|
+
{
|
|
607
|
+
name: "Value Realized Case",
|
|
608
|
+
weight: 1.3,
|
|
609
|
+
rubric: "Did the rep make the case for value delivered before discussing renewal terms?",
|
|
610
|
+
anchorsHigh: ["Concrete outcomes and ROI tied to the customer's goals"],
|
|
611
|
+
anchorsLow: ["Asks to renew with no value recap"],
|
|
612
|
+
evidenceCues: ["value realized", "roi", "since last year"],
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
name: "Renewal Risk & Timing",
|
|
616
|
+
weight: 1.2,
|
|
617
|
+
rubric: "Were renewal risks (budget, sponsor change, competitor) and timing surfaced early?",
|
|
618
|
+
anchorsHigh: ["Knows renewal date, risks, and the approval path"],
|
|
619
|
+
anchorsLow: ["Renewal raised late with unknown risk"],
|
|
620
|
+
evidenceCues: ["up for renewal", "contract is ending", "budget"],
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
name: "Expansion Path",
|
|
624
|
+
weight: 1.1,
|
|
625
|
+
rubric: "Was a credible expansion opportunity explored and tied to need?",
|
|
626
|
+
anchorsHigh: ["Expansion grounded in a real unmet need"],
|
|
627
|
+
anchorsLow: ["No growth conversation, or a pushy untethered upsell"],
|
|
628
|
+
evidenceCues: ["add seats", "expansion", "additional licenses"],
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: "Commitment & Next Steps",
|
|
632
|
+
weight: 1,
|
|
633
|
+
rubric: "Did the call end with a concrete path to a signed renewal?",
|
|
634
|
+
anchorsHigh: ["Agreed steps and date to renewal signature"],
|
|
635
|
+
anchorsLow: ["Open-ended"],
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
},
|
|
639
|
+
qbr: {
|
|
640
|
+
scale: 5,
|
|
641
|
+
name: "Quarterly Business Review",
|
|
642
|
+
callType: "qbr",
|
|
643
|
+
bands: BANDS_5,
|
|
644
|
+
dimensions: [
|
|
645
|
+
{
|
|
646
|
+
name: "Results & Value Narrative",
|
|
647
|
+
weight: 1.3,
|
|
648
|
+
rubric: "Did the rep present quantified results against the goals set, not just activity?",
|
|
649
|
+
anchorsHigh: ["Outcomes vs. targets with numbers the sponsor cares about"],
|
|
650
|
+
anchorsLow: ["Activity recap with no outcome framing"],
|
|
651
|
+
evidenceCues: ["results this quarter", "roi", "value realized"],
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "Executive Engagement",
|
|
655
|
+
weight: 1.2,
|
|
656
|
+
rubric: "Was the economic buyer / sponsor engaged in a strategic, not tactical, conversation?",
|
|
657
|
+
anchorsHigh: ["Sponsor present and engaged on strategy"],
|
|
658
|
+
anchorsLow: ["Only day-to-day users; no exec"],
|
|
659
|
+
evidenceCues: ["executive sponsor", "business review"],
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "Forward Plan",
|
|
663
|
+
weight: 1.2,
|
|
664
|
+
rubric: "Was there a joint plan for next quarter tied to the customer's objectives?",
|
|
665
|
+
anchorsHigh: ["Mutual goals and initiatives for next quarter agreed"],
|
|
666
|
+
anchorsLow: ["No forward plan"],
|
|
667
|
+
evidenceCues: ["next quarter we", "goals for"],
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "Growth & Renewal Setup",
|
|
671
|
+
weight: 1,
|
|
672
|
+
rubric: "Did the QBR set up renewal/expansion by reinforcing value and surfacing needs?",
|
|
673
|
+
anchorsHigh: ["Naturally teed up renewal/growth from demonstrated value"],
|
|
674
|
+
anchorsLow: ["Missed the opening entirely"],
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
/**
|
|
680
|
+
* The type-specific rubric for a call type, or the generic fallback. The
|
|
681
|
+
* generic rubric is intentionally discovery-leaning (the most common scored
|
|
682
|
+
* call) and is what `other`/`internal`/unknown resolve to.
|
|
683
|
+
*/
|
|
684
|
+
export function rubricForCallType(type, fallback) {
|
|
685
|
+
return RUBRICS[type] ?? fallback;
|
|
686
|
+
}
|
|
687
|
+
/** All authored presets, for `--list` and docs. */
|
|
688
|
+
export function rubricPresets() {
|
|
689
|
+
return CALL_TYPE_IDS.map((id) => RUBRICS[id]).filter((r) => Boolean(r));
|
|
690
|
+
}
|