jobnimbus-mcp-client
Version:
JobNimbus MCP Client - Connect Claude Desktop to remote JobNimbus MCP server
283 lines • 14.1 kB
JavaScript
/**
* Get Financial Forecasting Analytics
* Comprehensive financial forecasting with revenue predictions, cash flow analysis, and financial health indicators
*/
import { BaseTool } from '../baseTool.js';
export class GetFinancialForecastingAnalyticsTool extends BaseTool {
get definition() {
return {
name: 'get_financial_forecasting_analytics',
description: 'Comprehensive financial forecasting with revenue predictions, cash flow projections, profitability analysis, financial health indicators, scenario planning, and risk assessment',
inputSchema: {
type: 'object',
properties: {
forecast_months: {
type: 'number',
default: 12,
description: 'Months to forecast (default: 12)',
},
include_scenarios: {
type: 'boolean',
default: true,
description: 'Include scenario analysis',
},
include_risk_assessment: {
type: 'boolean',
default: true,
description: 'Include financial risk assessment',
},
},
},
};
}
async execute(input, context) {
try {
const forecastMonths = input.forecast_months || 12;
const includeScenarios = input.include_scenarios !== false;
const includeRiskAssessment = input.include_risk_assessment !== false;
const [jobsResponse] = await Promise.all([
this.client.get(context.apiKey, 'jobs', { size: 100 }),
// this.client.get(context.apiKey, 'estimates', { size: 100 }),
]);
const jobs = jobsResponse.data?.results || [];
// const estimates = estimatesResponse.data?.results || [];
const now = Date.now();
const oneYearAgo = now - (365 * 24 * 60 * 60 * 1000);
// Calculate monthly revenue
const monthlyRevenue = new Map();
let totalRevenueYTD = 0;
for (const job of jobs) {
const status = (job.status_name || '').toLowerCase();
if (!status.includes('complete') && !status.includes('won'))
continue;
const completedDate = job.date_status_change || job.date_updated || 0;
if (completedDate === 0)
continue;
const monthKey = new Date(completedDate).toISOString().slice(0, 7);
const revenue = parseFloat(job.total || job.value || 0);
if (!monthlyRevenue.has(monthKey)) {
monthlyRevenue.set(monthKey, 0);
}
monthlyRevenue.set(monthKey, monthlyRevenue.get(monthKey) + revenue);
if (completedDate >= oneYearAgo) {
totalRevenueYTD += revenue;
}
}
// Sort months
const sortedMonths = Array.from(monthlyRevenue.keys()).sort();
const recentMonths = sortedMonths.slice(-6);
// Current MRR
const currentMRR = recentMonths.length > 0
? monthlyRevenue.get(recentMonths[recentMonths.length - 1]) || 0
: 0;
// Average monthly revenue
const avgMonthlyRevenue = recentMonths.length > 0
? recentMonths.reduce((sum, month) => sum + (monthlyRevenue.get(month) || 0), 0) / recentMonths.length
: 0;
// Growth rate (comparing last 3 months to previous 3 months)
const last3Months = recentMonths.slice(-3);
const prev3Months = recentMonths.slice(-6, -3);
const avgLast3 = last3Months.length > 0
? last3Months.reduce((sum, m) => sum + (monthlyRevenue.get(m) || 0), 0) / last3Months.length
: 0;
const avgPrev3 = prev3Months.length > 0
? prev3Months.reduce((sum, m) => sum + (monthlyRevenue.get(m) || 0), 0) / prev3Months.length
: 0;
const growthRate = avgPrev3 > 0 ? ((avgLast3 - avgPrev3) / avgPrev3) * 100 : 0;
// Estimate expenses (40% of revenue for simplicity)
const totalExpensesYTD = totalRevenueYTD * 0.4;
const netProfitYTD = totalRevenueYTD - totalExpensesYTD;
const profitMargin = totalRevenueYTD > 0 ? (netProfitYTD / totalRevenueYTD) * 100 : 0;
// Cash flow health
const cashFlowHealth = profitMargin >= 30 ? 'Excellent' :
profitMargin >= 20 ? 'Good' :
profitMargin >= 10 ? 'Fair' : 'Critical';
const financialMetrics = {
current_mrr: currentMRR,
projected_next_month_revenue: avgMonthlyRevenue * (1 + growthRate / 100),
yoy_growth_rate: growthRate,
avg_monthly_revenue: avgMonthlyRevenue,
total_revenue_ytd: totalRevenueYTD,
total_expenses_ytd: totalExpensesYTD,
net_profit_ytd: netProfitYTD,
profit_margin: profitMargin,
cash_flow_health: cashFlowHealth,
};
// Revenue forecasts
const revenueForecasts = [];
const monthlyGrowthRate = growthRate / 100 / 12;
// Conservative
revenueForecasts.push({
period: 'Next Quarter (3 months)',
forecast_type: 'Conservative',
projected_revenue: avgMonthlyRevenue * 3 * (1 + monthlyGrowthRate * 0.5),
confidence_level: 80,
based_on: 'Historical avg with 50% growth rate',
growth_rate: growthRate * 0.5,
});
// Likely
revenueForecasts.push({
period: 'Next Quarter (3 months)',
forecast_type: 'Likely',
projected_revenue: avgMonthlyRevenue * 3 * (1 + monthlyGrowthRate),
confidence_level: 65,
based_on: 'Historical avg with current growth rate',
growth_rate: growthRate,
});
// Optimistic
revenueForecasts.push({
period: 'Next Quarter (3 months)',
forecast_type: 'Optimistic',
projected_revenue: avgMonthlyRevenue * 3 * (1 + monthlyGrowthRate * 1.5),
confidence_level: 50,
based_on: 'Historical avg with 150% growth rate',
growth_rate: growthRate * 1.5,
});
// Cash flow projections
const cashFlowProjections = [];
let cumulativeCash = netProfitYTD;
for (let i = 0; i < forecastMonths; i++) {
const monthDate = new Date(now);
monthDate.setMonth(monthDate.getMonth() + i + 1);
const monthKey = monthDate.toISOString().slice(0, 7);
const projectedInflow = avgMonthlyRevenue * (1 + (monthlyGrowthRate * i));
const projectedOutflow = projectedInflow * 0.4; // 40% expenses
const netCashFlow = projectedInflow - projectedOutflow;
cumulativeCash += netCashFlow;
const burnRate = projectedOutflow;
const runwayMonths = burnRate > 0 ? cumulativeCash / burnRate : 999;
cashFlowProjections.push({
month: monthKey,
projected_inflow: projectedInflow,
projected_outflow: projectedOutflow,
net_cash_flow: netCashFlow,
cumulative_cash: cumulativeCash,
burn_rate: burnRate,
runway_months: Math.min(runwayMonths, 999),
});
}
// Financial health indicators
const financialHealthIndicators = [
{
indicator_name: 'Profit Margin',
current_value: profitMargin,
target_value: 25,
status: profitMargin >= 25 ? 'Healthy' : profitMargin >= 15 ? 'Warning' : 'Critical',
trend: growthRate > 0 ? 'Improving' : growthRate < -5 ? 'Declining' : 'Stable',
recommendation: profitMargin < 25 ? 'Optimize costs or increase pricing' : 'Maintain current trajectory',
},
{
indicator_name: 'Revenue Growth Rate',
current_value: growthRate,
target_value: 20,
status: growthRate >= 20 ? 'Healthy' : growthRate >= 10 ? 'Warning' : 'Critical',
trend: growthRate > 0 ? 'Improving' : 'Declining',
recommendation: growthRate < 20 ? 'Invest in sales and marketing' : 'Continue scaling',
},
{
indicator_name: 'Cash Runway',
current_value: cashFlowProjections[0]?.runway_months || 0,
target_value: 12,
status: cashFlowProjections[0]?.runway_months >= 12 ? 'Healthy' :
cashFlowProjections[0]?.runway_months >= 6 ? 'Warning' : 'Critical',
trend: 'Stable',
recommendation: cashFlowProjections[0]?.runway_months < 12 ? 'Raise capital or reduce burn' : 'Healthy cash position',
},
];
// Profitability trends
const profitabilityTrends = [];
for (const month of recentMonths.slice(-6)) {
const revenue = monthlyRevenue.get(month) || 0;
const costs = revenue * 0.4;
const grossProfit = revenue - costs;
const netProfit = grossProfit;
const margin = revenue > 0 ? (netProfit / revenue) * 100 : 0;
profitabilityTrends.push({
month: month,
revenue: revenue,
costs: costs,
gross_profit: grossProfit,
net_profit: netProfit,
profit_margin: margin,
trend_direction: 'Stable',
});
}
// Scenario analysis
const scenarioAnalyses = [];
if (includeScenarios) {
scenarioAnalyses.push({
scenario: 'Best Case',
probability: 20,
projected_revenue_next_quarter: avgMonthlyRevenue * 3 * 1.5,
projected_profit: avgMonthlyRevenue * 3 * 1.5 * 0.6,
key_assumptions: ['All deals close', 'High market demand', 'No competition'],
risk_factors: ['Overexpansion', 'Quality issues'],
});
scenarioAnalyses.push({
scenario: 'Base Case',
probability: 60,
projected_revenue_next_quarter: avgMonthlyRevenue * 3,
projected_profit: avgMonthlyRevenue * 3 * 0.6,
key_assumptions: ['Normal win rate', 'Stable market'],
risk_factors: ['Competition', 'Economic headwinds'],
});
scenarioAnalyses.push({
scenario: 'Worst Case',
probability: 20,
projected_revenue_next_quarter: avgMonthlyRevenue * 3 * 0.7,
projected_profit: avgMonthlyRevenue * 3 * 0.7 * 0.6,
key_assumptions: ['Low conversion', 'Market downturn'],
risk_factors: ['Loss of key clients', 'Recession'],
});
}
// Financial risks
const financialRisks = [];
if (includeRiskAssessment) {
if (profitMargin < 20) {
financialRisks.push({
risk_type: 'Low Profit Margin',
severity: 'High',
impact_amount: totalRevenueYTD * 0.1,
likelihood: 80,
mitigation_strategy: 'Review pricing and reduce costs',
priority: 1,
});
}
if (growthRate < 10) {
financialRisks.push({
risk_type: 'Slow Growth',
severity: 'Medium',
impact_amount: avgMonthlyRevenue * 12 * 0.2,
likelihood: 60,
mitigation_strategy: 'Increase sales and marketing investment',
priority: 2,
});
}
}
return {
data_source: 'Live JobNimbus API data',
analysis_timestamp: new Date().toISOString(),
financial_metrics: financialMetrics,
revenue_forecasts: revenueForecasts,
cash_flow_projections: cashFlowProjections.slice(0, 12),
financial_health_indicators: financialHealthIndicators,
profitability_trends: profitabilityTrends,
scenario_analyses: includeScenarios ? scenarioAnalyses : undefined,
financial_risks: includeRiskAssessment ? financialRisks : undefined,
key_insights: [
`Current MRR: $${currentMRR.toLocaleString()}`,
`Growth rate: ${growthRate.toFixed(1)}%`,
`Profit margin: ${profitMargin.toFixed(1)}%`,
`Cash runway: ${cashFlowProjections[0]?.runway_months.toFixed(0)} months`,
],
};
}
catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
status: 'Failed',
};
}
}
}
//# sourceMappingURL=getFinancialForecastingAnalytics.js.map