introduce investment models

This commit is contained in:
Tobias Brunner 2025-07-21 17:06:29 +02:00
parent 8ab864e444
commit 626badffe9
Signed by: tobru
SSH key fingerprint: SHA256:kOXg1R6c11XW3/Pt9dbLdQvOJGFAy+B2K6v6PtRWBGQ
2 changed files with 181 additions and 27 deletions

View file

@ -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?')) {