@tpmjs/nps-analysis 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +183 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 TPMJS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as ai from 'ai';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NPS Analysis Tool for TPMJS
|
|
5
|
+
* Analyzes NPS survey responses and extracts themes
|
|
6
|
+
*
|
|
7
|
+
* This is a proper AI SDK v6 tool that can be used with streamText()
|
|
8
|
+
* Uses jsonSchema() to avoid Zod 4 JSON Schema conversion issues with OpenAI
|
|
9
|
+
*/
|
|
10
|
+
interface NPSResponse {
|
|
11
|
+
score: number;
|
|
12
|
+
comment?: string;
|
|
13
|
+
respondentId?: string;
|
|
14
|
+
date?: string;
|
|
15
|
+
}
|
|
16
|
+
interface NPSCategory {
|
|
17
|
+
category: 'promoter' | 'passive' | 'detractor';
|
|
18
|
+
count: number;
|
|
19
|
+
percentage: number;
|
|
20
|
+
responses: NPSResponse[];
|
|
21
|
+
themes: string[];
|
|
22
|
+
}
|
|
23
|
+
interface NPSAnalysis {
|
|
24
|
+
npsScore: number;
|
|
25
|
+
totalResponses: number;
|
|
26
|
+
distribution: {
|
|
27
|
+
promoters: NPSCategory;
|
|
28
|
+
passives: NPSCategory;
|
|
29
|
+
detractors: NPSCategory;
|
|
30
|
+
};
|
|
31
|
+
topThemes: {
|
|
32
|
+
promoterThemes: string[];
|
|
33
|
+
detractorThemes: string[];
|
|
34
|
+
};
|
|
35
|
+
recommendations: string[];
|
|
36
|
+
summary: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Input type for NPS Analysis Tool
|
|
40
|
+
*/
|
|
41
|
+
type NPSAnalysisInput = {
|
|
42
|
+
responses: NPSResponse[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* NPS Analysis Tool
|
|
46
|
+
* Analyzes NPS survey responses and extracts themes
|
|
47
|
+
*
|
|
48
|
+
* This is a proper AI SDK v6 tool that can be used with streamText()
|
|
49
|
+
*/
|
|
50
|
+
declare const npsAnalysisTool: ai.Tool<NPSAnalysisInput, {
|
|
51
|
+
npsScore: number;
|
|
52
|
+
totalResponses: number;
|
|
53
|
+
distribution: {
|
|
54
|
+
promoters: {
|
|
55
|
+
category: "promoter";
|
|
56
|
+
count: number;
|
|
57
|
+
percentage: number;
|
|
58
|
+
responses: NPSResponse[];
|
|
59
|
+
themes: string[];
|
|
60
|
+
};
|
|
61
|
+
passives: {
|
|
62
|
+
category: "passive";
|
|
63
|
+
count: number;
|
|
64
|
+
percentage: number;
|
|
65
|
+
responses: NPSResponse[];
|
|
66
|
+
themes: never[];
|
|
67
|
+
};
|
|
68
|
+
detractors: {
|
|
69
|
+
category: "detractor";
|
|
70
|
+
count: number;
|
|
71
|
+
percentage: number;
|
|
72
|
+
responses: NPSResponse[];
|
|
73
|
+
themes: string[];
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
topThemes: {
|
|
77
|
+
promoterThemes: string[];
|
|
78
|
+
detractorThemes: string[];
|
|
79
|
+
};
|
|
80
|
+
recommendations: string[];
|
|
81
|
+
summary: string;
|
|
82
|
+
}>;
|
|
83
|
+
|
|
84
|
+
export { type NPSAnalysis, type NPSCategory, type NPSResponse, npsAnalysisTool as default, npsAnalysisTool };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { tool, jsonSchema } from 'ai';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function categorizeScore(score) {
|
|
5
|
+
if (score >= 9) return "promoter";
|
|
6
|
+
if (score >= 7) return "passive";
|
|
7
|
+
return "detractor";
|
|
8
|
+
}
|
|
9
|
+
function extractThemesFromComments(comments) {
|
|
10
|
+
const themeKeywords = {
|
|
11
|
+
"Easy to Use": ["easy", "simple", "intuitive", "user-friendly", "straightforward"],
|
|
12
|
+
"Great Support": ["support", "help", "customer service", "responsive", "helpful"],
|
|
13
|
+
"Feature Rich": ["features", "functionality", "capabilities", "powerful", "comprehensive"],
|
|
14
|
+
"Good Value": ["value", "price", "worth", "affordable", "reasonable"],
|
|
15
|
+
Reliable: ["reliable", "stable", "dependable", "consistent", "works well"],
|
|
16
|
+
"Difficult to Use": ["difficult", "hard", "confusing", "complicated", "complex"],
|
|
17
|
+
"Missing Features": ["missing", "lack", "need", "want", "wish", "should have"],
|
|
18
|
+
"Poor Performance": ["slow", "lag", "crash", "freeze", "performance"],
|
|
19
|
+
Expensive: ["expensive", "costly", "overpriced", "too much", "price"],
|
|
20
|
+
"Poor Support": ["bad support", "slow response", "unhelpful", "poor service"],
|
|
21
|
+
"Bugs/Issues": ["bug", "broken", "error", "issue", "problem", "glitch"]
|
|
22
|
+
};
|
|
23
|
+
const themeCounts = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const comment of comments) {
|
|
25
|
+
const lowerComment = comment.toLowerCase();
|
|
26
|
+
for (const [theme, keywords] of Object.entries(themeKeywords)) {
|
|
27
|
+
if (keywords.some((keyword) => lowerComment.includes(keyword))) {
|
|
28
|
+
themeCounts.set(theme, (themeCounts.get(theme) || 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Array.from(themeCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([theme]) => theme);
|
|
33
|
+
}
|
|
34
|
+
function generateRecommendations(npsScore, detractorThemes, promoterThemes) {
|
|
35
|
+
const recommendations = [];
|
|
36
|
+
if (npsScore < 0) {
|
|
37
|
+
recommendations.push(
|
|
38
|
+
"CRITICAL: NPS is negative - immediate action required to address customer dissatisfaction"
|
|
39
|
+
);
|
|
40
|
+
} else if (npsScore < 30) {
|
|
41
|
+
recommendations.push("LOW: Focus on addressing detractor concerns to improve NPS");
|
|
42
|
+
}
|
|
43
|
+
if (detractorThemes.includes("Difficult to Use")) {
|
|
44
|
+
recommendations.push("Invest in UX improvements and onboarding materials");
|
|
45
|
+
}
|
|
46
|
+
if (detractorThemes.includes("Missing Features")) {
|
|
47
|
+
recommendations.push("Review feature requests and prioritize high-impact additions");
|
|
48
|
+
}
|
|
49
|
+
if (detractorThemes.includes("Poor Performance")) {
|
|
50
|
+
recommendations.push("Prioritize performance optimization and infrastructure improvements");
|
|
51
|
+
}
|
|
52
|
+
if (detractorThemes.includes("Poor Support")) {
|
|
53
|
+
recommendations.push("Improve support response times and training");
|
|
54
|
+
}
|
|
55
|
+
if (detractorThemes.includes("Expensive")) {
|
|
56
|
+
recommendations.push("Review pricing strategy or add more value to justify current pricing");
|
|
57
|
+
}
|
|
58
|
+
if (detractorThemes.includes("Bugs/Issues")) {
|
|
59
|
+
recommendations.push("Focus on stability and quality assurance");
|
|
60
|
+
}
|
|
61
|
+
if (promoterThemes.length > 0) {
|
|
62
|
+
recommendations.push(
|
|
63
|
+
`Amplify strengths in marketing: ${promoterThemes.slice(0, 2).join(", ")}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (recommendations.length === 0) {
|
|
67
|
+
recommendations.push("Maintain current service levels and continue monitoring feedback");
|
|
68
|
+
}
|
|
69
|
+
return recommendations;
|
|
70
|
+
}
|
|
71
|
+
var npsAnalysisTool = tool({
|
|
72
|
+
description: "Analyzes NPS survey responses to categorize by promoter/passive/detractor and extract themes from comments. Provides NPS score, distribution, and actionable insights.",
|
|
73
|
+
inputSchema: jsonSchema({
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
responses: {
|
|
77
|
+
type: "array",
|
|
78
|
+
description: "NPS survey responses with score (0-10) and optional comment",
|
|
79
|
+
items: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
score: {
|
|
83
|
+
type: "number",
|
|
84
|
+
description: "NPS score from 0-10",
|
|
85
|
+
minimum: 0,
|
|
86
|
+
maximum: 10
|
|
87
|
+
},
|
|
88
|
+
comment: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Optional comment from respondent"
|
|
91
|
+
},
|
|
92
|
+
respondentId: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Optional respondent identifier"
|
|
95
|
+
},
|
|
96
|
+
date: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "Optional response date (ISO format)"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ["score"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ["responses"],
|
|
106
|
+
additionalProperties: false
|
|
107
|
+
}),
|
|
108
|
+
async execute({ responses }) {
|
|
109
|
+
if (!Array.isArray(responses) || responses.length === 0) {
|
|
110
|
+
throw new Error("responses must be a non-empty array");
|
|
111
|
+
}
|
|
112
|
+
for (const response of responses) {
|
|
113
|
+
if (response.score < 0 || response.score > 10) {
|
|
114
|
+
throw new Error(`Invalid NPS score: ${response.score}. Must be between 0 and 10.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const promoters = [];
|
|
118
|
+
const passives = [];
|
|
119
|
+
const detractors = [];
|
|
120
|
+
for (const response of responses) {
|
|
121
|
+
const category = categorizeScore(response.score);
|
|
122
|
+
if (category === "promoter") {
|
|
123
|
+
promoters.push(response);
|
|
124
|
+
} else if (category === "passive") {
|
|
125
|
+
passives.push(response);
|
|
126
|
+
} else {
|
|
127
|
+
detractors.push(response);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const totalResponses = responses.length;
|
|
131
|
+
const promoterPercentage = promoters.length / totalResponses * 100;
|
|
132
|
+
const detractorPercentage = detractors.length / totalResponses * 100;
|
|
133
|
+
const npsScore = Math.round(promoterPercentage - detractorPercentage);
|
|
134
|
+
const promoterComments = promoters.map((r) => r.comment).filter((c) => !!c && c.trim().length > 0);
|
|
135
|
+
const detractorComments = detractors.map((r) => r.comment).filter((c) => !!c && c.trim().length > 0);
|
|
136
|
+
const promoterThemes = extractThemesFromComments(promoterComments);
|
|
137
|
+
const detractorThemes = extractThemesFromComments(detractorComments);
|
|
138
|
+
const recommendations = generateRecommendations(npsScore, detractorThemes, promoterThemes);
|
|
139
|
+
let summary = `NPS Score: ${npsScore}. `;
|
|
140
|
+
summary += `${promoters.length} promoters (${Math.round(promoterPercentage)}%), `;
|
|
141
|
+
summary += `${passives.length} passives (${Math.round(passives.length / totalResponses * 100)}%), `;
|
|
142
|
+
summary += `${detractors.length} detractors (${Math.round(detractorPercentage)}%). `;
|
|
143
|
+
if (detractorThemes.length > 0) {
|
|
144
|
+
summary += `Top detractor concerns: ${detractorThemes.slice(0, 2).join(", ")}.`;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
npsScore,
|
|
148
|
+
totalResponses,
|
|
149
|
+
distribution: {
|
|
150
|
+
promoters: {
|
|
151
|
+
category: "promoter",
|
|
152
|
+
count: promoters.length,
|
|
153
|
+
percentage: Math.round(promoterPercentage * 100) / 100,
|
|
154
|
+
responses: promoters,
|
|
155
|
+
themes: promoterThemes
|
|
156
|
+
},
|
|
157
|
+
passives: {
|
|
158
|
+
category: "passive",
|
|
159
|
+
count: passives.length,
|
|
160
|
+
percentage: Math.round(passives.length / totalResponses * 100 * 100) / 100,
|
|
161
|
+
responses: passives,
|
|
162
|
+
themes: []
|
|
163
|
+
},
|
|
164
|
+
detractors: {
|
|
165
|
+
category: "detractor",
|
|
166
|
+
count: detractors.length,
|
|
167
|
+
percentage: Math.round(detractorPercentage * 100) / 100,
|
|
168
|
+
responses: detractors,
|
|
169
|
+
themes: detractorThemes
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
topThemes: {
|
|
173
|
+
promoterThemes,
|
|
174
|
+
detractorThemes
|
|
175
|
+
},
|
|
176
|
+
recommendations,
|
|
177
|
+
summary
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
var index_default = npsAnalysisTool;
|
|
182
|
+
|
|
183
|
+
export { index_default as default, npsAnalysisTool };
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tpmjs/nps-analysis",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Analyzes NPS survey responses to categorize by promoter/detractor and extract themes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tpmjs",
|
|
8
|
+
"cx",
|
|
9
|
+
"nps",
|
|
10
|
+
"survey",
|
|
11
|
+
"customer-success",
|
|
12
|
+
"analytics"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.3.5",
|
|
25
|
+
"typescript": "^5.9.3",
|
|
26
|
+
"@tpmjs/tsconfig": "0.0.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/ajaxdavis/tpmjs.git",
|
|
34
|
+
"directory": "packages/tools/official/nps-analysis"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://tpmjs.com",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"tpmjs": {
|
|
39
|
+
"category": "cx",
|
|
40
|
+
"frameworks": [
|
|
41
|
+
"vercel-ai"
|
|
42
|
+
],
|
|
43
|
+
"tools": [
|
|
44
|
+
{
|
|
45
|
+
"exportName": "npsAnalysisTool",
|
|
46
|
+
"description": "Analyzes NPS survey responses to categorize by promoter/passive/detractor and extract themes from comments. Provides NPS score, distribution, and actionable insights.",
|
|
47
|
+
"parameters": [
|
|
48
|
+
{
|
|
49
|
+
"name": "responses",
|
|
50
|
+
"type": "object[]",
|
|
51
|
+
"description": "NPS survey responses with score (0-10) and optional comment",
|
|
52
|
+
"required": true
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"returns": {
|
|
56
|
+
"type": "NPSAnalysis",
|
|
57
|
+
"description": "NPS breakdown with score, distribution, themes by category, and recommendations"
|
|
58
|
+
},
|
|
59
|
+
"aiAgent": {
|
|
60
|
+
"useCase": "Use this tool to analyze NPS survey results, identify themes from promoters and detractors, and extract actionable insights for product and customer success teams.",
|
|
61
|
+
"limitations": "Theme extraction is keyword-based. For deeper sentiment analysis, consider using an AI model. Requires sufficient response volume for meaningful analysis.",
|
|
62
|
+
"examples": [
|
|
63
|
+
"Analyze quarterly NPS survey results",
|
|
64
|
+
"Extract themes from detractor comments",
|
|
65
|
+
"Compare promoter vs detractor feedback"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"ai": "6.0.0-beta.124"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"dev": "tsup --watch",
|
|
77
|
+
"type-check": "tsc --noEmit",
|
|
78
|
+
"clean": "rm -rf dist .turbo"
|
|
79
|
+
}
|
|
80
|
+
}
|