jobnimbus-mcp-client
Version:
JobNimbus MCP Client - Connect Claude Desktop to remote JobNimbus MCP server
157 lines • 6.84 kB
JavaScript
/**
* Get Sales Rep Performance - Detailed analytics per sales representative
* Replicates Python implementation from mcp_server_consolidated_final.py
*/
import { BaseTool } from '../baseTool.js';
export class GetSalesRepPerformanceTool extends BaseTool {
get definition() {
return {
name: 'get_sales_rep_performance',
description: 'Detailed performance analytics per sales representative with accurate financial metrics',
inputSchema: {
type: 'object',
properties: {
period: {
type: 'string',
description: 'Analysis period',
default: 'current_month',
},
},
},
};
}
async execute(_input, context) {
try {
// Fetch jobs and estimates from JobNimbus API
const [jobsResponse, estimatesResponse] = await Promise.all([
this.client.get(context.apiKey, 'jobs', { size: 20 }),
this.client.get(context.apiKey, 'estimates', { size: 15 }),
]);
const jobs = jobsResponse.data?.results || [];
const estimates = estimatesResponse.data?.results || [];
// Build lookup maps
const jobLookup = new Map();
for (const job of jobs) {
if (job.jnid) {
jobLookup.set(job.jnid, job);
}
}
// Map estimates to jobs
const estimatesByJob = new Map();
for (const estimate of estimates) {
const related = estimate.related || [];
for (const rel of related) {
if (rel.type === 'job' && rel.id) {
const jobId = rel.id;
if (!estimatesByJob.has(jobId)) {
estimatesByJob.set(jobId, []);
}
estimatesByJob.get(jobId).push(estimate);
}
}
}
// Calculate performance by rep
const repPerformance = new Map();
for (const job of jobs) {
if (!job.jnid)
continue;
const salesRep = job.sales_rep || job.assigned_to || job.created_by || 'Unknown';
const salesRepName = job.sales_rep_name || 'Unknown';
if (!repPerformance.has(salesRep)) {
repPerformance.set(salesRep, {
rep_id: salesRep,
name: salesRepName,
jobs_count: 0,
total_value: 0,
avg_value: 0,
conversion_rate: 0,
won_jobs: 0,
lost_jobs: 0,
pending_jobs: 0,
estimates_sent: 0,
estimates_approved: 0,
});
}
const rep = repPerformance.get(salesRep);
rep.jobs_count += 1;
// Categorize job status
const statusName = (job.status_name || '').toLowerCase();
if (statusName.includes('complete') ||
statusName.includes('won') ||
statusName.includes('sold') ||
statusName.includes('approved')) {
rep.won_jobs += 1;
}
else if (statusName.includes('lost') ||
statusName.includes('cancelled') ||
statusName.includes('declined')) {
rep.lost_jobs += 1;
}
else {
rep.pending_jobs += 1;
}
// Process job estimates
const jobEstimates = estimatesByJob.get(job.jnid) || [];
let jobEstimateValue = 0;
for (const estimate of jobEstimates) {
rep.estimates_sent += 1;
const estimateTotal = parseFloat(estimate.total || 0) || 0;
jobEstimateValue += estimateTotal;
// Check if estimate is approved
const estimateStatus = (estimate.status_name || '').toLowerCase();
if (estimate.date_signed > 0 ||
estimateStatus === 'approved' ||
estimateStatus === 'signed') {
rep.estimates_approved += 1;
}
}
rep.total_value += jobEstimateValue;
}
// Calculate averages and conversion rates
for (const rep of repPerformance.values()) {
if (rep.jobs_count > 0) {
rep.avg_value = rep.total_value / rep.jobs_count;
const totalDecisions = rep.won_jobs + rep.lost_jobs;
if (totalDecisions > 0) {
rep.conversion_rate = rep.won_jobs / totalDecisions;
}
}
}
// Sort by total value
const sortedReps = Array.from(repPerformance.values()).sort((a, b) => b.total_value - a.total_value);
// Calculate team summary
let totalJobs = 0;
let totalValue = 0;
let totalEstimates = 0;
let totalApproved = 0;
for (const rep of repPerformance.values()) {
totalJobs += rep.jobs_count;
totalValue += rep.total_value;
totalEstimates += rep.estimates_sent;
totalApproved += rep.estimates_approved;
}
return {
data_source: 'Live JobNimbus API data with FIXED matching logic',
analysis_timestamp: new Date().toISOString(),
total_sales_reps: repPerformance.size,
team_summary: {
total_jobs: totalJobs,
total_pipeline_value: totalValue,
total_estimates_sent: totalEstimates,
total_estimates_approved: totalApproved,
team_conversion_rate: totalEstimates > 0 ? totalApproved / totalEstimates : 0,
average_deal_size: totalJobs > 0 ? totalValue / totalJobs : 0,
},
performance_by_rep: sortedReps.slice(0, 15),
fix_status: 'APPLIED - Job-estimate matching corrected',
};
}
catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 'Failed',
};
}
}
}
//# sourceMappingURL=getSalesRepPerformance.js.map