@sprinterai/runtime 0.5.0 → 0.7.1
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/dist/agent/execute-agent.d.ts +5 -8
- package/dist/agent/execute-agent.d.ts.map +1 -1
- package/dist/agent/execute-agent.js +2 -3
- package/dist/agent/execute-agent.js.map +1 -1
- package/dist/chat/chat-handler.d.ts +2 -0
- package/dist/chat/chat-handler.d.ts.map +1 -1
- package/dist/chat/chat-handler.js +15 -12
- package/dist/chat/chat-handler.js.map +1 -1
- package/dist/chat/chat-handler.test.js +1 -0
- package/dist/chat/chat-handler.test.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/inngest/create-extraction-functions.d.ts +71 -0
- package/dist/inngest/create-extraction-functions.d.ts.map +1 -0
- package/dist/inngest/create-extraction-functions.js +71 -0
- package/dist/inngest/create-extraction-functions.js.map +1 -0
- package/dist/inngest/event-names.d.ts +20 -0
- package/dist/inngest/event-names.d.ts.map +1 -0
- package/dist/inngest/event-names.js +19 -0
- package/dist/inngest/event-names.js.map +1 -0
- package/dist/inngest/index.d.ts +5 -0
- package/dist/inngest/index.d.ts.map +1 -0
- package/dist/inngest/index.js +3 -0
- package/dist/inngest/index.js.map +1 -0
- package/dist/package-exports.test.d.ts +2 -0
- package/dist/package-exports.test.d.ts.map +1 -0
- package/dist/package-exports.test.js +36 -0
- package/dist/package-exports.test.js.map +1 -0
- package/dist/testing/in-memory-chat-store.d.ts +4 -3
- package/dist/testing/in-memory-chat-store.d.ts.map +1 -1
- package/dist/testing/in-memory-chat-store.js +1 -1
- package/dist/testing/in-memory-chat-store.js.map +1 -1
- package/dist/tool/entity-tools-factory.d.ts.map +1 -1
- package/dist/tool/entity-tools-factory.js +80 -0
- package/dist/tool/entity-tools-factory.js.map +1 -1
- package/dist/tool/entity-tools-factory.test.js +68 -2
- package/dist/tool/entity-tools-factory.test.js.map +1 -1
- package/dist/tool/gallery/gallery-module.d.ts +4 -0
- package/dist/tool/gallery/gallery-module.d.ts.map +1 -0
- package/dist/tool/gallery/gallery-module.js +10 -0
- package/dist/tool/gallery/gallery-module.js.map +1 -0
- package/dist/tool/gallery/gallery-tool-definitions.d.ts +17 -0
- package/dist/tool/gallery/gallery-tool-definitions.d.ts.map +1 -0
- package/dist/tool/gallery/gallery-tool-definitions.js +748 -0
- package/dist/tool/gallery/gallery-tool-definitions.js.map +1 -0
- package/dist/tool/gallery/gallery-tools.d.ts +11 -0
- package/dist/tool/gallery/gallery-tools.d.ts.map +1 -0
- package/dist/tool/gallery/gallery-tools.js +971 -0
- package/dist/tool/gallery/gallery-tools.js.map +1 -0
- package/dist/tool/gallery/gallery-tools.test.d.ts +2 -0
- package/dist/tool/gallery/gallery-tools.test.d.ts.map +1 -0
- package/dist/tool/gallery/gallery-tools.test.js +75 -0
- package/dist/tool/gallery/gallery-tools.test.js.map +1 -0
- package/dist/tool/gallery/index.d.ts +5 -0
- package/dist/tool/gallery/index.d.ts.map +1 -0
- package/dist/tool/gallery/index.js +4 -0
- package/dist/tool/gallery/index.js.map +1 -0
- package/dist/tool/index.d.ts +9 -7
- package/dist/tool/index.d.ts.map +1 -1
- package/dist/tool/index.js +6 -5
- package/dist/tool/index.js.map +1 -1
- package/dist/tool/tool-registry.d.ts +1 -1
- package/dist/tool/tool-registry.d.ts.map +1 -1
- package/dist/tool/tool-registry.js +1 -0
- package/dist/tool/tool-registry.js.map +1 -1
- package/dist/tool/tool-registry.test.js +7 -0
- package/dist/tool/tool-registry.test.js.map +1 -1
- package/dist/workflow/create-node-executor.d.ts +40 -0
- package/dist/workflow/create-node-executor.d.ts.map +1 -0
- package/dist/workflow/create-node-executor.js +234 -0
- package/dist/workflow/create-node-executor.js.map +1 -0
- package/dist/workflow/field-recovery.d.ts +12 -0
- package/dist/workflow/field-recovery.d.ts.map +1 -0
- package/dist/workflow/field-recovery.js +86 -0
- package/dist/workflow/field-recovery.js.map +1 -0
- package/dist/workflow/index.d.ts +5 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/run-entity-workflow.d.ts +53 -0
- package/dist/workflow/run-entity-workflow.d.ts.map +1 -0
- package/dist/workflow/run-entity-workflow.js +72 -0
- package/dist/workflow/run-entity-workflow.js.map +1 -0
- package/package.json +123 -3
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gallery tools — executable ToolDefinition implementations for all 17
|
|
3
|
+
* interactive demo tools. Each tool has a Zod input schema and an
|
|
4
|
+
* async execute function that performs the computation logic.
|
|
5
|
+
*
|
|
6
|
+
* These complement gallery-tool-definitions.ts (which provides JSON Schema
|
|
7
|
+
* metadata for seeding the database / rendering forms).
|
|
8
|
+
*/
|
|
9
|
+
import { defineTool } from '@sprinterai/core';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
// ── Roofing & Home Services ──────────────────────────────────────────
|
|
12
|
+
const roofReadinessCheck = defineTool({
|
|
13
|
+
slug: 'roof-readiness-check',
|
|
14
|
+
name: 'Roof Readiness Check',
|
|
15
|
+
description: 'Help homeowners understand if they likely need repair, replacement, or a professional inspection first.',
|
|
16
|
+
category: 'roofing',
|
|
17
|
+
icon: 'home',
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
roofAge: z.enum(['Under 10 years', '10–19 years', '20–24 years', '25+ years']),
|
|
20
|
+
symptom: z.enum([
|
|
21
|
+
'Granule loss / cosmetic wear',
|
|
22
|
+
'Missing shingles or flashing concern',
|
|
23
|
+
'Active leak or interior staining',
|
|
24
|
+
'Sagging / structural concern',
|
|
25
|
+
]),
|
|
26
|
+
stormStatus: z.enum([
|
|
27
|
+
'No known storm event',
|
|
28
|
+
'Storm damage suspected',
|
|
29
|
+
'Known storm / claim discussion',
|
|
30
|
+
]),
|
|
31
|
+
timeline: z.enum(['Just exploring', 'This month', 'This week', 'Need help immediately']),
|
|
32
|
+
}),
|
|
33
|
+
async execute(raw) {
|
|
34
|
+
const input = raw;
|
|
35
|
+
const ageScores = {
|
|
36
|
+
'Under 10 years': 1,
|
|
37
|
+
'10–19 years': 1,
|
|
38
|
+
'20–24 years': 2,
|
|
39
|
+
'25+ years': 3,
|
|
40
|
+
};
|
|
41
|
+
const symptomScores = {
|
|
42
|
+
'Granule loss / cosmetic wear': 1,
|
|
43
|
+
'Missing shingles or flashing concern': 2,
|
|
44
|
+
'Active leak or interior staining': 4,
|
|
45
|
+
'Sagging / structural concern': 5,
|
|
46
|
+
};
|
|
47
|
+
const stormScores = {
|
|
48
|
+
'No known storm event': 0,
|
|
49
|
+
'Storm damage suspected': 2,
|
|
50
|
+
'Known storm / claim discussion': 3,
|
|
51
|
+
};
|
|
52
|
+
const timelineScores = {
|
|
53
|
+
'Just exploring': 0,
|
|
54
|
+
'This month': 1,
|
|
55
|
+
'This week': 2,
|
|
56
|
+
'Need help immediately': 3,
|
|
57
|
+
};
|
|
58
|
+
const score = (ageScores[input.roofAge] ?? 0) +
|
|
59
|
+
(symptomScores[input.symptom] ?? 0) +
|
|
60
|
+
(stormScores[input.stormStatus] ?? 0) +
|
|
61
|
+
(timelineScores[input.timeline] ?? 0);
|
|
62
|
+
const urgency = score >= 9 ? 'High' : score >= 5 ? 'Medium' : 'Low';
|
|
63
|
+
const path = urgency === 'High'
|
|
64
|
+
? 'Replacement review'
|
|
65
|
+
: urgency === 'Medium'
|
|
66
|
+
? 'Inspection + insurance review'
|
|
67
|
+
: 'Repair-first consult';
|
|
68
|
+
return { urgency, likelyPath: path, score, summary: `${urgency} urgency — ${path}` };
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const projectBudgetEstimator = defineTool({
|
|
72
|
+
slug: 'project-budget-estimator',
|
|
73
|
+
name: 'Project Budget Estimator',
|
|
74
|
+
description: 'Give homeowners a rough project cost band before they ever talk to sales.',
|
|
75
|
+
category: 'roofing',
|
|
76
|
+
icon: 'calculator',
|
|
77
|
+
inputSchema: z.object({
|
|
78
|
+
squares: z.coerce.number().min(12).max(60),
|
|
79
|
+
material: z.enum([
|
|
80
|
+
'Architectural shingle',
|
|
81
|
+
'Premium shingle',
|
|
82
|
+
'Standing seam metal',
|
|
83
|
+
'Tile / specialty roof',
|
|
84
|
+
]),
|
|
85
|
+
complexity: z.enum([
|
|
86
|
+
'Simple roofline',
|
|
87
|
+
'Moderate tear-off / complexity',
|
|
88
|
+
'Steep pitch / complex details',
|
|
89
|
+
]),
|
|
90
|
+
insuranceCredit: z.enum([
|
|
91
|
+
'No claim contribution',
|
|
92
|
+
'$3,000 contribution',
|
|
93
|
+
'$7,000 contribution',
|
|
94
|
+
'$12,000 contribution',
|
|
95
|
+
]),
|
|
96
|
+
}),
|
|
97
|
+
async execute(raw) {
|
|
98
|
+
const input = raw;
|
|
99
|
+
const baseRates = {
|
|
100
|
+
'Architectural shingle': 540,
|
|
101
|
+
'Premium shingle': 760,
|
|
102
|
+
'Standing seam metal': 1040,
|
|
103
|
+
'Tile / specialty roof': 1460,
|
|
104
|
+
};
|
|
105
|
+
const multipliers = {
|
|
106
|
+
'Simple roofline': 1.0,
|
|
107
|
+
'Moderate tear-off / complexity': 1.12,
|
|
108
|
+
'Steep pitch / complex details': 1.28,
|
|
109
|
+
};
|
|
110
|
+
const credits = {
|
|
111
|
+
'No claim contribution': 0,
|
|
112
|
+
'$3,000 contribution': 3000,
|
|
113
|
+
'$7,000 contribution': 7000,
|
|
114
|
+
'$12,000 contribution': 12000,
|
|
115
|
+
};
|
|
116
|
+
const baseRate = baseRates[input.material] ?? 540;
|
|
117
|
+
const multiplier = multipliers[input.complexity] ?? 1.0;
|
|
118
|
+
const credit = credits[input.insuranceCredit] ?? 0;
|
|
119
|
+
const subtotal = input.squares * baseRate * multiplier;
|
|
120
|
+
const low = Math.round(subtotal * 0.92 - credit);
|
|
121
|
+
const high = Math.round(subtotal * 1.08 - credit);
|
|
122
|
+
return {
|
|
123
|
+
estimatedRange: `$${low.toLocaleString()} – $${high.toLocaleString()}`,
|
|
124
|
+
material: input.material,
|
|
125
|
+
complexity: input.complexity,
|
|
126
|
+
insuranceCredit: `$${credit.toLocaleString()}`,
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// ── Mortgage & Lending ───────────────────────────────────────────────
|
|
131
|
+
const borrowerScenarioStudio = defineTool({
|
|
132
|
+
slug: 'borrower-scenario-studio',
|
|
133
|
+
name: 'Borrower Scenario Studio',
|
|
134
|
+
description: 'Let borrowers test the real impact of rate, down payment, and timeline changes on their monthly payment.',
|
|
135
|
+
category: 'mortgage',
|
|
136
|
+
icon: 'landmark',
|
|
137
|
+
inputSchema: z.object({
|
|
138
|
+
purchasePrice: z.coerce.number().min(250000).max(1200000),
|
|
139
|
+
downPaymentPct: z.coerce.number().min(3).max(30),
|
|
140
|
+
creditRange: z.enum(['660–699', '700–719', '720–739', '740+']),
|
|
141
|
+
timeline: z.enum(['30 days', '60 days', '90 days', '6+ months']),
|
|
142
|
+
}),
|
|
143
|
+
async execute(raw) {
|
|
144
|
+
const input = raw;
|
|
145
|
+
const baseRate = 6.55;
|
|
146
|
+
const creditAdj = {
|
|
147
|
+
'660–699': 0.6,
|
|
148
|
+
'700–719': 0.28,
|
|
149
|
+
'720–739': 0,
|
|
150
|
+
'740+': -0.15,
|
|
151
|
+
};
|
|
152
|
+
const dpAdj = input.downPaymentPct >= 20 ? -0.22 : input.downPaymentPct >= 10 ? 0 : 0.18;
|
|
153
|
+
const rate = baseRate + (creditAdj[input.creditRange] ?? 0) + dpAdj;
|
|
154
|
+
const loanAmount = input.purchasePrice * (1 - input.downPaymentPct / 100);
|
|
155
|
+
const monthlyRate = rate / 100 / 12;
|
|
156
|
+
const n = 360;
|
|
157
|
+
const payment = Math.round((loanAmount * (monthlyRate * Math.pow(1 + monthlyRate, n))) /
|
|
158
|
+
(Math.pow(1 + monthlyRate, n) - 1));
|
|
159
|
+
const taxes = Math.round((input.purchasePrice * 0.012) / 12);
|
|
160
|
+
const insurance = Math.round((input.purchasePrice * 0.0035) / 12);
|
|
161
|
+
const total = payment + taxes + insurance;
|
|
162
|
+
const readiness = rate < 6.5 ? 'Strong' : rate < 7 ? 'Workable' : 'Needs structuring';
|
|
163
|
+
return {
|
|
164
|
+
estimatedMonthlyPayment: `$${(total - 140).toLocaleString()} – $${(total + 140).toLocaleString()}`,
|
|
165
|
+
rate: `${rate.toFixed(3)}%`,
|
|
166
|
+
loanAmount: `$${loanAmount.toLocaleString()}`,
|
|
167
|
+
readiness,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const refiSavingsAudit = defineTool({
|
|
172
|
+
slug: 'refi-savings-audit',
|
|
173
|
+
name: 'Refi Savings Audit',
|
|
174
|
+
description: 'Help homeowners see whether a refinance conversation is worth having now.',
|
|
175
|
+
category: 'mortgage',
|
|
176
|
+
icon: 'percent',
|
|
177
|
+
inputSchema: z.object({
|
|
178
|
+
currentBalance: z.coerce.number().min(150000).max(900000),
|
|
179
|
+
currentRate: z.enum(['6.125%', '6.500%', '6.875%', '7.250%']),
|
|
180
|
+
homeValue: z.coerce.number().min(250000).max(1200000),
|
|
181
|
+
cashOutGoal: z.enum(['No cash-out', '$25,000', '$50,000', '$75,000']),
|
|
182
|
+
}),
|
|
183
|
+
async execute(raw) {
|
|
184
|
+
const input = raw;
|
|
185
|
+
const rate = parseFloat(input.currentRate);
|
|
186
|
+
const cashOutMap = {
|
|
187
|
+
'No cash-out': 0,
|
|
188
|
+
'$25,000': 25000,
|
|
189
|
+
'$50,000': 50000,
|
|
190
|
+
'$75,000': 75000,
|
|
191
|
+
};
|
|
192
|
+
const cashOut = cashOutMap[input.cashOutGoal] ?? 0;
|
|
193
|
+
const ltv = (input.currentBalance + cashOut) / input.homeValue;
|
|
194
|
+
const rateReduction = ltv <= 0.6 ? 0.75 : ltv <= 0.8 ? 0.5 : 0.25;
|
|
195
|
+
const cashPenalty = cashOut >= 75000 ? 0.2 : cashOut >= 25000 ? 0.1 : 0;
|
|
196
|
+
const newRate = Math.max(5.625, rate - rateReduction + cashPenalty);
|
|
197
|
+
const monthlyRate = rate / 100 / 12;
|
|
198
|
+
const newMonthlyRate = newRate / 100 / 12;
|
|
199
|
+
const n = 360;
|
|
200
|
+
const currentPayment = (input.currentBalance * (monthlyRate * Math.pow(1 + monthlyRate, n))) /
|
|
201
|
+
(Math.pow(1 + monthlyRate, n) - 1);
|
|
202
|
+
const newPayment = ((input.currentBalance + cashOut) * (newMonthlyRate * Math.pow(1 + newMonthlyRate, n))) /
|
|
203
|
+
(Math.pow(1 + newMonthlyRate, n) - 1);
|
|
204
|
+
const delta = Math.round(currentPayment - newPayment);
|
|
205
|
+
const breakEven = delta > 0 ? Math.round(4800 / delta) : null;
|
|
206
|
+
const recommendation = delta > 100 ? 'Worth a live review' : 'Monitor and revisit';
|
|
207
|
+
return {
|
|
208
|
+
monthlySavings: `$${delta}/mo`,
|
|
209
|
+
newRate: `${newRate.toFixed(3)}%`,
|
|
210
|
+
breakEvenMonths: breakEven,
|
|
211
|
+
recommendation,
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
// ── Building Products ────────────────────────────────────────────────
|
|
216
|
+
const materialMatchSelector = defineTool({
|
|
217
|
+
slug: 'material-match-selector',
|
|
218
|
+
name: 'Material Match Selector',
|
|
219
|
+
description: 'Guide specifiers to the right product family based on environment, budget, and install constraints.',
|
|
220
|
+
category: 'building-products',
|
|
221
|
+
icon: 'layers',
|
|
222
|
+
inputSchema: z.object({
|
|
223
|
+
application: z.enum([
|
|
224
|
+
'Exterior cladding',
|
|
225
|
+
'Multifamily retrofit',
|
|
226
|
+
'Commercial interior',
|
|
227
|
+
'Residential finish package',
|
|
228
|
+
]),
|
|
229
|
+
environment: z.enum([
|
|
230
|
+
'Coastal / humidity-heavy',
|
|
231
|
+
'Freeze-thaw exposure',
|
|
232
|
+
'Standard weather exposure',
|
|
233
|
+
'Interior / dry environment',
|
|
234
|
+
]),
|
|
235
|
+
budget: z.enum(['Value-focused', 'Mid-range', 'Premium']),
|
|
236
|
+
priority: z.enum([
|
|
237
|
+
'Low maintenance',
|
|
238
|
+
'Design flexibility',
|
|
239
|
+
'Install speed',
|
|
240
|
+
'Cleaner code path',
|
|
241
|
+
]),
|
|
242
|
+
}),
|
|
243
|
+
async execute(raw) {
|
|
244
|
+
const input = raw;
|
|
245
|
+
let result;
|
|
246
|
+
if (input.application.includes('interior') && input.priority === 'Design flexibility') {
|
|
247
|
+
// "Commercial interior" includes 'interior'
|
|
248
|
+
result = 'Architectural wall panel system';
|
|
249
|
+
}
|
|
250
|
+
else if (input.application.includes('retrofit') && input.priority === 'Install speed') {
|
|
251
|
+
// "Multifamily retrofit" includes 'retrofit'
|
|
252
|
+
result = 'Panelized retrofit cladding system';
|
|
253
|
+
}
|
|
254
|
+
else if (input.budget === 'Premium' && input.priority === 'Design flexibility') {
|
|
255
|
+
result = 'Powder-coated aluminum panel';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
result = 'Fiber-cement rainscreen panel';
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
recommendedFamily: result,
|
|
262
|
+
application: input.application,
|
|
263
|
+
environment: input.environment,
|
|
264
|
+
priority: input.priority,
|
|
265
|
+
};
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const specReadinessAudit = defineTool({
|
|
269
|
+
slug: 'spec-readiness-audit',
|
|
270
|
+
name: 'Spec Readiness Audit',
|
|
271
|
+
description: 'Expose missing inputs before an architect or contractor sends an incomplete request.',
|
|
272
|
+
category: 'building-products',
|
|
273
|
+
icon: 'clipboard-check',
|
|
274
|
+
inputSchema: z.object({
|
|
275
|
+
projectType: z.enum([
|
|
276
|
+
'Multifamily retrofit',
|
|
277
|
+
'New commercial build',
|
|
278
|
+
'Tenant improvement',
|
|
279
|
+
'Custom residential',
|
|
280
|
+
]),
|
|
281
|
+
substrateStatus: z.enum(['Unknown', 'Assumed but unverified', 'Confirmed']),
|
|
282
|
+
installMethod: z.enum([
|
|
283
|
+
'Undecided',
|
|
284
|
+
'Direct attach',
|
|
285
|
+
'Clip / rail system',
|
|
286
|
+
'Panelized assembly',
|
|
287
|
+
]),
|
|
288
|
+
compliance: z.enum([
|
|
289
|
+
'Standard code review',
|
|
290
|
+
'NFPA / fire path needed',
|
|
291
|
+
'Acoustic performance needed',
|
|
292
|
+
'Energy / envelope focus',
|
|
293
|
+
]),
|
|
294
|
+
}),
|
|
295
|
+
async execute(raw) {
|
|
296
|
+
const input = raw;
|
|
297
|
+
const gaps = [];
|
|
298
|
+
if (input.substrateStatus !== 'Confirmed') {
|
|
299
|
+
gaps.push('Substrate and assembly confirmation');
|
|
300
|
+
}
|
|
301
|
+
if (input.installMethod === 'Undecided') {
|
|
302
|
+
gaps.push('Install method / attachment path');
|
|
303
|
+
}
|
|
304
|
+
if (input.compliance.includes('NFPA')) {
|
|
305
|
+
gaps.push('Fire / assembly documentation package');
|
|
306
|
+
}
|
|
307
|
+
if (input.projectType.includes('retrofit')) {
|
|
308
|
+
gaps.push('Existing wall condition verification');
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
readiness: gaps.length === 0 ? 'Ready' : `${gaps.length} items missing`,
|
|
312
|
+
missingItems: gaps,
|
|
313
|
+
topGap: gaps[0] ?? 'None',
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
// ── Legal Intake ─────────────────────────────────────────────────────
|
|
318
|
+
const matterFitScreener = defineTool({
|
|
319
|
+
slug: 'matter-fit-screener',
|
|
320
|
+
name: 'Matter Fit Screener',
|
|
321
|
+
description: 'Let prospects understand likely case fit before waiting on a callback.',
|
|
322
|
+
category: 'legal-intake',
|
|
323
|
+
icon: 'scale',
|
|
324
|
+
inputSchema: z.object({
|
|
325
|
+
practiceArea: z.enum(['Personal Injury', 'Employment', 'Immigration', 'Consumer Protection']),
|
|
326
|
+
incidentType: z.enum([
|
|
327
|
+
'Auto accident',
|
|
328
|
+
'Workplace injury',
|
|
329
|
+
'Product liability',
|
|
330
|
+
'Wrongful termination',
|
|
331
|
+
'Wage / hour dispute',
|
|
332
|
+
'Visa application',
|
|
333
|
+
'Billing or contract dispute',
|
|
334
|
+
'Other',
|
|
335
|
+
]),
|
|
336
|
+
timeline: z.enum(['Less than 30 days', '30–90 days', 'More than 90 days', 'No deadline yet']),
|
|
337
|
+
evidence: z.enum([
|
|
338
|
+
'None gathered yet',
|
|
339
|
+
'Some (photos, records, etc.)',
|
|
340
|
+
'Extensive documentation',
|
|
341
|
+
]),
|
|
342
|
+
}),
|
|
343
|
+
async execute(raw) {
|
|
344
|
+
const input = raw;
|
|
345
|
+
const practiceScores = {
|
|
346
|
+
'Personal Injury': 4,
|
|
347
|
+
Employment: 3,
|
|
348
|
+
Immigration: 2,
|
|
349
|
+
'Consumer Protection': 2,
|
|
350
|
+
};
|
|
351
|
+
const incidentScores = {
|
|
352
|
+
'Auto accident': 4,
|
|
353
|
+
'Workplace injury': 3,
|
|
354
|
+
'Product liability': 3,
|
|
355
|
+
'Wrongful termination': 3,
|
|
356
|
+
'Wage / hour dispute': 2,
|
|
357
|
+
'Visa application': 2,
|
|
358
|
+
};
|
|
359
|
+
const timelineScores = {
|
|
360
|
+
'Less than 30 days': 4,
|
|
361
|
+
'30–90 days': 3,
|
|
362
|
+
'More than 90 days': 1,
|
|
363
|
+
'No deadline yet': 2,
|
|
364
|
+
};
|
|
365
|
+
const evidenceScores = {
|
|
366
|
+
'None gathered yet': 0,
|
|
367
|
+
'Some (photos, records, etc.)': 2,
|
|
368
|
+
'Extensive documentation': 4,
|
|
369
|
+
};
|
|
370
|
+
const total = (practiceScores[input.practiceArea] ?? 0) +
|
|
371
|
+
(incidentScores[input.incidentType] ?? 1) +
|
|
372
|
+
(timelineScores[input.timeline] ?? 0) +
|
|
373
|
+
(evidenceScores[input.evidence] ?? 0);
|
|
374
|
+
const tier = total >= 11 ? 'Strong' : total >= 7 ? 'Possible' : 'Unlikely';
|
|
375
|
+
const path = tier === 'Strong'
|
|
376
|
+
? 'Priority attorney review'
|
|
377
|
+
: tier === 'Possible'
|
|
378
|
+
? 'Standard intake callback'
|
|
379
|
+
: 'Self-service resources';
|
|
380
|
+
return { fitTier: tier, intakePath: path, score: total };
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
// ── Healthcare & Clinics ─────────────────────────────────────────────
|
|
384
|
+
const consultReadinessNavigator = defineTool({
|
|
385
|
+
slug: 'consult-readiness-navigator',
|
|
386
|
+
name: 'Consult Readiness Navigator',
|
|
387
|
+
description: 'Guide patients through fit, timing, and expectation questions before they book a consultation.',
|
|
388
|
+
category: 'clinics',
|
|
389
|
+
icon: 'stethoscope',
|
|
390
|
+
inputSchema: z.object({
|
|
391
|
+
category: z.enum(['Aesthetics / MedSpa', 'Dental', 'Sports Medicine', 'Functional Medicine']),
|
|
392
|
+
concern: z.enum([
|
|
393
|
+
'Body contouring',
|
|
394
|
+
'Skin rejuvenation',
|
|
395
|
+
'Injectables / fillers',
|
|
396
|
+
'Laser treatments',
|
|
397
|
+
'Cosmetic dentistry',
|
|
398
|
+
'Implants',
|
|
399
|
+
'Orthodontics',
|
|
400
|
+
'Restorative',
|
|
401
|
+
'Injury rehabilitation',
|
|
402
|
+
'Performance optimization',
|
|
403
|
+
'Chronic pain management',
|
|
404
|
+
'Surgical consultation',
|
|
405
|
+
'Hormone optimization',
|
|
406
|
+
'Gut health',
|
|
407
|
+
'Autoimmune support',
|
|
408
|
+
'Longevity / wellness',
|
|
409
|
+
]),
|
|
410
|
+
budget: z.enum(['$500–$1,500', '$1,500–$5,000', '$5,000+']),
|
|
411
|
+
urgency: z.enum(['Exploring (3+ months)', 'Planning (1–3 months)', 'Ready now']),
|
|
412
|
+
}),
|
|
413
|
+
async execute(raw) {
|
|
414
|
+
const input = raw;
|
|
415
|
+
const catScores = {
|
|
416
|
+
'Aesthetics / MedSpa': 3,
|
|
417
|
+
Dental: 3,
|
|
418
|
+
'Sports Medicine': 2,
|
|
419
|
+
'Functional Medicine': 2,
|
|
420
|
+
};
|
|
421
|
+
const budgetScores = {
|
|
422
|
+
'$500–$1,500': 1,
|
|
423
|
+
'$1,500–$5,000': 3,
|
|
424
|
+
'$5,000+': 4,
|
|
425
|
+
};
|
|
426
|
+
const urgencyScores = {
|
|
427
|
+
'Exploring (3+ months)': 1,
|
|
428
|
+
'Planning (1–3 months)': 3,
|
|
429
|
+
'Ready now': 4,
|
|
430
|
+
};
|
|
431
|
+
const total = (catScores[input.category] ?? 0) +
|
|
432
|
+
(budgetScores[input.budget] ?? 0) +
|
|
433
|
+
(urgencyScores[input.urgency] ?? 0);
|
|
434
|
+
const fit = total >= 9 ? 'High' : total >= 6 ? 'Medium' : 'Low';
|
|
435
|
+
const serviceTier = input.budget.includes('5,000+')
|
|
436
|
+
? 'Premium'
|
|
437
|
+
: input.budget.includes('1,500')
|
|
438
|
+
? 'Standard'
|
|
439
|
+
: 'Introductory';
|
|
440
|
+
const nextStep = fit === 'High'
|
|
441
|
+
? 'Book priority consultation'
|
|
442
|
+
: fit === 'Medium'
|
|
443
|
+
? 'Schedule discovery call'
|
|
444
|
+
: 'Review resources';
|
|
445
|
+
return {
|
|
446
|
+
fitScore: fit,
|
|
447
|
+
serviceTier,
|
|
448
|
+
nextStep,
|
|
449
|
+
category: input.category,
|
|
450
|
+
concern: input.concern,
|
|
451
|
+
};
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
// ── Recruiting & Staffing ────────────────────────────────────────────
|
|
455
|
+
const candidateFitSnapshot = defineTool({
|
|
456
|
+
slug: 'candidate-fit-snapshot',
|
|
457
|
+
name: 'Candidate Fit Snapshot',
|
|
458
|
+
description: 'Show candidates how well they match a role before a recruiter spends live time.',
|
|
459
|
+
category: 'staffing',
|
|
460
|
+
icon: 'user-check',
|
|
461
|
+
inputSchema: z.object({
|
|
462
|
+
roleType: z.enum(['Technology', 'Finance / Accounting', 'Operations', 'Creative / Marketing']),
|
|
463
|
+
experience: z.enum(['0–2 years', '3–7 years', '8–15 years', '15+ years']),
|
|
464
|
+
workPref: z.enum(['Remote', 'Hybrid', 'Onsite', 'Flexible']),
|
|
465
|
+
startTimeline: z.enum(['Immediately', 'Within 30 days', '60+ days']),
|
|
466
|
+
}),
|
|
467
|
+
async execute(raw) {
|
|
468
|
+
const input = raw;
|
|
469
|
+
const roleScores = {
|
|
470
|
+
Technology: 3,
|
|
471
|
+
'Finance / Accounting': 3,
|
|
472
|
+
Operations: 2,
|
|
473
|
+
'Creative / Marketing': 2,
|
|
474
|
+
};
|
|
475
|
+
const expScores = {
|
|
476
|
+
'0–2 years': 1,
|
|
477
|
+
'3–7 years': 3,
|
|
478
|
+
'8–15 years': 4,
|
|
479
|
+
'15+ years': 3,
|
|
480
|
+
};
|
|
481
|
+
const workScores = {
|
|
482
|
+
Remote: 2,
|
|
483
|
+
Hybrid: 3,
|
|
484
|
+
Onsite: 2,
|
|
485
|
+
Flexible: 4,
|
|
486
|
+
};
|
|
487
|
+
const startScores = {
|
|
488
|
+
Immediately: 4,
|
|
489
|
+
'Within 30 days': 3,
|
|
490
|
+
'60+ days': 1,
|
|
491
|
+
};
|
|
492
|
+
const rawScore = (roleScores[input.roleType] ?? 0) +
|
|
493
|
+
(expScores[input.experience] ?? 0) +
|
|
494
|
+
(workScores[input.workPref] ?? 0) +
|
|
495
|
+
(startScores[input.startTimeline] ?? 0);
|
|
496
|
+
const matchScore = Math.min(10, Math.round((rawScore / 15) * 10 * 10) / 10);
|
|
497
|
+
const tier = matchScore >= 8 ? 'Hot' : matchScore >= 5.5 ? 'Warm' : 'Cool';
|
|
498
|
+
const timeToPlace = tier === 'Hot' ? '2–4 weeks' : tier === 'Warm' ? '4–8 weeks' : '8+ weeks';
|
|
499
|
+
return {
|
|
500
|
+
matchScore,
|
|
501
|
+
tier,
|
|
502
|
+
estimatedTimeToPlacement: timeToPlace,
|
|
503
|
+
roleType: input.roleType,
|
|
504
|
+
experience: input.experience,
|
|
505
|
+
};
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
// ── Tax Advisory ─────────────────────────────────────────────────────
|
|
509
|
+
const taxStrategySnapshot = defineTool({
|
|
510
|
+
slug: 'tax-strategy-snapshot',
|
|
511
|
+
name: 'Tax Strategy Snapshot',
|
|
512
|
+
description: 'Help business owners gauge whether there is meaningful strategy upside before a paid advisory conversation.',
|
|
513
|
+
category: 'tax-advisory',
|
|
514
|
+
icon: 'receipt',
|
|
515
|
+
inputSchema: z.object({
|
|
516
|
+
entityType: z.enum(['Sole Proprietorship', 'LLC', 'S-Corp', 'C-Corp']),
|
|
517
|
+
revenue: z.enum(['Under $500k', '$500k–$1M', '$1M–$3M', '$3M+']),
|
|
518
|
+
compStyle: z.enum(['W-2 only', 'W-2 + distributions', 'Distributions only', '1099 only']),
|
|
519
|
+
multiState: z.enum(['One state', 'Two states', 'Three or more states']),
|
|
520
|
+
}),
|
|
521
|
+
async execute(raw) {
|
|
522
|
+
const input = raw;
|
|
523
|
+
const entityScores = {
|
|
524
|
+
'Sole Proprietorship': 4,
|
|
525
|
+
LLC: 3,
|
|
526
|
+
'S-Corp': 2,
|
|
527
|
+
'C-Corp': 1,
|
|
528
|
+
};
|
|
529
|
+
const revScores = {
|
|
530
|
+
'Under $500k': 1,
|
|
531
|
+
'$500k–$1M': 2,
|
|
532
|
+
'$1M–$3M': 3,
|
|
533
|
+
'$3M+': 4,
|
|
534
|
+
};
|
|
535
|
+
const compScores = {
|
|
536
|
+
'W-2 only': 1,
|
|
537
|
+
'W-2 + distributions': 3,
|
|
538
|
+
'Distributions only': 4,
|
|
539
|
+
'1099 only': 4,
|
|
540
|
+
};
|
|
541
|
+
const stateScores = {
|
|
542
|
+
'One state': 0,
|
|
543
|
+
'Two states': 2,
|
|
544
|
+
'Three or more states': 3,
|
|
545
|
+
};
|
|
546
|
+
const total = (entityScores[input.entityType] ?? 0) +
|
|
547
|
+
(revScores[input.revenue] ?? 0) +
|
|
548
|
+
(compScores[input.compStyle] ?? 0) +
|
|
549
|
+
(stateScores[input.multiState] ?? 0);
|
|
550
|
+
const tier = total >= 10 ? 'High' : total >= 6 ? 'Moderate' : 'Low';
|
|
551
|
+
const revMap = {
|
|
552
|
+
'Under $500k': 500000,
|
|
553
|
+
'$500k–$1M': 750000,
|
|
554
|
+
'$1M–$3M': 2000000,
|
|
555
|
+
'$3M+': 4000000,
|
|
556
|
+
};
|
|
557
|
+
const revNum = revMap[input.revenue] ?? 500000;
|
|
558
|
+
const low = Math.round(revNum * 0.018);
|
|
559
|
+
const high = Math.round(revNum * 0.042);
|
|
560
|
+
return {
|
|
561
|
+
opportunityTier: tier,
|
|
562
|
+
upsideRange: `$${low.toLocaleString()} – $${high.toLocaleString()}`,
|
|
563
|
+
score: total,
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
const entityChoicePlanner = defineTool({
|
|
568
|
+
slug: 'entity-choice-planner',
|
|
569
|
+
name: 'Entity Choice Planner',
|
|
570
|
+
description: 'Give business owners a better starting point for entity and compensation structure conversations.',
|
|
571
|
+
category: 'tax-advisory',
|
|
572
|
+
icon: 'building',
|
|
573
|
+
inputSchema: z.object({
|
|
574
|
+
currentEntity: z.enum(['Sole Proprietorship', 'LLC', 'S-Corp', 'C-Corp']),
|
|
575
|
+
netProfit: z.coerce.number().min(50000).max(1000000),
|
|
576
|
+
ownerGoal: z.enum(['Reduce taxes', 'Fund growth', 'Plan exit', 'Protect assets']),
|
|
577
|
+
hiringPlans: z.enum(['Staying solo', 'Hiring soon', 'Already have employees']),
|
|
578
|
+
}),
|
|
579
|
+
async execute(raw) {
|
|
580
|
+
const input = raw;
|
|
581
|
+
let recommendation = 'S-Corp election';
|
|
582
|
+
if (input.currentEntity === 'S-Corp') {
|
|
583
|
+
recommendation = 'Optimize existing S-Corp structure';
|
|
584
|
+
}
|
|
585
|
+
else if (input.currentEntity === 'C-Corp') {
|
|
586
|
+
recommendation = 'Consult on C-Corp tradeoffs';
|
|
587
|
+
}
|
|
588
|
+
else if (input.hiringPlans === 'Hiring soon' && input.netProfit > 300000) {
|
|
589
|
+
recommendation = 'S-Corp + profit-sharing plan';
|
|
590
|
+
}
|
|
591
|
+
const currentSE = input.netProfit * 0.153 * 0.9235;
|
|
592
|
+
let savings = Math.round(currentSE * 0.35);
|
|
593
|
+
if (input.currentEntity === 'S-Corp') {
|
|
594
|
+
savings = Math.round(input.netProfit * 0.04);
|
|
595
|
+
}
|
|
596
|
+
if (input.hiringPlans === 'Hiring soon' && input.netProfit > 300000) {
|
|
597
|
+
savings = Math.round(input.netProfit * 0.12);
|
|
598
|
+
}
|
|
599
|
+
if (input.currentEntity === 'C-Corp') {
|
|
600
|
+
savings = 0;
|
|
601
|
+
}
|
|
602
|
+
const low = Math.round(savings * 0.8);
|
|
603
|
+
const high = Math.round(savings * 1.2);
|
|
604
|
+
return {
|
|
605
|
+
recommendation,
|
|
606
|
+
estimatedSavings: savings > 0
|
|
607
|
+
? `$${low.toLocaleString()} – $${high.toLocaleString()}`
|
|
608
|
+
: 'Structure-dependent',
|
|
609
|
+
currentEntity: input.currentEntity,
|
|
610
|
+
netProfit: `$${input.netProfit.toLocaleString()}`,
|
|
611
|
+
};
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
// ── Industrial Quotes ────────────────────────────────────────────────
|
|
615
|
+
const rfqReadinessBuilder = defineTool({
|
|
616
|
+
slug: 'rfq-readiness-builder',
|
|
617
|
+
name: 'RFQ Readiness Builder',
|
|
618
|
+
description: 'Help buyers gather the right technical details before they ask for a quote.',
|
|
619
|
+
category: 'industrial-quotes',
|
|
620
|
+
icon: 'clipboard-list',
|
|
621
|
+
inputSchema: z.object({
|
|
622
|
+
application: z.enum(['Conveyor drive', 'Pump drive', 'Mixer', 'Fan / blower', 'General']),
|
|
623
|
+
capacity: z.enum(['Light', 'Moderate', 'Heavy', 'Severe']),
|
|
624
|
+
environment: z.enum(['Indoor / clean', 'Outdoor / dusty', 'Washdown', 'Hazardous']),
|
|
625
|
+
timeline: z.enum(['2 weeks', '4 weeks', '8 weeks', 'Flexible']),
|
|
626
|
+
}),
|
|
627
|
+
async execute(raw) {
|
|
628
|
+
const input = raw;
|
|
629
|
+
const gaps = [];
|
|
630
|
+
if (input.application === 'Conveyor drive') {
|
|
631
|
+
gaps.push('Center-to-center distance');
|
|
632
|
+
gaps.push('Mounting orientation');
|
|
633
|
+
}
|
|
634
|
+
else if (input.application === 'Pump drive') {
|
|
635
|
+
gaps.push('Fluid type / specific gravity');
|
|
636
|
+
gaps.push('NPSH requirements');
|
|
637
|
+
}
|
|
638
|
+
else if (input.application === 'Mixer') {
|
|
639
|
+
gaps.push('Impeller diameter');
|
|
640
|
+
gaps.push('Liquid viscosity');
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
gaps.push('Service factor');
|
|
644
|
+
gaps.push('Overload class');
|
|
645
|
+
}
|
|
646
|
+
if (input.environment === 'Outdoor / dusty') {
|
|
647
|
+
gaps.push('IP/NEMA enclosure rating');
|
|
648
|
+
}
|
|
649
|
+
if (input.environment === 'Washdown') {
|
|
650
|
+
gaps.push('NSF/FDA compliance documentation');
|
|
651
|
+
}
|
|
652
|
+
if (input.environment === 'Hazardous') {
|
|
653
|
+
gaps.push('ATEX/NEC hazard classification');
|
|
654
|
+
}
|
|
655
|
+
const urgent = input.timeline === '2 weeks' || input.timeline === '4 weeks';
|
|
656
|
+
const readiness = gaps.length <= 1
|
|
657
|
+
? 'Ready to quote'
|
|
658
|
+
: gaps.length <= 3
|
|
659
|
+
? `Mostly ready — ${gaps.length} inputs missing`
|
|
660
|
+
: `Needs more detail — ${gaps.length} inputs missing`;
|
|
661
|
+
return {
|
|
662
|
+
readiness,
|
|
663
|
+
missingInputs: gaps,
|
|
664
|
+
urgent,
|
|
665
|
+
topMissing: gaps[0] ?? 'None',
|
|
666
|
+
};
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
const productFamilyShortlist = defineTool({
|
|
670
|
+
slug: 'product-family-shortlist',
|
|
671
|
+
name: 'Product Family Shortlist',
|
|
672
|
+
description: 'Guide buyers toward the right product family before a sales engineer gets involved.',
|
|
673
|
+
category: 'industrial-quotes',
|
|
674
|
+
icon: 'cog',
|
|
675
|
+
inputSchema: z.object({
|
|
676
|
+
industry: z.enum([
|
|
677
|
+
'Material handling',
|
|
678
|
+
'Food & beverage',
|
|
679
|
+
'Aggregate',
|
|
680
|
+
'Chemical',
|
|
681
|
+
'General manufacturing',
|
|
682
|
+
]),
|
|
683
|
+
useCase: z.enum([
|
|
684
|
+
'Moderate shock',
|
|
685
|
+
'Severe shock',
|
|
686
|
+
'Low-speed / high-torque',
|
|
687
|
+
'Variable speed',
|
|
688
|
+
]),
|
|
689
|
+
dutyLevel: z.enum(['Light', 'Standard', 'Heavy']),
|
|
690
|
+
constraint: z.enum(['Compact envelope', 'NEMA 4X', 'Budget priority', 'Lead time priority']),
|
|
691
|
+
}),
|
|
692
|
+
async execute(raw) {
|
|
693
|
+
const input = raw;
|
|
694
|
+
let family;
|
|
695
|
+
if (input.constraint === 'NEMA 4X' && input.dutyLevel !== 'Light') {
|
|
696
|
+
family = 'Shaft-mount with NEMA 4X enclosure';
|
|
697
|
+
}
|
|
698
|
+
else if (input.useCase === 'Severe shock') {
|
|
699
|
+
family = 'Heavy-duty parallel shaft reducer';
|
|
700
|
+
}
|
|
701
|
+
else if (input.useCase === 'Low-speed / high-torque') {
|
|
702
|
+
family = 'Planetary gear reducer';
|
|
703
|
+
}
|
|
704
|
+
else if (input.industry === 'Food & beverage') {
|
|
705
|
+
family = 'Food-grade stainless reducer';
|
|
706
|
+
}
|
|
707
|
+
else if (input.industry === 'Aggregate') {
|
|
708
|
+
family = 'Heavy-duty shaft-mount with backstop';
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
family = 'Inline helical gear reducer';
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
recommendedFamily: family,
|
|
715
|
+
industry: input.industry,
|
|
716
|
+
useCase: input.useCase,
|
|
717
|
+
constraint: input.constraint,
|
|
718
|
+
};
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
// ── Realtor Teams ────────────────────────────────────────────────────
|
|
722
|
+
const moveReadinessPlanner = defineTool({
|
|
723
|
+
slug: 'move-readiness-planner',
|
|
724
|
+
name: 'Move Readiness Planner',
|
|
725
|
+
description: 'Show buyers and sellers what to do next based on timing, budget, and urgency.',
|
|
726
|
+
category: 'realtor-teams',
|
|
727
|
+
icon: 'map-pin',
|
|
728
|
+
inputSchema: z.object({
|
|
729
|
+
moveType: z.enum(['Sell and buy', 'Sell only', 'Buy only', 'Rent first']),
|
|
730
|
+
timeline: z.enum(['1 month', '3 months', '6 months', 'Exploring']),
|
|
731
|
+
equity: z.coerce.number().min(0).max(800000),
|
|
732
|
+
prepStatus: z.enum(['Ready now', 'Light prep needed', 'Staging needed', 'Major work required']),
|
|
733
|
+
}),
|
|
734
|
+
async execute(raw) {
|
|
735
|
+
const input = raw;
|
|
736
|
+
const moveScores = {
|
|
737
|
+
'Buy only': 3,
|
|
738
|
+
'Sell only': 3,
|
|
739
|
+
'Sell and buy': 2,
|
|
740
|
+
'Rent first': 1,
|
|
741
|
+
};
|
|
742
|
+
const timeScores = {
|
|
743
|
+
'1 month': 4,
|
|
744
|
+
'3 months': 3,
|
|
745
|
+
'6 months': 2,
|
|
746
|
+
Exploring: 1,
|
|
747
|
+
};
|
|
748
|
+
const equityScore = input.equity >= 200000 ? 3 : input.equity >= 100000 ? 2 : 1;
|
|
749
|
+
const prepScores = {
|
|
750
|
+
'Ready now': 4,
|
|
751
|
+
'Light prep needed': 3,
|
|
752
|
+
'Staging needed': 2,
|
|
753
|
+
'Major work required': 1,
|
|
754
|
+
};
|
|
755
|
+
const total = (moveScores[input.moveType] ?? 0) +
|
|
756
|
+
(timeScores[input.timeline] ?? 0) +
|
|
757
|
+
equityScore +
|
|
758
|
+
(prepScores[input.prepStatus] ?? 0);
|
|
759
|
+
const readiness = total >= 11 ? 'Strong readiness' : total >= 7 ? 'On track — minor items' : 'Plan first';
|
|
760
|
+
const netProceeds = Math.round(input.equity * 0.93);
|
|
761
|
+
return {
|
|
762
|
+
readiness,
|
|
763
|
+
score: total,
|
|
764
|
+
netProceeds: `$${netProceeds.toLocaleString()}`,
|
|
765
|
+
moveType: input.moveType,
|
|
766
|
+
timeline: input.timeline,
|
|
767
|
+
};
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
const offerStrengthCheck = defineTool({
|
|
771
|
+
slug: 'offer-strength-check',
|
|
772
|
+
name: 'Offer Strength Check',
|
|
773
|
+
description: 'Help buyers understand how competitive their likely offer position is before touring.',
|
|
774
|
+
category: 'realtor-teams',
|
|
775
|
+
icon: 'shield-check',
|
|
776
|
+
inputSchema: z.object({
|
|
777
|
+
downPayment: z.coerce.number().min(10000).max(500000),
|
|
778
|
+
preApproval: z.enum(['Not started', 'Pre-qualified', 'Pre-approved', 'Cash buyer']),
|
|
779
|
+
marketSpeed: z.enum(['Slow', 'Moderate', 'Fast', 'Bidding war']),
|
|
780
|
+
closingFlex: z.enum(['Flexible', 'Moderate', 'Constrained']),
|
|
781
|
+
}),
|
|
782
|
+
async execute(raw) {
|
|
783
|
+
const input = raw;
|
|
784
|
+
const dpScore = input.downPayment >= 200000
|
|
785
|
+
? 4
|
|
786
|
+
: input.downPayment >= 100000
|
|
787
|
+
? 3
|
|
788
|
+
: input.downPayment >= 50000
|
|
789
|
+
? 2
|
|
790
|
+
: 1;
|
|
791
|
+
const approvalScores = {
|
|
792
|
+
'Cash buyer': 5,
|
|
793
|
+
'Pre-approved': 3,
|
|
794
|
+
'Pre-qualified': 1,
|
|
795
|
+
'Not started': 0,
|
|
796
|
+
};
|
|
797
|
+
const marketScores = {
|
|
798
|
+
Slow: 4,
|
|
799
|
+
Moderate: 3,
|
|
800
|
+
Fast: 2,
|
|
801
|
+
'Bidding war': 1,
|
|
802
|
+
};
|
|
803
|
+
const flexScores = {
|
|
804
|
+
Flexible: 3,
|
|
805
|
+
Moderate: 2,
|
|
806
|
+
Constrained: 1,
|
|
807
|
+
};
|
|
808
|
+
const approvalScore = approvalScores[input.preApproval] ?? 0;
|
|
809
|
+
const flexScore = flexScores[input.closingFlex] ?? 0;
|
|
810
|
+
const total = dpScore + approvalScore + (marketScores[input.marketSpeed] ?? 0) + flexScore;
|
|
811
|
+
const tier = total >= 13
|
|
812
|
+
? 'Very competitive'
|
|
813
|
+
: total >= 9
|
|
814
|
+
? 'Competitive'
|
|
815
|
+
: total >= 6
|
|
816
|
+
? 'Workable — gaps to fix'
|
|
817
|
+
: 'Needs strengthening';
|
|
818
|
+
const weakness = approvalScore <= 1
|
|
819
|
+
? 'Get pre-approved before competing'
|
|
820
|
+
: dpScore <= 2
|
|
821
|
+
? 'Increase down payment for stronger position'
|
|
822
|
+
: flexScore <= 1
|
|
823
|
+
? 'Add closing flexibility'
|
|
824
|
+
: 'Position looks solid';
|
|
825
|
+
return {
|
|
826
|
+
strengthTier: tier,
|
|
827
|
+
score: total,
|
|
828
|
+
primaryWeakness: weakness,
|
|
829
|
+
downPayment: `$${input.downPayment.toLocaleString()}`,
|
|
830
|
+
};
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
// ── Private Equity ───────────────────────────────────────────────────
|
|
834
|
+
const diligenceReadinessScorecard = defineTool({
|
|
835
|
+
slug: 'diligence-readiness-scorecard',
|
|
836
|
+
name: 'Diligence Readiness Scorecard',
|
|
837
|
+
description: 'Expose operational gaps before a management team enters a live deal process.',
|
|
838
|
+
category: 'private-equity',
|
|
839
|
+
icon: 'bar-chart',
|
|
840
|
+
inputSchema: z.object({
|
|
841
|
+
companyType: z.enum(['SaaS', 'Services', 'Manufacturing', 'Distribution']),
|
|
842
|
+
reportingCadence: z.enum(['Weekly', 'Monthly', 'Quarterly', 'Ad-hoc']),
|
|
843
|
+
keymanRisk: z.enum(['None', 'Present', 'Severe']),
|
|
844
|
+
financialModel: z.enum(['Complete', 'Partial', 'None']),
|
|
845
|
+
}),
|
|
846
|
+
async execute(raw) {
|
|
847
|
+
const input = raw;
|
|
848
|
+
const gaps = [];
|
|
849
|
+
if (input.keymanRisk === 'Present' || input.keymanRisk === 'Severe') {
|
|
850
|
+
gaps.push('Key-man dependency');
|
|
851
|
+
}
|
|
852
|
+
if (input.financialModel === 'Partial') {
|
|
853
|
+
gaps.push('Incomplete financial model');
|
|
854
|
+
}
|
|
855
|
+
if (input.financialModel === 'None') {
|
|
856
|
+
gaps.push('No financial model (critical)');
|
|
857
|
+
}
|
|
858
|
+
if (input.reportingCadence === 'Quarterly' || input.reportingCadence === 'Ad-hoc') {
|
|
859
|
+
gaps.push('Infrequent reporting');
|
|
860
|
+
}
|
|
861
|
+
if (input.companyType === 'Services' || input.companyType === 'Manufacturing') {
|
|
862
|
+
gaps.push('Customer concentration risk');
|
|
863
|
+
}
|
|
864
|
+
if (input.companyType === 'SaaS') {
|
|
865
|
+
gaps.push('ARR/churn metrics needed');
|
|
866
|
+
}
|
|
867
|
+
const readiness = gaps.length === 0
|
|
868
|
+
? 'Diligence-ready'
|
|
869
|
+
: gaps.length <= 2
|
|
870
|
+
? `Moderate — ${gaps.length} gaps`
|
|
871
|
+
: `Needs work — ${gaps.length} gaps`;
|
|
872
|
+
const quickWin = input.financialModel !== 'Complete'
|
|
873
|
+
? 'Complete the financial model'
|
|
874
|
+
: input.reportingCadence !== 'Weekly' && input.reportingCadence !== 'Monthly'
|
|
875
|
+
? 'Move to monthly reporting'
|
|
876
|
+
: 'Prepare data room';
|
|
877
|
+
return {
|
|
878
|
+
readiness,
|
|
879
|
+
gapCount: gaps.length,
|
|
880
|
+
gaps,
|
|
881
|
+
quickWin,
|
|
882
|
+
topRisk: gaps[0] ?? 'None',
|
|
883
|
+
};
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
const portcoSignalBrief = defineTool({
|
|
887
|
+
slug: 'portco-signal-brief',
|
|
888
|
+
name: 'Portco Signal Brief',
|
|
889
|
+
description: 'Show portfolio teams how a lightweight signal monitor could work before full implementation.',
|
|
890
|
+
category: 'private-equity',
|
|
891
|
+
icon: 'activity',
|
|
892
|
+
inputSchema: z.object({
|
|
893
|
+
revenueTrend: z.enum(['Growing', 'Flat', 'Declining']),
|
|
894
|
+
headcountTrend: z.enum(['Growing', 'Stable', 'Decline', 'Rapid decline']),
|
|
895
|
+
churnLevel: z.enum(['Low', 'Elevated', 'High']),
|
|
896
|
+
reportingLag: z.enum(['Under 7 days', '7–14 days', '15+ days', '30+ days']),
|
|
897
|
+
}),
|
|
898
|
+
async execute(raw) {
|
|
899
|
+
const input = raw;
|
|
900
|
+
let score = 0;
|
|
901
|
+
const triggers = [];
|
|
902
|
+
if (input.revenueTrend === 'Declining') {
|
|
903
|
+
score += 3;
|
|
904
|
+
triggers.push('Revenue decline');
|
|
905
|
+
}
|
|
906
|
+
else if (input.revenueTrend === 'Flat') {
|
|
907
|
+
score += 1;
|
|
908
|
+
}
|
|
909
|
+
if (input.headcountTrend === 'Rapid decline') {
|
|
910
|
+
score += 3;
|
|
911
|
+
triggers.push('Headcount reduction');
|
|
912
|
+
}
|
|
913
|
+
else if (input.headcountTrend === 'Decline') {
|
|
914
|
+
score += 2;
|
|
915
|
+
triggers.push('Headcount reduction');
|
|
916
|
+
}
|
|
917
|
+
if (input.churnLevel === 'High') {
|
|
918
|
+
score += 3;
|
|
919
|
+
triggers.push('Customer churn');
|
|
920
|
+
}
|
|
921
|
+
else if (input.churnLevel === 'Elevated') {
|
|
922
|
+
score += 2;
|
|
923
|
+
triggers.push('Elevated churn');
|
|
924
|
+
}
|
|
925
|
+
if (input.reportingLag === '30+ days') {
|
|
926
|
+
score += 2;
|
|
927
|
+
triggers.push('Reporting lag');
|
|
928
|
+
}
|
|
929
|
+
else if (input.reportingLag === '15+ days') {
|
|
930
|
+
score += 1;
|
|
931
|
+
}
|
|
932
|
+
const status = score >= 6
|
|
933
|
+
? 'Escalation recommended'
|
|
934
|
+
: score >= 3
|
|
935
|
+
? 'Watchlist'
|
|
936
|
+
: 'Monitoring — no escalation';
|
|
937
|
+
const cadence = score >= 6
|
|
938
|
+
? 'Weekly ops call for 60 days'
|
|
939
|
+
: score >= 3
|
|
940
|
+
? 'Bi-weekly check-in + monthly deep-dive'
|
|
941
|
+
: 'Standard monthly reporting';
|
|
942
|
+
return {
|
|
943
|
+
status,
|
|
944
|
+
score,
|
|
945
|
+
triggers,
|
|
946
|
+
cadence,
|
|
947
|
+
primaryTrigger: triggers[0] ?? 'No active triggers',
|
|
948
|
+
};
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
// ── Export ────────────────────────────────────────────────────────────
|
|
952
|
+
export const galleryTools = [
|
|
953
|
+
roofReadinessCheck,
|
|
954
|
+
projectBudgetEstimator,
|
|
955
|
+
borrowerScenarioStudio,
|
|
956
|
+
refiSavingsAudit,
|
|
957
|
+
materialMatchSelector,
|
|
958
|
+
specReadinessAudit,
|
|
959
|
+
matterFitScreener,
|
|
960
|
+
consultReadinessNavigator,
|
|
961
|
+
candidateFitSnapshot,
|
|
962
|
+
taxStrategySnapshot,
|
|
963
|
+
entityChoicePlanner,
|
|
964
|
+
rfqReadinessBuilder,
|
|
965
|
+
productFamilyShortlist,
|
|
966
|
+
moveReadinessPlanner,
|
|
967
|
+
offerStrengthCheck,
|
|
968
|
+
diligenceReadinessScorecard,
|
|
969
|
+
portcoSignalBrief,
|
|
970
|
+
];
|
|
971
|
+
//# sourceMappingURL=gallery-tools.js.map
|