introduce investment models
This commit is contained in:
parent
8ab864e444
commit
626badffe9
2 changed files with 181 additions and 27 deletions
|
|
@ -48,9 +48,12 @@ class ROICalculator {
|
|||
}
|
||||
|
||||
getInputValues() {
|
||||
const investmentModel = document.querySelector('input[name="investment-model"]:checked').value;
|
||||
return {
|
||||
investmentAmount: parseFloat(document.getElementById('investment-amount').getAttribute('data-value')),
|
||||
timeframe: parseInt(document.getElementById('timeframe').value),
|
||||
investmentModel: investmentModel,
|
||||
loanInterestRate: parseFloat(document.getElementById('loan-interest-rate').value) / 100,
|
||||
revenuePerInstance: parseFloat(document.getElementById('revenue-per-instance').value),
|
||||
servalaShare: parseFloat(document.getElementById('servala-share').value) / 100,
|
||||
gracePeriod: parseInt(document.getElementById('grace-period').value)
|
||||
|
|
@ -61,15 +64,30 @@ class ROICalculator {
|
|||
const scenario = this.scenarios[scenarioKey];
|
||||
if (!scenario.enabled) return null;
|
||||
|
||||
// Calculate investment scaling factor
|
||||
// Calculate loan payment if using loan model
|
||||
let monthlyLoanPayment = 0;
|
||||
if (inputs.investmentModel === 'loan') {
|
||||
const monthlyRate = inputs.loanInterestRate / 12;
|
||||
const numPayments = inputs.timeframe * 12;
|
||||
// Calculate fixed monthly payment using amortization formula
|
||||
if (monthlyRate > 0) {
|
||||
monthlyLoanPayment = inputs.investmentAmount *
|
||||
(monthlyRate * Math.pow(1 + monthlyRate, numPayments)) /
|
||||
(Math.pow(1 + monthlyRate, numPayments) - 1);
|
||||
} else {
|
||||
monthlyLoanPayment = inputs.investmentAmount / numPayments;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate investment scaling factor (only for direct investment)
|
||||
// Base investment of CHF 500,000 = 1.0x multiplier
|
||||
// Higher investments get multiplicative benefits for instance acquisition
|
||||
const baseInvestment = 500000;
|
||||
const investmentScaleFactor = Math.sqrt(inputs.investmentAmount / baseInvestment);
|
||||
const investmentScaleFactor = inputs.investmentModel === 'loan' ? 1.0 : Math.sqrt(inputs.investmentAmount / baseInvestment);
|
||||
|
||||
// Calculate churn reduction factor based on investment
|
||||
// Calculate churn reduction factor based on investment (only for direct investment)
|
||||
// Higher investment = better customer success = lower churn
|
||||
const churnReductionFactor = Math.max(0.7, 1 - (inputs.investmentAmount - baseInvestment) / 2000000 * 0.3);
|
||||
const churnReductionFactor = inputs.investmentModel === 'loan' ? 1.0 : Math.max(0.7, 1 - (inputs.investmentAmount - baseInvestment) / 2000000 * 0.3);
|
||||
|
||||
// Calculate adjusted churn rate with investment-based reduction
|
||||
const adjustedChurnRate = scenario.churnRate * churnReductionFactor;
|
||||
|
|
@ -102,17 +120,26 @@ class ROICalculator {
|
|||
// Update total instances
|
||||
currentInstances = currentInstances + newInstances - churnedInstances;
|
||||
|
||||
// Calculate revenue
|
||||
const monthlyRevenue = currentInstances * inputs.revenuePerInstance;
|
||||
|
||||
// Determine revenue split based on grace period
|
||||
let cspRevenue, servalaRevenue;
|
||||
if (month <= inputs.gracePeriod) {
|
||||
cspRevenue = monthlyRevenue;
|
||||
// Calculate revenue based on investment model
|
||||
let cspRevenue, servalaRevenue, monthlyRevenue;
|
||||
|
||||
if (inputs.investmentModel === 'loan') {
|
||||
// Loan model: CSP receives fixed monthly loan payment
|
||||
cspRevenue = monthlyLoanPayment;
|
||||
servalaRevenue = 0;
|
||||
monthlyRevenue = monthlyLoanPayment;
|
||||
} else {
|
||||
cspRevenue = monthlyRevenue * (1 - inputs.servalaShare);
|
||||
servalaRevenue = monthlyRevenue * inputs.servalaShare;
|
||||
// Direct investment model: Revenue based on instances
|
||||
monthlyRevenue = currentInstances * inputs.revenuePerInstance;
|
||||
|
||||
// Determine revenue split based on grace period
|
||||
if (month <= inputs.gracePeriod) {
|
||||
cspRevenue = monthlyRevenue;
|
||||
servalaRevenue = 0;
|
||||
} else {
|
||||
cspRevenue = monthlyRevenue * (1 - inputs.servalaShare);
|
||||
servalaRevenue = monthlyRevenue * inputs.servalaShare;
|
||||
}
|
||||
}
|
||||
|
||||
// Update cumulative revenue
|
||||
|
|
@ -148,6 +175,7 @@ class ROICalculator {
|
|||
|
||||
return {
|
||||
scenario: scenario.name,
|
||||
investmentModel: inputs.investmentModel,
|
||||
finalInstances: currentInstances,
|
||||
totalRevenue,
|
||||
cspRevenue: cumulativeCSPRevenue,
|
||||
|
|
@ -341,9 +369,14 @@ class ROICalculator {
|
|||
tbody.innerHTML = '';
|
||||
|
||||
Object.values(this.results).forEach(result => {
|
||||
const modelLabel = result.investmentModel === 'loan' ?
|
||||
'<span class="badge bg-warning">Loan</span>' :
|
||||
'<span class="badge bg-success">Direct</span>';
|
||||
|
||||
const row = tbody.insertRow();
|
||||
row.innerHTML = `
|
||||
<td><strong>${result.scenario}</strong></td>
|
||||
<td>${modelLabel}</td>
|
||||
<td>${result.finalInstances.toLocaleString()}</td>
|
||||
<td>${this.formatCurrencyDetailed(result.totalRevenue)}</td>
|
||||
<td>${this.formatCurrencyDetailed(result.cspRevenue)}</td>
|
||||
|
|
@ -690,9 +723,13 @@ function exportToPDF() {
|
|||
const params = [
|
||||
['Investment Amount:', calculator.formatCurrencyDetailed(inputs.investmentAmount)],
|
||||
['Investment Timeframe:', `${inputs.timeframe} years`],
|
||||
['Investment Model:', inputs.investmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'],
|
||||
...(inputs.investmentModel === 'loan' ? [['Loan Interest Rate:', `${(inputs.loanInterestRate * 100).toFixed(1)}%`]] : []),
|
||||
['Revenue per Instance:', calculator.formatCurrencyDetailed(inputs.revenuePerInstance)],
|
||||
['Servala Revenue Share:', `${(inputs.servalaShare * 100).toFixed(0)}%`],
|
||||
['Grace Period:', `${inputs.gracePeriod} months`]
|
||||
...(inputs.investmentModel === 'direct' ? [
|
||||
['Servala Revenue Share:', `${(inputs.servalaShare * 100).toFixed(0)}%`],
|
||||
['Grace Period:', `${inputs.gracePeriod} months`]
|
||||
] : [])
|
||||
];
|
||||
|
||||
params.forEach(([label, value]) => {
|
||||
|
|
@ -808,16 +845,24 @@ function exportToCSV() {
|
|||
const inputs = calculator.getInputValues();
|
||||
csvContent += `Investment Amount,${inputs.investmentAmount}\n`;
|
||||
csvContent += `Timeframe (years),${inputs.timeframe}\n`;
|
||||
csvContent += `Investment Model,${inputs.investmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'}\n`;
|
||||
if (inputs.investmentModel === 'loan') {
|
||||
csvContent += `Loan Interest Rate (%),${(inputs.loanInterestRate * 100).toFixed(1)}\n`;
|
||||
}
|
||||
csvContent += `Revenue per Instance,${inputs.revenuePerInstance}\n`;
|
||||
csvContent += `Servala Share (%),${(inputs.servalaShare * 100).toFixed(0)}\n`;
|
||||
csvContent += `Grace Period (months),${inputs.gracePeriod}\n\n`;
|
||||
if (inputs.investmentModel === 'direct') {
|
||||
csvContent += `Servala Share (%),${(inputs.servalaShare * 100).toFixed(0)}\n`;
|
||||
csvContent += `Grace Period (months),${inputs.gracePeriod}\n`;
|
||||
}
|
||||
csvContent += '\n';
|
||||
|
||||
// Add scenario summary
|
||||
csvContent += 'SCENARIO SUMMARY\n';
|
||||
csvContent += 'Scenario,Final Instances,Total Revenue,CSP Revenue,Servala Revenue,ROI (%),Break-even (months)\n';
|
||||
csvContent += 'Scenario,Investment Model,Final Instances,Total Revenue,CSP Revenue,Servala Revenue,ROI (%),Break-even (months)\n';
|
||||
|
||||
Object.values(calculator.results).forEach(result => {
|
||||
csvContent += `${result.scenario},${result.finalInstances},${result.totalRevenue.toFixed(2)},${result.cspRevenue.toFixed(2)},${result.servalaRevenue.toFixed(2)},${result.roi.toFixed(2)},${result.breakEvenMonth || 'N/A'}\n`;
|
||||
const modelText = result.investmentModel === 'loan' ? 'Loan' : 'Direct';
|
||||
csvContent += `${result.scenario},${modelText},${result.finalInstances},${result.totalRevenue.toFixed(2)},${result.cspRevenue.toFixed(2)},${result.servalaRevenue.toFixed(2)},${result.roi.toFixed(2)},${result.breakEvenMonth || 'N/A'}\n`;
|
||||
});
|
||||
|
||||
csvContent += '\n';
|
||||
|
|
@ -872,6 +917,9 @@ function resetCalculator() {
|
|||
investmentInput.value = '500,000';
|
||||
document.getElementById('investment-slider').value = 500000;
|
||||
document.getElementById('timeframe').value = 3;
|
||||
document.getElementById('direct-model').checked = true;
|
||||
document.getElementById('loan-interest-rate').value = 5.0;
|
||||
document.getElementById('loan-rate-slider').value = 5.0;
|
||||
document.getElementById('revenue-per-instance').value = 50;
|
||||
document.getElementById('revenue-slider').value = 50;
|
||||
document.getElementById('servala-share').value = 25;
|
||||
|
|
@ -890,11 +938,36 @@ function resetCalculator() {
|
|||
// Reset advanced parameters
|
||||
resetAdvancedParameters();
|
||||
|
||||
// Reset investment model toggle
|
||||
toggleInvestmentModel();
|
||||
|
||||
// Recalculate (this will be called by resetAdvancedParameters, but we ensure it happens)
|
||||
updateCalculations();
|
||||
}
|
||||
}
|
||||
|
||||
// Investment model toggle functions
|
||||
function toggleInvestmentModel() {
|
||||
const selectedModel = document.querySelector('input[name="investment-model"]:checked').value;
|
||||
const loanSection = document.getElementById('loan-rate-section');
|
||||
const modelDescription = document.getElementById('model-description');
|
||||
|
||||
if (selectedModel === 'loan') {
|
||||
loanSection.style.display = 'block';
|
||||
modelDescription.textContent = 'Loan Model: Guaranteed returns with fixed monthly payments';
|
||||
} else {
|
||||
loanSection.style.display = 'none';
|
||||
modelDescription.textContent = 'Direct Investment: Higher potential returns based on your sales performance';
|
||||
}
|
||||
|
||||
updateCalculations();
|
||||
}
|
||||
|
||||
function updateLoanRate(value) {
|
||||
document.getElementById('loan-interest-rate').value = value;
|
||||
updateCalculations();
|
||||
}
|
||||
|
||||
// Logout function
|
||||
function logout() {
|
||||
if (confirm('Are you sure you want to logout?')) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue