@tpmjs/official-survey-analyze 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 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.
@@ -0,0 +1,67 @@
1
+ import * as ai from 'ai';
2
+
3
+ /**
4
+ * Survey Analyze Tool for TPMJS
5
+ * Analyzes employee survey responses to extract themes, sentiment, and action items
6
+ */
7
+ /**
8
+ * Sentiment score and analysis
9
+ */
10
+ interface SentimentScore {
11
+ overall: number;
12
+ label: 'negative' | 'neutral' | 'positive';
13
+ confidence: number;
14
+ }
15
+ /**
16
+ * Theme extracted from survey responses
17
+ */
18
+ interface Theme {
19
+ name: string;
20
+ description: string;
21
+ frequency: number;
22
+ sentiment: number;
23
+ examples: string[];
24
+ }
25
+ /**
26
+ * Action item suggested based on survey
27
+ */
28
+ interface ActionItem {
29
+ priority: 'high' | 'medium' | 'low';
30
+ category: string;
31
+ recommendation: string;
32
+ rationale: string;
33
+ }
34
+ /**
35
+ * Per-question analysis
36
+ */
37
+ interface QuestionAnalysis {
38
+ question: string;
39
+ sentiment: SentimentScore;
40
+ topThemes: string[];
41
+ responseCount: number;
42
+ }
43
+ /**
44
+ * Input interface for survey analysis
45
+ */
46
+ interface SurveyAnalyzeInput {
47
+ responses: string[];
48
+ questions: string[];
49
+ }
50
+ /**
51
+ * Survey analysis output
52
+ */
53
+ interface SurveyAnalysis {
54
+ overallSentiment: SentimentScore;
55
+ themes: Theme[];
56
+ actionItems: ActionItem[];
57
+ questionAnalysis: QuestionAnalysis[];
58
+ summary: string;
59
+ participationRate?: number;
60
+ }
61
+ /**
62
+ * Survey Analyze Tool
63
+ * Analyzes employee survey responses to extract themes, sentiment, and action items
64
+ */
65
+ declare const surveyAnalyzeTool: ai.Tool<SurveyAnalyzeInput, SurveyAnalysis>;
66
+
67
+ export { type SurveyAnalysis, surveyAnalyzeTool as default, surveyAnalyzeTool };
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ import { tool, jsonSchema } from 'ai';
2
+
3
+ // src/index.ts
4
+ var surveyAnalyzeTool = tool({
5
+ description: "Analyzes employee survey responses to extract key themes, assess sentiment (overall and per-question), and suggest actionable recommendations. Processes both structured and open-ended survey responses to provide comprehensive insights.",
6
+ inputSchema: jsonSchema({
7
+ type: "object",
8
+ properties: {
9
+ responses: {
10
+ type: "array",
11
+ items: { type: "string" },
12
+ description: "Array of survey responses from employees",
13
+ minItems: 1
14
+ },
15
+ questions: {
16
+ type: "array",
17
+ items: { type: "string" },
18
+ description: "Array of survey questions that were asked",
19
+ minItems: 1
20
+ }
21
+ },
22
+ required: ["responses", "questions"],
23
+ additionalProperties: false
24
+ }),
25
+ execute: async ({ responses, questions }) => {
26
+ if (!Array.isArray(responses) || responses.length === 0) {
27
+ throw new Error("Responses must be a non-empty array");
28
+ }
29
+ if (!Array.isArray(questions) || questions.length === 0) {
30
+ throw new Error("Questions must be a non-empty array");
31
+ }
32
+ if (responses.some((r) => typeof r !== "string")) {
33
+ throw new Error("All responses must be strings");
34
+ }
35
+ if (questions.some((q) => typeof q !== "string")) {
36
+ throw new Error("All questions must be strings");
37
+ }
38
+ try {
39
+ const overallSentiment = analyzeSentiment(responses);
40
+ const themes = extractThemes(responses);
41
+ const actionItems = generateActionItems(themes, overallSentiment);
42
+ const questionAnalysis = questions.map((question, idx) => {
43
+ const questionResponses = responses.filter((_, i) => i % questions.length === idx);
44
+ const sentiment = analyzeSentiment(questionResponses);
45
+ const topThemes = extractThemes(questionResponses).slice(0, 3).map((t) => t.name);
46
+ return {
47
+ question,
48
+ sentiment,
49
+ topThemes,
50
+ responseCount: questionResponses.length
51
+ };
52
+ });
53
+ const summary = generateSummary(overallSentiment, themes, actionItems);
54
+ return {
55
+ overallSentiment,
56
+ themes,
57
+ actionItems,
58
+ questionAnalysis,
59
+ summary,
60
+ participationRate: void 0
61
+ // Requires additional context
62
+ };
63
+ } catch (error) {
64
+ throw new Error(
65
+ `Failed to analyze survey: ${error instanceof Error ? error.message : String(error)}`
66
+ );
67
+ }
68
+ }
69
+ });
70
+ function analyzeSentiment(texts) {
71
+ const positiveWords = [
72
+ "good",
73
+ "great",
74
+ "excellent",
75
+ "love",
76
+ "amazing",
77
+ "fantastic",
78
+ "happy",
79
+ "satisfied",
80
+ "appreciate"
81
+ ];
82
+ const negativeWords = [
83
+ "bad",
84
+ "poor",
85
+ "terrible",
86
+ "hate",
87
+ "awful",
88
+ "disappointed",
89
+ "frustrated",
90
+ "unhappy",
91
+ "dissatisfied"
92
+ ];
93
+ let positiveCount = 0;
94
+ let negativeCount = 0;
95
+ let totalWords = 0;
96
+ for (const text of texts) {
97
+ const words = text.toLowerCase().split(/\s+/);
98
+ totalWords += words.length;
99
+ for (const word of words) {
100
+ if (positiveWords.some((pw) => word.includes(pw))) positiveCount++;
101
+ if (negativeWords.some((nw) => word.includes(nw))) negativeCount++;
102
+ }
103
+ }
104
+ const score = (positiveCount - negativeCount) / Math.max(totalWords / 10, 1);
105
+ const normalizedScore = Math.max(-1, Math.min(1, score));
106
+ let label;
107
+ if (normalizedScore < -0.2) label = "negative";
108
+ else if (normalizedScore > 0.2) label = "positive";
109
+ else label = "neutral";
110
+ const confidence = Math.min(1, Math.abs(normalizedScore) + 0.3);
111
+ return {
112
+ overall: normalizedScore,
113
+ label,
114
+ confidence
115
+ };
116
+ }
117
+ function extractThemes(responses) {
118
+ const themeKeywords = {
119
+ "Work-Life Balance": ["balance", "hours", "overtime", "flexible", "remote", "workload"],
120
+ Compensation: ["salary", "pay", "compensation", "bonus", "benefits", "equity"],
121
+ Management: ["manager", "leadership", "supervisor", "boss", "management"],
122
+ "Career Growth": ["growth", "promotion", "career", "development", "learning", "training"],
123
+ Culture: ["culture", "environment", "team", "collaboration", "values", "diversity"],
124
+ "Tools & Resources": ["tools", "resources", "equipment", "software", "technology"],
125
+ Communication: ["communication", "transparency", "feedback", "updates", "meetings"]
126
+ };
127
+ const themes = [];
128
+ for (const [themeName, keywords] of Object.entries(themeKeywords)) {
129
+ let frequency = 0;
130
+ const examples = [];
131
+ let sentimentSum = 0;
132
+ for (const response of responses) {
133
+ const lowerResponse = response.toLowerCase();
134
+ const hasKeyword = keywords.some((kw) => lowerResponse.includes(kw));
135
+ if (hasKeyword) {
136
+ frequency++;
137
+ if (examples.length < 3) {
138
+ examples.push(response.substring(0, 100) + (response.length > 100 ? "..." : ""));
139
+ }
140
+ const responseSentiment = analyzeSentiment([response]);
141
+ sentimentSum += responseSentiment.overall;
142
+ }
143
+ }
144
+ if (frequency > 0) {
145
+ themes.push({
146
+ name: themeName,
147
+ description: `Theme related to ${themeName.toLowerCase()}`,
148
+ frequency,
149
+ sentiment: sentimentSum / frequency,
150
+ examples
151
+ });
152
+ }
153
+ }
154
+ return themes.sort((a, b) => b.frequency - a.frequency);
155
+ }
156
+ function generateActionItems(themes, sentiment) {
157
+ const actionItems = [];
158
+ for (const theme of themes) {
159
+ if (theme.sentiment < -0.2 && theme.frequency >= 3) {
160
+ actionItems.push({
161
+ priority: theme.frequency > 10 ? "high" : theme.frequency > 5 ? "medium" : "low",
162
+ category: theme.name,
163
+ recommendation: `Address concerns related to ${theme.name.toLowerCase()}`,
164
+ rationale: `${theme.frequency} responses mentioned ${theme.name.toLowerCase()} with negative sentiment (${theme.sentiment.toFixed(2)})`
165
+ });
166
+ }
167
+ }
168
+ if (sentiment.overall < -0.3) {
169
+ actionItems.unshift({
170
+ priority: "high",
171
+ category: "Overall Satisfaction",
172
+ recommendation: "Conduct immediate follow-up to address widespread dissatisfaction",
173
+ rationale: `Overall sentiment is negative (${sentiment.overall.toFixed(2)})`
174
+ });
175
+ }
176
+ if (actionItems.length === 0) {
177
+ actionItems.push({
178
+ priority: "medium",
179
+ category: "Continuous Improvement",
180
+ recommendation: "Continue monitoring employee satisfaction trends",
181
+ rationale: "No major issues identified, maintain current practices"
182
+ });
183
+ }
184
+ return actionItems;
185
+ }
186
+ function generateSummary(sentiment, themes, actionItems) {
187
+ const sentimentDesc = sentiment.label === "positive" ? "positive" : sentiment.label === "negative" ? "negative" : "neutral";
188
+ const topThemes = themes.slice(0, 3).map((t) => t.name);
189
+ const highPriorityActions = actionItems.filter((a) => a.priority === "high").length;
190
+ return `Survey analysis reveals ${sentimentDesc} overall sentiment (${sentiment.overall.toFixed(2)}). Top themes: ${topThemes.join(", ")}. ${highPriorityActions} high-priority action items identified.`;
191
+ }
192
+ var index_default = surveyAnalyzeTool;
193
+
194
+ export { index_default as default, surveyAnalyzeTool };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@tpmjs/official-survey-analyze",
3
+ "version": "0.1.0",
4
+ "description": "Analyzes employee survey responses to extract themes, sentiment, and action items",
5
+ "type": "module",
6
+ "keywords": [
7
+ "tpmjs",
8
+ "hr",
9
+ "survey",
10
+ "analysis",
11
+ "sentiment"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "devDependencies": {
23
+ "tsup": "^8.3.5",
24
+ "typescript": "^5.9.3",
25
+ "@tpmjs/tsconfig": "0.0.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/anthropics/tpmjs.git",
33
+ "directory": "packages/tools/official/survey-analyze"
34
+ },
35
+ "homepage": "https://tpmjs.com",
36
+ "license": "MIT",
37
+ "tpmjs": {
38
+ "category": "hr",
39
+ "frameworks": [
40
+ "vercel-ai"
41
+ ],
42
+ "tools": [
43
+ {
44
+ "name": "surveyAnalyzeTool",
45
+ "description": "Analyzes employee survey responses to extract themes, sentiment, and action items",
46
+ "parameters": [
47
+ {
48
+ "name": "responses",
49
+ "type": "string[]",
50
+ "description": "Survey responses from employees",
51
+ "required": true
52
+ },
53
+ {
54
+ "name": "questions",
55
+ "type": "string[]",
56
+ "description": "Survey questions asked",
57
+ "required": true
58
+ }
59
+ ],
60
+ "returns": {
61
+ "type": "SurveyAnalysis",
62
+ "description": "Survey analysis with themes, sentiment, and action items"
63
+ }
64
+ }
65
+ ]
66
+ },
67
+ "dependencies": {
68
+ "ai": "6.0.0-beta.124"
69
+ },
70
+ "scripts": {
71
+ "build": "tsup",
72
+ "dev": "tsup --watch",
73
+ "type-check": "tsc --noEmit",
74
+ "clean": "rm -rf dist .turbo"
75
+ }
76
+ }