add core revenue to model and allow to tweak params

This commit is contained in:
Tobias Brunner 2025-07-23 14:29:54 +02:00
parent a07788cb74
commit 491dbacda4
Signed by: tobru
SSH key fingerprint: SHA256:kOXg1R6c11XW3/Pt9dbLdQvOJGFAy+B2K6v6PtRWBGQ
7 changed files with 476 additions and 118 deletions

View file

@ -32,6 +32,7 @@ function updateRevenuePerInstance(value) { window.ROICalculatorApp?.updateRevenu
function updateServalaShare(value) { window.ROICalculatorApp?.updateServalaShare(value); }
function updateGracePeriod(value) { window.ROICalculatorApp?.updateGracePeriod(value); }
function updateLoanRate(value) { window.ROICalculatorApp?.updateLoanRate(value); }
function updateCoreServiceRevenue(value) { window.ROICalculatorApp?.updateCoreServiceRevenue(value); }
function updateScenarioChurn(scenarioKey, churnRate) { window.ROICalculatorApp?.updateScenarioChurn(scenarioKey, churnRate); }
function updateScenarioPhase(scenarioKey, phaseIndex, newInstancesPerMonth) { window.ROICalculatorApp?.updateScenarioPhase(scenarioKey, phaseIndex, newInstancesPerMonth); }
function resetAdvancedParameters() { window.ROICalculatorApp?.resetAdvancedParameters(); }
@ -124,60 +125,65 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Organized Controls Section -->
<div class="py-3">
<!-- Primary Controls Row -->
<div class="row align-items-end mb-3">
<!-- Main Configuration Section -->
<div class="py-4">
<!-- Investment & Timeframe Row -->
<div class="row mb-4">
<!-- Investment Amount -->
<div class="col-lg-3 col-md-4 mb-2">
<label class="form-label small fw-semibold mb-1">Initial Investment</label>
<div class="input-group input-group-sm">
<div class="col-lg-4 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Initial Investment</label>
<div class="input-group">
<span class="input-group-text">CHF</span>
<input type="text" class="form-control" id="investment-amount"
data-value="500000" value="500,000"
oninput="handleInvestmentAmountInput(this)"
onchange="updateCalculations()">
</div>
<input type="range" class="form-range mt-1" id="investment-slider"
<input type="range" class="form-range mt-2" id="investment-slider"
min="100000" max="2000000" step="50000" value="500000"
onchange="updateInvestmentAmount(this.value)">
</div>
<!-- Time & Revenue -->
<div class="col-lg-3 col-md-4 mb-2">
<div class="row">
<div class="col-6">
<label class="form-label small fw-semibold mb-1">Years</label>
<select class="form-select form-select-sm" id="timeframe" onchange="updateCalculations()">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<div class="col-6">
<label class="form-label small fw-semibold mb-1">Revenue/Instance</label>
<div class="input-group input-group-sm">
<input type="number" class="form-control" id="revenue-per-instance"
min="20" max="200" step="5" value="50" onchange="updateCalculations()">
<span class="input-group-text">CHF</span>
</div>
<input type="range" class="form-range mt-1" id="revenue-slider"
min="20" max="200" step="5" value="50"
onchange="updateRevenuePerInstance(this.value)">
</div>
<div class="d-flex justify-content-between">
<small class="text-muted">CHF 100K</small>
<small class="text-muted">CHF 2M</small>
</div>
</div>
<!-- Timeframe -->
<div class="col-lg-2 col-md-3 mb-3">
<label class="form-label fw-semibold mb-2">Analysis Period</label>
<select class="form-select" id="timeframe" onchange="updateCalculations()">
<option value="1">1 Year</option>
<option value="2">2 Years</option>
<option value="3" selected>3 Years</option>
<option value="4">4 Years</option>
<option value="5">5 Years</option>
</select>
</div>
<!-- Service Revenue -->
<div class="col-lg-3 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Service Revenue per Instance</label>
<div class="input-group">
<input type="number" class="form-control" id="revenue-per-instance"
min="20" max="200" step="5" value="50" onchange="updateCalculations()">
<span class="input-group-text">CHF/month</span>
</div>
<input type="range" class="form-range mt-2" id="revenue-slider"
min="20" max="200" step="5" value="50"
onchange="updateRevenuePerInstance(this.value)">
<div class="d-flex justify-content-between">
<small class="text-muted">CHF 20</small>
<small class="text-muted">CHF 200</small>
</div>
</div>
<!-- Actions -->
<div class="col-lg-2 col-md-4 mb-2">
<div class="d-flex justify-content-end gap-2">
<button class="btn btn-outline-info btn-sm" type="button" onclick="toggleAdvancedControls()" id="advancedToggleBtn">
<i class="bi bi-gear"></i> Advanced
<div class="col-lg-3 col-md-3 mb-3 d-flex align-items-end">
<div class="d-flex gap-2 w-100">
<button class="btn btn-outline-info" type="button" onclick="toggleAdvancedControls()" id="advancedToggleBtn">
<i class="bi bi-gear"></i> Advanced Settings
</button>
<a href="{% url 'services:roi_calculator_help' %}" class="btn btn-outline btn-sm" target="_blank">
<a href="{% url 'services:roi_calculator_help' %}" class="btn btn-outline-secondary" target="_blank">
<i class="bi bi-question-circle"></i> Help
</a>
</div>
@ -185,49 +191,54 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
<!-- Scenarios & Results Row -->
<div class="row align-items-center">
<!-- Scenario Selection -->
<div class="col-lg-6 col-md-8 mb-2">
<label class="form-label small fw-semibold mb-2">Growth Scenarios</label>
<div class="d-flex gap-3 justify-content-center">
<div class="row">
<!-- Growth Scenarios -->
<div class="col-lg-6 mb-3">
<label class="form-label fw-semibold mb-2">Growth Scenarios</label>
<div class="d-flex gap-4 flex-wrap">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="conservative-enabled" checked onchange="toggleScenario('conservative')">
<label class="form-check-label small fw-medium" for="conservative-enabled" data-bs-toggle="tooltip" title="Conservative: 2% churn, steady growth">
<span class="text-success"></span> Conservative
<label class="form-check-label fw-medium" for="conservative-enabled" data-bs-toggle="tooltip" title="Conservative: 2% churn, steady growth">
<span class="text-success fs-5"></span> Conservative
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="moderate-enabled" checked onchange="toggleScenario('moderate')">
<label class="form-check-label small fw-medium" for="moderate-enabled" data-bs-toggle="tooltip" title="Moderate: 3% churn, balanced growth">
<span class="text-warning"></span> Moderate
<label class="form-check-label fw-medium" for="moderate-enabled" data-bs-toggle="tooltip" title="Moderate: 3% churn, balanced growth">
<span class="text-warning fs-5"></span> Moderate
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="aggressive-enabled" checked onchange="toggleScenario('aggressive')">
<label class="form-check-label small fw-medium" for="aggressive-enabled" data-bs-toggle="tooltip" title="Aggressive: 5% churn, rapid growth">
<span class="text-danger"></span> Aggressive
<label class="form-check-label fw-medium" for="aggressive-enabled" data-bs-toggle="tooltip" title="Aggressive: 5% churn, rapid growth">
<span class="text-danger fs-5"></span> Aggressive
</label>
</div>
</div>
</div>
<!-- Model Results -->
<div class="col-lg-6 col-md-4 mb-2">
<div class="row">
<div class="col-lg-6 mb-3">
<label class="form-label fw-semibold mb-2">Real-Time Results</label>
<div class="row g-2">
<div class="col-6">
<div class="text-center p-2 border rounded model-result-box">
<div class="text-success fw-semibold small mb-1">Direct Investment</div>
<div class="fw-bold" id="net-position-direct" style="font-size: 1.1rem; line-height: 1.2;">CHF 0</div>
<div class="fw-bold text-primary" id="roi-percentage-direct" style="font-size: 0.9rem; line-height: 1.2;">0%</div>
<div style="font-size: 0.7rem;" class="text-muted">Net Position / ROI</div>
<div class="card h-100">
<div class="card-body text-center p-3">
<div class="text-success fw-bold mb-2">Direct Investment</div>
<div class="h5 mb-1" id="net-position-direct">CHF 0</div>
<div class="text-primary fw-bold" id="roi-percentage-direct">0%</div>
<small class="text-muted">Net Profit / ROI</small>
</div>
</div>
</div>
<div class="col-6">
<div class="text-center p-2 border rounded model-result-box">
<div class="text-warning fw-semibold small mb-1">Loan Model</div>
<div class="fw-bold" id="net-position-loan" style="font-size: 1.1rem; line-height: 1.2;">CHF 0</div>
<div class="fw-bold text-primary" id="roi-percentage-loan" style="font-size: 0.9rem; line-height: 1.2;">0%</div>
<div style="font-size: 0.7rem;" class="text-muted">Net Position / ROI</div>
<div class="card h-100">
<div class="card-body text-center p-3">
<div class="text-warning fw-bold mb-2">Loan Model</div>
<div class="h5 mb-1" id="net-position-loan">CHF 0</div>
<div class="text-primary fw-bold" id="roi-percentage-loan">0%</div>
<small class="text-muted">Net Profit / ROI</small>
</div>
</div>
</div>
</div>
@ -235,53 +246,232 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Collapsible Advanced Controls -->
<!-- Advanced Controls Section -->
<div class="collapse" id="advancedControls">
<div class="row py-2 bg-white border-top">
<!-- Loan Rate (for loan model calculations) -->
<div class="col-md-2" id="loan-rate-section">
<label class="form-label small mb-1">Loan Rate (%)</label>
<input type="number" class="form-control form-control-sm" id="loan-interest-rate"
min="3" max="8" step="0.1" value="5.0" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="loan-rate-slider"
min="3" max="8" step="0.1" value="5.0" onchange="updateLoanRate(this.value)">
</div>
<!-- Servala Share -->
<div class="col-md-2">
<label class="form-label small mb-1">Servala Share (%)</label>
<input type="number" class="form-control form-control-sm" id="servala-share"
min="10" max="40" step="1" value="25" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="share-slider"
min="10" max="40" step="1" value="25" onchange="updateServalaShare(this.value)">
</div>
<!-- Grace Period -->
<div class="col-md-2">
<label class="form-label small mb-1">Grace Period (Mo)</label>
<input type="number" class="form-control form-control-sm" id="grace-period"
min="0" max="24" step="1" value="6" onchange="updateCalculations()">
<input type="range" class="form-range mt-1" id="grace-slider"
min="0" max="24" step="1" value="6" onchange="updateGracePeriod(this.value)">
</div>
<!-- Scenario Tuning -->
<div class="col-md-6">
<div class="bg-light border-top py-4">
<div class="container-fluid">
<h6 class="text-primary mb-4"><i class="bi bi-gear"></i> Advanced Configuration</h6>
<!-- Investment Model Parameters -->
<div class="row mb-4">
<div class="col-12">
<h6 class="text-secondary mb-3">Investment Model Settings</h6>
</div>
<!-- Loan Rate -->
<div class="col-lg-3 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Loan Interest Rate</label>
<div class="input-group">
<input type="number" class="form-control" id="loan-interest-rate"
min="3" max="8" step="0.1" value="5.0" onchange="updateCalculations()">
<span class="input-group-text">% annual</span>
</div>
<input type="range" class="form-range mt-2" id="loan-rate-slider"
min="3" max="8" step="0.1" value="5.0" onchange="updateLoanRate(this.value)">
<div class="d-flex justify-content-between">
<small class="text-muted">3%</small>
<small class="text-muted">8%</small>
</div>
</div>
<!-- Servala Share -->
<div class="col-lg-3 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Servala Revenue Share</label>
<div class="input-group">
<input type="number" class="form-control" id="servala-share"
min="10" max="40" step="1" value="25" onchange="updateCalculations()">
<span class="input-group-text">%</span>
</div>
<input type="range" class="form-range mt-2" id="share-slider"
min="10" max="40" step="1" value="25" onchange="updateServalaShare(this.value)">
<div class="d-flex justify-content-between">
<small class="text-muted">10%</small>
<small class="text-muted">40%</small>
</div>
</div>
<!-- Grace Period -->
<div class="col-lg-3 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Grace Period</label>
<div class="input-group">
<input type="number" class="form-control" id="grace-period"
min="0" max="24" step="1" value="6" onchange="updateCalculations()">
<span class="input-group-text">months</span>
</div>
<input type="range" class="form-range mt-2" id="grace-slider"
min="0" max="24" step="1" value="6" onchange="updateGracePeriod(this.value)">
<div class="d-flex justify-content-between">
<small class="text-muted">0</small>
<small class="text-muted">24</small>
</div>
</div>
<!-- Core Service Revenue -->
<div class="col-lg-3 col-md-6 mb-3">
<label class="form-label fw-semibold mb-2">Core Service Revenue</label>
<div class="input-group">
<input type="number" class="form-control" id="core-service-revenue"
min="0" max="500" step="5" value="0" onchange="updateCalculations()">
<span class="input-group-text">CHF/month</span>
</div>
<input type="range" class="form-range mt-2" id="core-revenue-slider"
min="0" max="500" step="5" value="0" onchange="updateCoreServiceRevenue(this.value)">
<div class="d-flex justify-content-between">
<small class="text-muted">CHF 0</small>
<small class="text-muted">CHF 500</small>
</div>
<small class="text-muted">Additional compute/storage revenue per instance</small>
</div>
</div>
<!-- Scenario Parameters -->
<div class="row mb-4">
<div class="col-12">
<h6 class="text-secondary mb-3">Growth Scenario Tuning</h6>
</div>
<!-- Conservative Scenario -->
<div class="col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header bg-success text-white">
<h6 class="mb-0"><i class="bi bi-shield-check"></i> Conservative Scenario</h6>
</div>
<div class="card-body">
<!-- Churn Rate -->
<div class="mb-3">
<label class="form-label fw-semibold mb-2">Monthly Churn Rate</label>
<div class="input-group input-group-sm">
<input type="number" class="form-control" id="conservative-churn"
min="0" max="10" step="0.1" value="2.0" onchange="updateScenarioChurn('conservative', this.value)">
<span class="input-group-text">%</span>
</div>
</div>
<!-- Phase Growth Parameters -->
<label class="form-label fw-semibold mb-2">Instance Growth per Phase</label>
<div class="row g-2">
<div class="col-6">
<label class="form-label small">Phase 1 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="conservative-phase-0"
min="10" max="200" value="50" onchange="updateScenarioPhase('conservative', 0, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 2 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="conservative-phase-1"
min="10" max="200" value="75" onchange="updateScenarioPhase('conservative', 1, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 3 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="conservative-phase-2"
min="10" max="300" value="100" onchange="updateScenarioPhase('conservative', 2, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 4 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="conservative-phase-3"
min="10" max="300" value="150" onchange="updateScenarioPhase('conservative', 3, this.value)">
</div>
</div>
</div>
</div>
</div>
<!-- Moderate Scenario -->
<div class="col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="bi bi-speedometer2"></i> Moderate Scenario</h6>
</div>
<div class="card-body">
<!-- Churn Rate -->
<div class="mb-3">
<label class="form-label fw-semibold mb-2">Monthly Churn Rate</label>
<div class="input-group input-group-sm">
<input type="number" class="form-control" id="moderate-churn"
min="0" max="10" step="0.1" value="3.0" onchange="updateScenarioChurn('moderate', this.value)">
<span class="input-group-text">%</span>
</div>
</div>
<!-- Phase Growth Parameters -->
<label class="form-label fw-semibold mb-2">Instance Growth per Phase</label>
<div class="row g-2">
<div class="col-6">
<label class="form-label small">Phase 1 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="moderate-phase-0"
min="20" max="300" value="100" onchange="updateScenarioPhase('moderate', 0, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 2 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="moderate-phase-1"
min="20" max="400" value="200" onchange="updateScenarioPhase('moderate', 1, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 3 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="moderate-phase-2"
min="20" max="500" value="300" onchange="updateScenarioPhase('moderate', 2, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 4 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="moderate-phase-3"
min="20" max="600" value="400" onchange="updateScenarioPhase('moderate', 3, this.value)">
</div>
</div>
</div>
</div>
</div>
<!-- Aggressive Scenario -->
<div class="col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<h6 class="mb-0"><i class="bi bi-rocket"></i> Aggressive Scenario</h6>
</div>
<div class="card-body">
<!-- Churn Rate -->
<div class="mb-3">
<label class="form-label fw-semibold mb-2">Monthly Churn Rate</label>
<div class="input-group input-group-sm">
<input type="number" class="form-control" id="aggressive-churn"
min="0" max="15" step="0.1" value="5.0" onchange="updateScenarioChurn('aggressive', this.value)">
<span class="input-group-text">%</span>
</div>
</div>
<!-- Phase Growth Parameters -->
<label class="form-label fw-semibold mb-2">Instance Growth per Phase</label>
<div class="row g-2">
<div class="col-6">
<label class="form-label small">Phase 1 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-phase-0"
min="50" max="500" value="200" onchange="updateScenarioPhase('aggressive', 0, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 2 (6mo)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-phase-1"
min="50" max="600" value="400" onchange="updateScenarioPhase('aggressive', 1, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 3 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-phase-2"
min="50" max="800" value="600" onchange="updateScenarioPhase('aggressive', 2, this.value)">
</div>
<div class="col-6">
<label class="form-label small">Phase 4 (12mo)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-phase-3"
min="50" max="1000" value="800" onchange="updateScenarioPhase('aggressive', 3, this.value)">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Reset Button -->
<div class="row">
<div class="col-4">
<label class="form-label small mb-1">Conservative Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="conservative-churn"
min="0" max="10" step="0.1" value="2.0" onchange="updateScenarioChurn('conservative', this.value)">
</div>
<div class="col-4">
<label class="form-label small mb-1">Moderate Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="moderate-churn"
min="0" max="10" step="0.1" value="3.0" onchange="updateScenarioChurn('moderate', this.value)">
</div>
<div class="col-4">
<label class="form-label small mb-1">Aggressive Churn (%)</label>
<input type="number" class="form-control form-control-sm" id="aggressive-churn"
min="0" max="15" step="0.1" value="5.0" onchange="updateScenarioChurn('aggressive', this.value)">
<div class="col-12 text-center">
<button type="button" class="btn btn-outline-secondary" onclick="resetAdvancedParameters()">
<i class="bi bi-arrow-clockwise"></i> Reset to Defaults
</button>
</div>
</div>
</div>
@ -445,7 +635,9 @@ document.addEventListener('DOMContentLoaded', function() {
<th>Scenario</th>
<th>Model</th>
<th>Instances</th>
<th>Monthly Revenue</th>
<th>Service Revenue</th>
<th>Core Revenue</th>
<th>Total Revenue</th>
<th>Your Share</th>
<th>Servala Share</th>
<th>Cumulative Net Position</th>

View file

@ -189,6 +189,14 @@ html {
<h3>How It Works</h3>
<p>Invest directly in Servala's operations and earn returns through revenue sharing that scales with performance and investment size.</p>
<h3>Total Revenue Potential</h3>
<div class="model-card direct-model">
<h5><i class="bi bi-cash-stack"></i> Dual Revenue Streams</h5>
<p><strong>Service Revenue:</strong> Monthly fees from Servala managed services (shared with Servala based on your investment terms)</p>
<p><strong>Core Service Revenue:</strong> Additional revenue from selling compute, storage, and infrastructure to support each managed service instance (100% retained by CSP)</p>
<p class="text-muted"><small>Example: CHF 50 service fee + CHF 100 compute/storage = CHF 150 total revenue per instance per month</small></p>
</div>
<h4>Progressive Scaling Benefits</h4>
<div class="table-responsive">
@ -300,7 +308,7 @@ html {
<ul>
<li><strong>Initial Investment:</strong> CHF 100K - 2M (with slider)</li>
<li><strong>Timeframe:</strong> 1-5 years</li>
<li><strong>Revenue/Instance:</strong> Monthly income per managed service (CHF 20-200)</li>
<li><strong>Service Revenue/Instance:</strong> Monthly Servala service fee per managed instance (CHF 20-200)</li>
<li><strong>Growth Scenarios:</strong> Conservative, Moderate, Aggressive</li>
</ul>
</div>
@ -310,6 +318,7 @@ html {
<li><strong>Loan Rate:</strong> Annual interest (3-8%) - affects loan model calculations</li>
<li><strong>Servala Share:</strong> Revenue split percentage (10-40%) for direct investment</li>
<li><strong>Grace Period:</strong> 100% revenue retention period (0-24 months, direct investment)</li>
<li><strong>Core Revenue/Instance:</strong> Additional monthly revenue from selling compute/storage per instance (CHF 0-500)</li>
<li><strong>Churn Rates:</strong> Customer loss percentages by scenario (0-15%)</li>
</ul>
</div>
@ -419,8 +428,11 @@ html {
<h4>What is the Performance Multiplier?</h4>
<p>The Performance Multiplier is an automatically calculated metric showing how your actual results compare to baseline expectations. For example, 1.5x means you're performing 50% better than the baseline scenario. <strong>This cannot be manually configured</strong> - it's calculated based on your investment scaling factors.</p>
<h4>What is Core Service Revenue?</h4>
<p>Core Service Revenue represents the additional monthly income CSPs earn by selling compute, storage, and infrastructure resources required to run each Servala managed service instance. This revenue stream is <strong>100% retained by the CSP</strong> and is not shared with Servala, providing additional profit potential beyond the service fees.</p>
<h4>What happens during the grace period?</h4>
<p>You keep 100% of revenue during this period. Grace periods are longer for larger investments (6-12 months).</p>
<p>You keep 100% of service revenue during this period, plus all core service revenue. Grace periods are longer for larger investments (6-12 months).</p>
<h4>How accurate are the projections?</h4>
<p>Projections are based on industry benchmarks and Servala's historical data, but actual results may vary based on market conditions and your sales performance.</p>