rework investment model

This commit is contained in:
Tobias Brunner 2025-07-22 09:16:40 +02:00
parent 22bea2c53d
commit 6f6c80480f
Signed by: tobru
SSH key fingerprint: SHA256:kOXg1R6c11XW3/Pt9dbLdQvOJGFAy+B2K6v6PtRWBGQ
6 changed files with 633 additions and 141 deletions

View file

@ -122,15 +122,32 @@ class ROICalculator {
}
}
// 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
// Enhanced investment scaling factor (only for direct investment)
// Progressive scaling provides clear incentives for larger investments
const baseInvestment = 500000;
const investmentScaleFactor = inputs.investmentModel === 'loan' ? 1.0 : Math.sqrt(inputs.investmentAmount / baseInvestment);
let investmentScaleFactor;
let churnReductionFactor;
// Calculate churn reduction factor based on investment (only for direct investment)
// Higher investment = better customer success = lower churn
const churnReductionFactor = inputs.investmentModel === 'loan' ? 1.0 : Math.max(0.7, 1 - (inputs.investmentAmount - baseInvestment) / 2000000 * 0.3);
if (inputs.investmentModel === 'loan') {
investmentScaleFactor = 1.0;
churnReductionFactor = 1.0;
} else {
// Progressive linear scaling with clear breakpoints
if (inputs.investmentAmount <= baseInvestment) {
investmentScaleFactor = inputs.investmentAmount / baseInvestment;
} else if (inputs.investmentAmount <= 1000000) {
// 500k to 1M: Scale from 1.0x to 1.5x
investmentScaleFactor = 1.0 + (inputs.investmentAmount - baseInvestment) / 1000000;
} else {
// 1M to 2M: Scale from 1.5x to 2.0x
investmentScaleFactor = 1.5 + (inputs.investmentAmount - 1000000) / 2000000;
}
// Enhanced churn reduction with better scaling
// Higher investment = better customer success = lower churn
const churnReductionRatio = Math.min((inputs.investmentAmount - baseInvestment) / 1500000, 1.0);
churnReductionFactor = Math.max(0.6, 1 - (churnReductionRatio * 0.4));
}
// Calculate adjusted churn rate with investment-based reduction
const adjustedChurnRate = scenario.churnRate * churnReductionFactor;
@ -142,6 +159,15 @@ class ROICalculator {
let cumulativeServalaRevenue = 0;
let breakEvenMonth = null;
// Calculate dynamic grace period based on investment size
const baseGracePeriod = inputs.gracePeriod;
const gracePeriodBonus = Math.floor((inputs.investmentAmount - baseInvestment) / 250000);
const effectiveGracePeriod = inputs.investmentModel === 'loan' ? 0 :
Math.min(baseGracePeriod + gracePeriodBonus, Math.floor(totalMonths / 2));
// Track baseline performance for performance bonuses (direct investment only)
let baselineInstances = 0; // Will track expected instances without performance scaling
// Track phase progression
let currentPhase = 0;
let monthsInCurrentPhase = 0;
@ -157,6 +183,11 @@ class ROICalculator {
const baseNewInstances = scenario.phases[currentPhase].newInstancesPerMonth;
const newInstances = Math.floor(baseNewInstances * investmentScaleFactor);
// Track baseline instances (without investment scaling) for performance comparison
const baselineNewInstances = baseNewInstances;
const baselineChurnedInstances = Math.floor(baselineInstances * scenario.churnRate);
baselineInstances = baselineInstances + baselineNewInstances - baselineChurnedInstances;
// Calculate churn using the pre-calculated adjusted churn rate
const churnedInstances = Math.floor(currentInstances * adjustedChurnRate);
@ -164,24 +195,33 @@ class ROICalculator {
currentInstances = currentInstances + newInstances - churnedInstances;
// Calculate revenue based on investment model
let cspRevenue, servalaRevenue, monthlyRevenue;
let cspRevenue, servalaRevenue, monthlyRevenue, performanceBonus = 0, adjustedServalaShare = inputs.servalaShare;
if (inputs.investmentModel === 'loan') {
// Loan model: CSP receives fixed monthly loan payment
// Loan model: CSP receives fixed monthly loan payment (predictable returns)
cspRevenue = monthlyLoanPayment;
servalaRevenue = 0;
monthlyRevenue = monthlyLoanPayment;
} else {
// Direct investment model: Revenue based on instances
// Direct investment model: Revenue based on instances with performance incentives
monthlyRevenue = currentInstances * inputs.revenuePerInstance;
// Determine revenue split based on grace period
if (month <= inputs.gracePeriod) {
// Calculate performance bonus if CSP exceeds baseline expectations
if (baselineInstances > 0 && month > 6) { // Start performance tracking after 6 months
const performanceRatio = currentInstances / Math.max(baselineInstances, 1);
if (performanceRatio > 1.1) { // 10% threshold for performance bonus
performanceBonus = Math.max(0, Math.min(0.15, (performanceRatio - 1.1) * 0.3)); // Up to 15% bonus
adjustedServalaShare = Math.max(0.10, inputs.servalaShare - performanceBonus);
}
}
// Determine revenue split based on dynamic grace period
if (month <= effectiveGracePeriod) {
cspRevenue = monthlyRevenue;
servalaRevenue = 0;
} else {
cspRevenue = monthlyRevenue * (1 - inputs.servalaShare);
servalaRevenue = monthlyRevenue * inputs.servalaShare;
cspRevenue = monthlyRevenue * (1 - adjustedServalaShare);
servalaRevenue = monthlyRevenue * adjustedServalaShare;
}
}
@ -189,8 +229,18 @@ class ROICalculator {
cumulativeCSPRevenue += cspRevenue;
cumulativeServalaRevenue += servalaRevenue;
// Check for break-even
if (breakEvenMonth === null && cumulativeCSPRevenue >= inputs.investmentAmount) {
// Enhanced break-even calculation
let netPosition; // CSP's net financial position
if (inputs.investmentModel === 'loan') {
// For loan model: net position is cumulative payments received minus loan principal outstanding
const principalPaid = inputs.investmentAmount * (month / totalMonths); // Simplified principal tracking
netPosition = cumulativeCSPRevenue - (inputs.investmentAmount - principalPaid);
} else {
// For direct investment: net position is cumulative revenue minus initial investment
netPosition = cumulativeCSPRevenue - inputs.investmentAmount;
}
if (breakEvenMonth === null && netPosition >= 0) {
breakEvenMonth = month;
}
@ -200,34 +250,55 @@ class ROICalculator {
newInstances,
churnedInstances,
totalInstances: currentInstances,
baselineInstances,
monthlyRevenue,
cspRevenue,
servalaRevenue,
cumulativeCSPRevenue,
cumulativeServalaRevenue,
netPosition,
performanceBonus,
adjustedServalaShare,
effectiveGracePeriod,
investmentScaleFactor: investmentScaleFactor,
adjustedChurnRate: adjustedChurnRate
adjustedChurnRate: adjustedChurnRate,
roiPercent: inputs.investmentModel === 'loan' ?
((cumulativeCSPRevenue - inputs.investmentAmount) / inputs.investmentAmount * 100) :
(netPosition / inputs.investmentAmount * 100)
});
monthsInCurrentPhase++;
}
// Calculate final metrics
// Calculate final metrics with enhanced business intelligence
const totalRevenue = cumulativeCSPRevenue + cumulativeServalaRevenue;
const roi = ((cumulativeCSPRevenue - inputs.investmentAmount) / inputs.investmentAmount) * 100;
const finalNetPosition = inputs.investmentModel === 'loan' ?
cumulativeCSPRevenue - inputs.investmentAmount :
cumulativeCSPRevenue - inputs.investmentAmount;
const roi = (finalNetPosition / inputs.investmentAmount) * 100;
// Calculate average performance bonus over the investment period
const performanceBonusMonths = monthlyData.filter(m => m.performanceBonus > 0);
const avgPerformanceBonus = performanceBonusMonths.length > 0 ?
performanceBonusMonths.reduce((sum, m) => sum + m.performanceBonus, 0) / performanceBonusMonths.length : 0;
return {
scenario: scenario.name,
investmentModel: inputs.investmentModel,
finalInstances: currentInstances,
baselineFinalInstances: baselineInstances,
totalRevenue,
cspRevenue: cumulativeCSPRevenue,
servalaRevenue: cumulativeServalaRevenue,
netPosition: finalNetPosition,
roi,
breakEvenMonth,
effectiveGracePeriod,
avgPerformanceBonus,
monthlyData,
investmentScaleFactor: investmentScaleFactor,
adjustedChurnRate: adjustedChurnRate * 100
adjustedChurnRate: adjustedChurnRate * 100,
performanceMultiplier: baselineInstances > 0 ? (currentInstances / baselineInstances) : 1.0
};
} catch (error) {
console.error(`Error calculating scenario ${scenarioKey}:`, error);

View file

@ -17,26 +17,31 @@ class ChartManager {
}
try {
// Instance Growth Chart
const instanceCanvas = document.getElementById('instanceGrowthChart');
if (!instanceCanvas) {
console.error('Instance growth chart canvas not found');
// ROI Progression Chart (replaces Instance Growth Chart)
const roiCanvas = document.getElementById('instanceGrowthChart');
if (!roiCanvas) {
console.error('ROI progression chart canvas not found');
return;
}
const instanceCtx = instanceCanvas.getContext('2d');
this.charts.instanceGrowth = new Chart(instanceCtx, {
const roiCtx = roiCanvas.getContext('2d');
this.charts.roiProgression = new Chart(roiCtx, {
type: 'line',
data: { labels: [], datasets: [] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' }
legend: { position: 'top' },
title: { display: true, text: 'ROI Progression Over Time' }
},
scales: {
y: {
beginAtZero: true,
title: { display: true, text: 'Total Instances' }
title: { display: true, text: 'ROI (%)' },
grid: {
color: function(context) {
return context.tick.value === 0 ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.1)';
}
}
},
x: {
title: { display: true, text: 'Month' }
@ -45,26 +50,31 @@ class ChartManager {
}
});
// Revenue Chart
const revenueCanvas = document.getElementById('revenueChart');
if (!revenueCanvas) {
console.error('Revenue chart canvas not found');
// Net Position Chart (replaces simple Revenue Chart)
const netPositionCanvas = document.getElementById('revenueChart');
if (!netPositionCanvas) {
console.error('Net position chart canvas not found');
return;
}
const revenueCtx = revenueCanvas.getContext('2d');
this.charts.revenue = new Chart(revenueCtx, {
const netPositionCtx = netPositionCanvas.getContext('2d');
this.charts.netPosition = new Chart(netPositionCtx, {
type: 'line',
data: { labels: [], datasets: [] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' }
legend: { position: 'top' },
title: { display: true, text: 'Net Financial Position (Break-Even Analysis)' }
},
scales: {
y: {
beginAtZero: true,
title: { display: true, text: 'Cumulative Revenue (CHF)' }
title: { display: true, text: 'Net Position (CHF)' },
grid: {
color: function(context) {
return context.tick.value === 0 ? 'rgba(0,0,0,0.8)' : 'rgba(0,0,0,0.1)';
}
}
},
x: {
title: { display: true, text: 'Month' }
@ -73,28 +83,30 @@ class ChartManager {
}
});
// Cash Flow Chart
const cashFlowCanvas = document.getElementById('cashFlowChart');
if (!cashFlowCanvas) {
console.error('Cash flow chart canvas not found');
// Model Comparison Chart (replaces generic Cash Flow Chart)
const modelComparisonCanvas = document.getElementById('cashFlowChart');
if (!modelComparisonCanvas) {
console.error('Model comparison chart canvas not found');
return;
}
const cashFlowCtx = cashFlowCanvas.getContext('2d');
this.charts.cashFlow = new Chart(cashFlowCtx, {
const modelComparisonCtx = modelComparisonCanvas.getContext('2d');
this.charts.modelComparison = new Chart(modelComparisonCtx, {
type: 'bar',
data: { labels: [], datasets: [] },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'top' }
legend: { position: 'top' },
title: { display: true, text: 'Investment Model Performance Comparison' }
},
scales: {
y: {
title: { display: true, text: 'Monthly Cash Flow (CHF)' }
beginAtZero: true,
title: { display: true, text: 'Total Return (CHF)' }
},
x: {
title: { display: true, text: 'Month' }
title: { display: true, text: 'Growth Scenario' }
}
}
}
@ -126,11 +138,11 @@ class ChartManager {
updateCharts() {
try {
const scenarios = Object.keys(this.calculator.results);
if (scenarios.length === 0 || !this.charts.instanceGrowth) return;
if (scenarios.length === 0 || !this.charts.roiProgression) return;
const colors = {
conservative: '#28a745',
moderate: '#ffc107',
moderate: '#ffc107',
aggressive: '#dc3545'
};
@ -138,43 +150,65 @@ class ChartManager {
const maxMonths = Math.max(...scenarios.map(s => this.calculator.monthlyData[s].length));
const monthLabels = Array.from({ length: maxMonths }, (_, i) => `M${i + 1}`);
// Update Instance Growth Chart
this.charts.instanceGrowth.data.labels = monthLabels;
this.charts.instanceGrowth.data.datasets = scenarios.map(scenario => ({
label: this.calculator.scenarios[scenario].name,
data: this.calculator.monthlyData[scenario].map(d => d.totalInstances),
// Update ROI Progression Chart
this.charts.roiProgression.data.labels = monthLabels;
this.charts.roiProgression.data.datasets = scenarios.map(scenario => ({
label: `${this.calculator.scenarios[scenario].name} (${this.calculator.results[scenario].investmentModel})`,
data: this.calculator.monthlyData[scenario].map(d => d.roiPercent),
borderColor: colors[scenario],
backgroundColor: colors[scenario] + '20',
tension: 0.4
tension: 0.4,
pointBackgroundColor: this.calculator.monthlyData[scenario].map(d =>
d.roiPercent >= 0 ? colors[scenario] : '#dc3545'
)
}));
this.charts.instanceGrowth.update();
this.charts.roiProgression.update();
// Update Revenue Chart
this.charts.revenue.data.labels = monthLabels;
this.charts.revenue.data.datasets = scenarios.map(scenario => ({
label: this.calculator.scenarios[scenario].name + ' (CSP)',
data: this.calculator.monthlyData[scenario].map(d => d.cumulativeCSPRevenue),
// Update Net Position Chart (Break-Even Analysis)
this.charts.netPosition.data.labels = monthLabels;
this.charts.netPosition.data.datasets = scenarios.map(scenario => ({
label: `${this.calculator.scenarios[scenario].name} Net Position`,
data: this.calculator.monthlyData[scenario].map(d => d.netPosition),
borderColor: colors[scenario],
backgroundColor: colors[scenario] + '20',
tension: 0.4
tension: 0.4,
fill: {
target: 'origin',
above: colors[scenario] + '10',
below: '#dc354510'
}
}));
this.charts.revenue.update();
this.charts.netPosition.update();
// Update Cash Flow Chart (show average across scenarios)
const avgCashFlow = monthLabels.map((_, monthIndex) => {
const monthData = scenarios.map(scenario =>
this.calculator.monthlyData[scenario][monthIndex]?.cspRevenue || 0
);
return monthData.reduce((sum, val) => sum + val, 0) / monthData.length;
});
this.charts.cashFlow.data.labels = monthLabels;
this.charts.cashFlow.data.datasets = [{
label: 'Average Monthly CSP Revenue',
data: avgCashFlow,
backgroundColor: '#007bff'
// Update Model Comparison Chart
const scenarioLabels = scenarios.map(s => this.calculator.scenarios[s].name);
const currentInvestmentModel = Object.values(this.calculator.results)[0]?.investmentModel || 'direct';
// Show comparison with both models for the same scenarios
this.charts.modelComparison.data.labels = scenarioLabels;
this.charts.modelComparison.data.datasets = [{
label: `${currentInvestmentModel === 'loan' ? 'Loan Model' : 'Direct Investment'} - Final Return`,
data: scenarios.map(scenario => this.calculator.results[scenario].cspRevenue),
backgroundColor: scenarios.map(scenario => colors[scenario] + '80'),
borderColor: scenarios.map(scenario => colors[scenario]),
borderWidth: 2
}];
this.charts.cashFlow.update();
// Add performance metrics for direct investment
if (currentInvestmentModel === 'direct') {
this.charts.modelComparison.data.datasets.push({
label: 'Performance Bonus Impact',
data: scenarios.map(scenario =>
this.calculator.results[scenario].avgPerformanceBonus *
this.calculator.results[scenario].cspRevenue
),
backgroundColor: '#17a2b8',
borderColor: '#17a2b8',
borderWidth: 2
});
}
this.charts.modelComparison.update();
} catch (error) {
console.error('Error updating charts:', error);
}

View file

@ -415,10 +415,10 @@ class ROICalculatorApp {
if (selectedModel === 'loan') {
if (loanSection) loanSection.style.display = 'block';
if (modelDescription) modelDescription.textContent = 'Loan Model: Guaranteed returns with fixed monthly payments';
if (modelDescription) modelDescription.textContent = 'Loan Model: 3-7% guaranteed annual returns with predictable monthly payments and low risk';
} else {
if (loanSection) loanSection.style.display = 'none';
if (modelDescription) modelDescription.textContent = 'Direct Investment: Higher potential returns based on your sales performance';
if (modelDescription) modelDescription.textContent = 'Direct Investment: Performance-based returns with progressive scaling, bonuses up to 15%, and dynamic grace periods';
}
this.updateCalculations();

View file

@ -11,23 +11,49 @@ class UIManager {
try {
const enabledResults = Object.values(this.calculator.results);
if (enabledResults.length === 0) {
this.setElementText('total-instances', '0');
this.setElementText('total-revenue', 'CHF 0');
this.setElementText('net-position', 'CHF 0');
this.setElementText('csp-revenue', 'CHF 0');
this.setElementText('roi-percentage', '0%');
this.setElementText('breakeven-time', 'N/A');
return;
}
// Calculate averages across enabled scenarios
const avgInstances = Math.round(enabledResults.reduce((sum, r) => sum + r.finalInstances, 0) / enabledResults.length);
const avgRevenue = enabledResults.reduce((sum, r) => sum + r.totalRevenue, 0) / enabledResults.length;
// Calculate averages across enabled scenarios with enhanced financial metrics
const avgNetPosition = enabledResults.reduce((sum, r) => sum + (r.netPosition || 0), 0) / enabledResults.length;
const avgCSPRevenue = enabledResults.reduce((sum, r) => sum + r.cspRevenue, 0) / enabledResults.length;
const avgROI = enabledResults.reduce((sum, r) => sum + r.roi, 0) / enabledResults.length;
const avgBreakeven = enabledResults.filter(r => r.breakEvenMonth).reduce((sum, r) => sum + r.breakEvenMonth, 0) / enabledResults.filter(r => r.breakEvenMonth).length;
this.setElementText('total-instances', avgInstances.toLocaleString());
this.setElementText('total-revenue', this.formatCurrency(avgRevenue));
// Update metrics with financial focus
this.setElementText('net-position', this.formatCurrency(avgNetPosition));
this.setElementText('csp-revenue', this.formatCurrency(avgCSPRevenue));
this.setElementText('roi-percentage', this.formatPercentage(avgROI));
this.setElementText('breakeven-time', isNaN(avgBreakeven) ? 'N/A' : `${Math.round(avgBreakeven)} months`);
// Update metric card styling based on performance
const netPositionElement = document.getElementById('net-position');
if (netPositionElement) {
if (avgNetPosition > 0) {
netPositionElement.className = 'metric-value text-success';
} else if (avgNetPosition < 0) {
netPositionElement.className = 'metric-value text-danger';
} else {
netPositionElement.className = 'metric-value';
}
}
const roiElement = document.getElementById('roi-percentage');
if (roiElement) {
if (avgROI > 15) {
roiElement.className = 'metric-value text-success';
} else if (avgROI > 5) {
roiElement.className = 'metric-value text-warning';
} else if (avgROI < 0) {
roiElement.className = 'metric-value text-danger';
} else {
roiElement.className = 'metric-value';
}
}
} catch (error) {
console.error('Error updating summary metrics:', error);
}
@ -48,15 +74,23 @@ class UIManager {
'<span class="badge bg-warning">Loan</span>' :
'<span class="badge bg-success">Direct</span>';
const performanceInfo = result.investmentModel === 'direct' ?
`<small class="text-muted d-block">Performance: ${result.performanceMultiplier.toFixed(2)}x</small>` +
`<small class="text-muted d-block">Grace: ${result.effectiveGracePeriod} months</small>` :
'<small class="text-muted">Fixed returns</small>';
const row = tbody.insertRow();
row.innerHTML = `
<td><strong>${result.scenario}</strong></td>
<td><strong>${result.scenario}</strong><br>${performanceInfo}</td>
<td>${modelLabel}</td>
<td>${result.finalInstances.toLocaleString()}</td>
<td>${this.formatCurrencyDetailed(result.totalRevenue)}</td>
<td>${this.formatCurrencyDetailed(result.cspRevenue)}</td>
<td>${this.formatCurrencyDetailed(result.servalaRevenue)}</td>
<td class="${result.roi >= 0 ? 'text-success' : 'text-danger'}">${this.formatPercentage(result.roi)}</td>
<td class="${result.roi >= 0 ? 'text-success' : 'text-danger'}">
${this.formatPercentage(result.roi)}
${result.avgPerformanceBonus > 0 ? `<br><small class="text-info">+${this.formatPercentage(result.avgPerformanceBonus * 100)} bonus</small>` : ''}
</td>
<td>${result.breakEvenMonth ? result.breakEvenMonth + ' months' : 'N/A'}</td>
`;
});
@ -87,16 +121,22 @@ class UIManager {
allData.forEach(data => {
const row = tbody.insertRow();
// Enhanced monthly breakdown with financial focus
const performanceIcon = data.performanceBonus > 0 ? ' <i class="bi bi-star-fill text-warning" title="Performance Bonus Active"></i>' : '';
const graceIcon = data.month <= (data.effectiveGracePeriod || 6) ? ' <i class="bi bi-shield-fill-check text-success" title="Grace Period Active"></i>' : '';
const netPositionClass = (data.netPosition || 0) >= 0 ? 'text-success' : 'text-danger';
row.innerHTML = `
<td>${data.month}</td>
<td><span class="badge bg-secondary">${data.scenario}</span></td>
<td>${data.newInstances}</td>
<td>${data.churnedInstances}</td>
<td><strong>${data.month}</strong>${graceIcon}</td>
<td><span class="badge bg-secondary">${data.scenario}</span>${performanceIcon}</td>
<td>+${data.newInstances}</td>
<td class="text-muted">-${data.churnedInstances}</td>
<td>${data.totalInstances.toLocaleString()}</td>
<td>${this.formatCurrencyDetailed(data.monthlyRevenue)}</td>
<td>${this.formatCurrencyDetailed(data.cspRevenue)}</td>
<td>${this.formatCurrencyDetailed(data.servalaRevenue)}</td>
<td>${this.formatCurrencyDetailed(data.cumulativeCSPRevenue)}</td>
<td class="fw-bold">${this.formatCurrencyDetailed(data.cspRevenue)}</td>
<td class="text-muted">${this.formatCurrencyDetailed(data.servalaRevenue)}</td>
<td class="${netPositionClass} fw-bold">${this.formatCurrencyDetailed(data.netPosition || (data.cumulativeCSPRevenue - 500000))}</td>
`;
});
} catch (error) {

View file

@ -86,22 +86,21 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="collapsible-content" id="help-section">
<div class="help-content">
<h6 class="text-primary mb-2"><i class="bi bi-lightbulb"></i> Calculator Overview</h6>
<p class="small">This ROI calculator models your investment in the Servala platform by simulating cloud service provider (CSP) business growth over time. Choose between lending to Servala (guaranteed returns) or direct platform investment (higher potential returns based on your sales performance).</p>
<p class="small">This ROI calculator models your investment in the Servala platform with two distinct approaches. Choose between guaranteed fixed returns through lending, or performance-based returns through direct investment that rewards your sales capabilities.</p>
<h6 class="text-primary mb-2 mt-3"><i class="bi bi-gear"></i> Key Parameters</h6>
<div class="small">
<p><strong>Investment Model:</strong> Loan to Servala (fixed returns) vs Direct Investment (performance-based returns).</p>
<p><strong>Investment Amount:</strong> Your capital provided to Servala for platform development and infrastructure.</p>
<p><strong>Monthly Revenue per Instance:</strong> The recurring revenue generated from each managed service instance (excl. compute).</p>
<p><strong>Servala Revenue Share:</strong> Percentage of revenue shared with Servala after the grace period (Direct Investment only).</p>
<p><strong>Grace Period:</strong> Initial months where you keep 100% of revenue before sharing begins with Servala (Direct Investment only).</p>
<p><strong>Loan Interest Rate:</strong> Annual interest rate on loan to Servala (Loan Model only).</p>
<p><strong>Investment Model:</strong> Loan (3-7% fixed returns) vs Direct Investment (15-40% performance-based returns).</p>
<p><strong>Investment Amount:</strong> Higher amounts unlock progressive scaling benefits - 500k = 1.0x, 1M = 1.5x, 2M = 2.0x performance.</p>
<p><strong>Dynamic Grace Period:</strong> Larger investments get longer 100% revenue retention (6+ months based on investment size).</p>
<p><strong>Performance Bonuses:</strong> Direct Investment CSPs earn up to 15% additional revenue share for exceeding sales targets.</p>
<p><strong>Monthly Revenue per Instance:</strong> Recurring revenue from managed services (typically CHF 50-200 per instance).</p>
</div>
<h6 class="text-primary mb-2 mt-3"><i class="bi bi-graph-up"></i> Investment Models</h6>
<div class="small">
<p><strong>Loan Model:</strong> Lend to Servala at fixed interest rate. Lower risk, guaranteed returns, but limited upside.</p>
<p><strong>Direct Investment:</strong> Invest directly in platform operations. Higher risk, but unlimited upside based on your sales performance during grace period and beyond.</p>
<p><strong>Loan Model:</strong> Lend capital at fixed interest (3-7% annually). Predictable monthly payments, lower risk, but capped returns.</p>
<p><strong>Direct Investment:</strong> Invest in operations with revenue sharing. Progressive scaling, performance bonuses, and extended grace periods reward larger investments and active sales participation.</p>
</div>
<h6 class="text-primary mb-2 mt-3"><i class="bi bi-graph-up"></i> Growth Scenarios</h6>
@ -114,10 +113,11 @@ function logout() { window.ROICalculatorApp?.logout(); }
<h6 class="text-primary mb-2 mt-3"><i class="bi bi-bullseye"></i> Understanding Results</h6>
<div class="small">
<p><strong>ROI:</strong> Return on Investment as a percentage of your initial investment.</p>
<p><strong>Break-even:</strong> Month when cumulative revenue equals your initial investment.</p>
<p><strong>Churn:</strong> Monthly percentage of instances that stop generating revenue (customer loss).</p>
<p><strong>Grace Period Advantage:</strong> Direct Investment model benefits significantly from aggressive sales during grace period (100% revenue retention).</p>
<p><strong>Net Position:</strong> Your financial position after accounting for initial investment (above zero = profitable).</p>
<p><strong>ROI Progression:</strong> How your returns develop over time - shows when investment becomes profitable.</p>
<p><strong>Performance Multiplier:</strong> How much your actual results exceed baseline expectations (1.0x = baseline, 1.5x = 50% better).</p>
<p><strong>Model Comparison:</strong> Direct side-by-side comparison of loan vs direct investment returns for your specific scenario.</p>
<p><strong>Investment Scaling:</strong> Larger investments unlock operational advantages - better customer acquisition and retention capabilities.</p>
</div>
</div>
</div>
@ -193,21 +193,21 @@ function logout() { window.ROICalculatorApp?.logout(); }
<i class="bi bi-question-circle-fill text-muted ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Choose your investment approach: Loan provides guaranteed returns but limited upside. Direct Investment rewards your sales performance with unlimited upside potential."
title="Loan Model: 3-7% guaranteed annual returns with predictable monthly payments. Direct Investment: 15-40% potential returns with progressive scaling, performance bonuses, and extended grace periods."
style="cursor: help; font-size: 0.8rem;"></i>
</label>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="investment-model" id="loan-model" value="loan" onchange="toggleInvestmentModel()">
<label class="btn btn-outline-warning" for="loan-model">
<i class="bi bi-bank"></i> Loan Model
<i class="bi bi-bank"></i> Loan Model (3-7%)
</label>
<input type="radio" class="btn-check" name="investment-model" id="direct-model" value="direct" checked onchange="toggleInvestmentModel()">
<label class="btn btn-outline-success" for="direct-model">
<i class="bi bi-rocket"></i> Direct Investment
<i class="bi bi-rocket"></i> Direct Investment (15-40%)
</label>
</div>
<small class="text-muted mt-1">
<span id="model-description">Direct Investment: Higher potential returns based on your sales performance</span>
<span id="model-description">Direct Investment: Performance-based returns with progressive scaling, bonuses up to 15%, and dynamic grace periods</span>
</small>
</div>
@ -500,30 +500,30 @@ function logout() { window.ROICalculatorApp?.logout(); }
<p class="mt-2">Calculating scenarios...</p>
</div>
<!-- Summary Metrics -->
<!-- Enhanced Financial Summary Metrics -->
<div class="row" id="summary-metrics">
<div class="col-md-3 col-sm-6">
<div class="metric-card text-center">
<div class="metric-value" id="total-instances">0</div>
<div class="metric-value" id="net-position">CHF 0</div>
<div class="metric-label">
Total Instances
Net Position
<i class="bi bi-question-circle-fill text-muted ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Average final number of cloud instances across all enabled scenarios at the end of your investment timeframe. This represents the scale of your cloud infrastructure business."
title="Your final financial position: CSP revenue minus initial investment. Shows your actual profit/loss in CHF. Positive values indicate profitable investment."
style="cursor: help; font-size: 0.8rem;"></i>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="metric-card text-center">
<div class="metric-value" id="total-revenue">CHF 0</div>
<div class="metric-value" id="csp-revenue">CHF 0</div>
<div class="metric-label">
Total Revenue
Your Revenue
<i class="bi bi-question-circle-fill text-muted ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Average total revenue generated across all enabled scenarios over your investment timeframe. This includes both CSP revenue (what you keep) and Servala revenue (platform fees)."
title="Total revenue you receive as CSP across all scenarios. For loan model: fixed loan payments. For direct investment: your share of business revenue including performance bonuses."
style="cursor: help; font-size: 0.8rem;"></i>
</div>
</div>
@ -532,11 +532,11 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="metric-card text-center">
<div class="metric-value" id="roi-percentage">0%</div>
<div class="metric-label">
Average ROI
ROI Performance
<i class="bi bi-question-circle-fill text-muted ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Return on Investment: Average percentage return across all enabled scenarios. Calculated as (CSP Revenue - Initial Investment) / Initial Investment × 100%. Shows how much profit you make relative to your initial investment."
title="Return on Investment: Your net profit as percentage of initial investment. Loan model typically 3-7%, Direct investment potentially 15-40% depending on performance."
style="cursor: help; font-size: 0.8rem;"></i>
</div>
</div>
@ -545,22 +545,23 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="metric-card text-center">
<div class="metric-value" id="breakeven-time">N/A</div>
<div class="metric-label">
Avg Break-even
Break-Even
<i class="bi bi-question-circle-fill text-muted ms-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Average time in months across all scenarios when your cumulative CSP revenue equals your initial investment. This is when you've recovered your upfront costs and start making pure profit."
title="Time when you recover your initial investment. Loan model: 12-18 months. Direct investment: 15-24 months, but with unlimited upside potential thereafter."
style="cursor: help; font-size: 0.8rem;"></i>
</div>
</div>
</div>
</div>
<!-- Charts -->
<!-- Enhanced Financial Charts -->
<div class="row">
<div class="col-12">
<div class="chart-container">
<h5><i class="bi bi-graph-up-arrow"></i> Instance Growth Over Time</h5>
<h5><i class="bi bi-graph-up-arrow"></i> ROI Progression Over Time</h5>
<p class="small text-muted mb-3">Shows when your investment becomes profitable (crosses zero line) and how returns develop over time.</p>
<canvas id="instanceGrowthChart"></canvas>
</div>
</div>
@ -569,13 +570,15 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="row">
<div class="col-md-6">
<div class="chart-container">
<h5><i class="bi bi-cash-stack"></i> Cumulative Revenue</h5>
<h5><i class="bi bi-cash-stack"></i> Net Financial Position (Break-Even Analysis)</h5>
<p class="small text-muted mb-3">Your cumulative profit/loss over time. Above zero = profitable, below zero = recovering investment.</p>
<canvas id="revenueChart"></canvas>
</div>
</div>
<div class="col-md-6">
<div class="chart-container">
<h5><i class="bi bi-bar-chart"></i> Monthly Cash Flow</h5>
<h5><i class="bi bi-bar-chart"></i> Investment Model Performance Comparison</h5>
<p class="small text-muted mb-3">Direct comparison of returns across growth scenarios, showing performance bonuses for direct investment.</p>
<canvas id="cashFlowChart"></canvas>
</div>
</div>
@ -596,18 +599,19 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="row">
<div class="col-12">
<div class="chart-container">
<h5><i class="bi bi-table"></i> Scenario Comparison</h5>
<h5><i class="bi bi-table"></i> Financial Performance Comparison</h5>
<p class="small text-muted mb-3">Detailed comparison of investment returns across growth scenarios. Direct Investment shows performance multipliers and grace period benefits.</p>
<div class="table-responsive">
<table class="table table-striped" id="comparison-table">
<thead class="table-dark">
<tr>
<th>Scenario</th>
<th>Investment Model</th>
<th>Final Instances</th>
<th>Scenario & Performance</th>
<th>Model</th>
<th>Business Scale</th>
<th>Total Revenue</th>
<th>CSP Revenue</th>
<th>Servala Revenue</th>
<th>ROI</th>
<th>Your Revenue</th>
<th>Servala Share</th>
<th>ROI & Bonuses</th>
<th>Break-even</th>
</tr>
</thead>
@ -626,25 +630,26 @@ function logout() { window.ROICalculatorApp?.logout(); }
<div class="collapsible-section">
<div class="collapsible-header" onclick="toggleCollapsible('monthly-breakdown')">
<h5 class="mb-0">
<i class="bi bi-calendar3"></i> Monthly Breakdown
<i class="bi bi-calendar3"></i> Monthly Financial Flow Analysis
<i class="bi bi-chevron-down float-end"></i>
</h5>
</div>
<div class="collapsible-content" id="monthly-breakdown">
<div class="chart-container">
<p class="small text-muted mb-3">Month-by-month financial performance showing revenue distribution, performance bonuses, and progress toward break-even.</p>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm table-striped" id="monthly-table">
<thead class="table-dark sticky-top">
<tr>
<th>Month</th>
<th>Scenario</th>
<th>New Instances</th>
<th>Churned</th>
<th>Total Instances</th>
<th>Growth</th>
<th>Churn</th>
<th>Scale</th>
<th>Monthly Revenue</th>
<th>CSP Revenue</th>
<th>Servala Revenue</th>
<th>Cumulative CSP</th>
<th>Your Share</th>
<th>Servala Share</th>
<th>Net Position</th>
</tr>
</thead>
<tbody id="monthly-tbody">