UNPKG

jobnimbus-mcp-client

Version:

JobNimbus MCP Client - Connect Claude Desktop to remote JobNimbus MCP server

281 lines 14.4 kB
/** * Get Door Knocking Scripts By Area * AI-generated door-to-door sales scripts customized by area demographics and market analysis */ import { BaseTool } from '../baseTool.js'; export class GetDoorKnockingScriptsByAreaTool extends BaseTool { get definition() { return { name: 'get_door_knocking_scripts_by_area', description: 'AI-generated door-to-door sales scripts customized by area demographics, market analysis, and proven techniques', inputSchema: { type: 'object', properties: { area: { type: 'string', description: 'Specific area/city to analyze (optional, analyzes all areas if not specified)', }, service_type: { type: 'string', description: 'Service type to focus scripts on (e.g., "roofing", "solar", "hvac")', }, include_objection_handlers: { type: 'boolean', default: true, description: 'Include common objection handlers', }, script_style: { type: 'string', enum: ['conversational', 'professional', 'friendly', 'consultative'], default: 'consultative', description: 'Communication style for scripts', }, }, }, }; } async execute(input, context) { try { const targetArea = input.area; const serviceType = input.service_type || 'general services'; const includeObjections = input.include_objection_handlers !== false; const scriptStyle = input.script_style || 'consultative'; // Fetch data const [jobsResponse, contactsResponse] = await Promise.all([ this.client.get(context.apiKey, 'jobs', { size: 100 }), this.client.get(context.apiKey, 'contacts', { size: 100 }), ]); const jobs = jobsResponse.data?.results || []; const contacts = contactsResponse.data?.results || []; // Build area profiles const areaMap = new Map(); // Process jobs const now = Date.now(); const thirtyDaysAgo = now - (30 * 24 * 60 * 60 * 1000); for (const job of jobs) { const city = job.city || 'Unknown'; const state = job.state || ''; const areaKey = city && state ? `${city}, ${state}` : city; if (targetArea && !areaKey.toLowerCase().includes(targetArea.toLowerCase())) { continue; } if (!areaMap.has(areaKey)) { areaMap.set(areaKey, { jobs: [], contacts: [], wonJobs: 0, serviceTypes: new Map(), }); } const area = areaMap.get(areaKey); area.jobs.push(job); const isWon = (job.status_name || '').toLowerCase().includes('complete') || (job.status_name || '').toLowerCase().includes('won'); if (isWon) area.wonJobs++; const jobType = job.job_type_name || 'General'; area.serviceTypes.set(jobType, (area.serviceTypes.get(jobType) || 0) + 1); } // Process contacts for (const contact of contacts) { const city = contact.city || 'Unknown'; const state = contact.state_text || ''; const areaKey = city && state ? `${city}, ${state}` : city; if (targetArea && !areaKey.toLowerCase().includes(targetArea.toLowerCase())) { continue; } if (!areaMap.has(areaKey)) { areaMap.set(areaKey, { jobs: [], contacts: [], wonJobs: 0, serviceTypes: new Map(), }); } const area = areaMap.get(areaKey); area.contacts.push(contact); } // Build area profiles const areaProfiles = []; for (const [areaName, data] of areaMap.entries()) { if (data.jobs.length === 0) continue; const recentJobs = data.jobs.filter(j => (j.date_created || 0) > thirtyDaysAgo).length; const winRate = data.jobs.length > 0 ? (data.wonJobs / data.jobs.length) * 100 : 0; const customerDensity = data.contacts.length / Math.max(data.jobs.length, 1); // Determine demographic type const hasCommercial = Array.from(data.serviceTypes.keys()).some(type => type.toLowerCase().includes('commercial') || type.toLowerCase().includes('business')); const demographicType = hasCommercial && data.jobs.length > 10 ? 'Mixed' : hasCommercial ? 'Commercial' : 'Residential'; // Property value estimate (simplified) const avgPropertyValue = winRate > 60 ? 'High' : winRate > 30 ? 'Medium' : 'Entry-level'; // Top service types const commonServices = Array.from(data.serviceTypes.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 3) .map(([type]) => type); areaProfiles.push({ area_name: areaName, demographic_type: demographicType, avg_property_value_estimate: avgPropertyValue, customer_density: customerDensity, recent_jobs_count: recentJobs, win_rate: winRate, common_service_types: commonServices, }); } // Sort by recent activity areaProfiles.sort((a, b) => b.recent_jobs_count - a.recent_jobs_count); // Generate scripts for each area const scriptTemplates = []; for (const profile of areaProfiles.slice(0, 5)) { // Top 5 areas const script = this.generateScript(profile, serviceType, scriptStyle, includeObjections); scriptTemplates.push(script); } // Generate area insights const insights = []; for (const profile of areaProfiles.slice(0, 3)) { if (profile.win_rate > 60) { insights.push({ insight_type: 'Opportunity', description: `${profile.area_name} has high win rate (${profile.win_rate.toFixed(1)}%)`, recommended_action: 'Increase door-knocking frequency and referral requests', }); } if (profile.win_rate < 30 && profile.recent_jobs_count > 0) { insights.push({ insight_type: 'Challenge', description: `${profile.area_name} has low win rate (${profile.win_rate.toFixed(1)}%)`, recommended_action: 'Review pricing strategy and competitive positioning', }); } if (profile.customer_density < 0.5) { insights.push({ insight_type: 'Strategy', description: `${profile.area_name} has low customer density - untapped market`, recommended_action: 'Implement targeted lead generation campaign', }); } } // General recommendations const recommendations = []; const avgWinRate = areaProfiles.length > 0 ? areaProfiles.reduce((sum, p) => sum + p.win_rate, 0) / areaProfiles.length : 0; recommendations.push(`📊 Average win rate across areas: ${avgWinRate.toFixed(1)}%`); const topArea = areaProfiles[0]; if (topArea) { recommendations.push(`🏆 Most active area: ${topArea.area_name} (${topArea.recent_jobs_count} recent jobs)`); } recommendations.push('🎯 Customize scripts based on property value and demographic type'); recommendations.push('📱 Use tablet/phone to show before/after photos during pitch'); recommendations.push('🤝 Always ask for referrals in high win-rate areas'); return { data_source: 'Live JobNimbus API data', analysis_timestamp: new Date().toISOString(), target_area: targetArea || 'All areas', service_type: serviceType, script_style: scriptStyle, summary: { areas_analyzed: areaProfiles.length, scripts_generated: scriptTemplates.length, avg_win_rate: avgWinRate, }, area_profiles: areaProfiles, customized_scripts: scriptTemplates, area_insights: insights, general_recommendations: recommendations, best_practices: [ 'Research the neighborhood before knocking (recent permits, storm damage, etc.)', 'Knock during optimal times: weekdays 5-8pm, weekends 10am-2pm', 'Lead with value, not features - focus on benefits to homeowner', 'Use local references and completed jobs as proof', 'Practice active listening - let them talk about their needs', 'Always follow up within 24 hours of initial contact', ], }; } catch (error) { return { error: error instanceof Error ? error.message : 'Unknown error', status: 'Failed', }; } } /** * Generate customized script based on area profile */ generateScript(profile, serviceType, style, includeObjections) { const isResidential = profile.demographic_type !== 'Commercial'; // Opening lines by style const openingLines = { conversational: `Hi there! I'm [Name] with [Company]. We've been helping your neighbors with ${serviceType} - mind if I share how we can help you too?`, professional: `Good ${this.getTimeOfDay()}, I'm [Name] from [Company]. We're the ${serviceType} experts in ${profile.area_name}.`, friendly: `Hey! I'm [Name], your local ${serviceType} specialist. We've been working in ${profile.area_name} and I wanted to stop by.`, consultative: `Hello, I'm [Name] with [Company]. We specialize in ${serviceType} solutions for ${isResidential ? 'homeowners' : 'businesses'} in ${profile.area_name}.`, }; // Value propositions const valueProps = [ `We've completed ${profile.recent_jobs_count}+ ${serviceType} projects in ${profile.area_name} this month`, `${profile.win_rate > 60 ? 'Your neighbors' : 'Local residents'} trust us for quality work and fair pricing`, `Free inspection with no obligation - we'll identify any issues before they become expensive`, ]; if (profile.avg_property_value_estimate === 'High') { valueProps.push('Premium materials and workmanship with extended warranty options'); } else { valueProps.push('Flexible financing options to fit any budget'); } // Objection handlers const objectionHandlers = includeObjections ? [ { objection: "I'm not interested", response: "I understand! Can I just leave you my card? If you know anyone who needs ${serviceType}, we offer $100 referral bonuses.", }, { objection: "How much does it cost?", response: "Great question! Every project is unique. Can I do a quick inspection? It's free and takes 10 minutes - then I can give you an exact quote.", }, { objection: "I need to think about it", response: "Absolutely, this is a big decision. What specific concerns do you have? I'm happy to address those right now.", }, { objection: "I already have someone", response: "That's great you're being proactive! Would you like a second opinion? No cost, and you might discover savings or better options.", }, ] : []; // Closing technique const closing = profile.win_rate > 60 ? "I have time this week for a free inspection. Does Thursday or Friday work better for you?" : "Let me leave my information. If you'd like a free consultation, give me a call or text anytime."; // Follow-up strategy const followUp = isResidential ? "Text within 24 hours with photo of completed project in neighborhood + special offer" : "Email detailed service brochure + schedule follow-up call in 3 days"; // Success rate estimate const successRate = profile.win_rate * 0.8; // 80% of historical win rate return { script_name: `${profile.area_name} - ${profile.demographic_type} Script`, target_demographic: `${profile.demographic_type} (${profile.avg_property_value_estimate} value)`, opening_line: openingLines[style] || openingLines.consultative, value_proposition: valueProps, objection_handlers: objectionHandlers, closing_technique: closing, follow_up_strategy: followUp, success_rate_estimate: successRate, }; } /** * Get time of day greeting */ getTimeOfDay() { const hour = new Date().getHours(); if (hour < 12) return 'morning'; if (hour < 17) return 'afternoon'; return 'evening'; } } //# sourceMappingURL=getDoorKnockingScriptsByArea.js.map