2025-07-16 15:12:25 +02:00
{% extends 'base.html' %}
{% load static %}
{% block title %}CSP ROI Calculator{% endblock %}
2025-07-22 08:33:54 +02:00
{% block extra_head %}
< meta name = "csrf-token" content = "{{ csrf_token }}" >
{% endblock %}
2025-07-16 15:12:25 +02:00
{% block extra_css %}
2025-07-21 16:04:53 +02:00
< link rel = "stylesheet" type = "text/css" href = '{% static "css/roi-calculator.css" %}' >
{% endblock %}
2025-07-16 15:15:34 +02:00
2025-07-21 16:04:53 +02:00
{% block extra_js %}
< script src = "{% static " js / chart . umd . min . js " % } " > < / script >
< script src = "{% static " js / jspdf . umd . min . js " % } " > < / script >
2025-07-22 08:50:48 +02:00
<!-- ROI Calculator Modules -->
< script src = "{% static " js / roi-calculator / input-utils . js " % } " > < / script >
< script src = "{% static " js / roi-calculator / calculator-core . js " % } " > < / script >
< script src = "{% static " js / roi-calculator / chart-manager . js " % } " > < / script >
< script src = "{% static " js / roi-calculator / ui-manager . js " % } " > < / script >
< script src = "{% static " js / roi-calculator / export-manager . js " % } " > < / script >
< script src = "{% static " js / roi-calculator / roi-calculator-app . js " % } " > < / script >
< script >
// Global function wrappers for HTML onclick handlers
function updateCalculations() { window.ROICalculatorApp?.updateCalculations(); }
function exportToPDF() { window.ROICalculatorApp?.exportToPDF(); }
function exportToCSV() { window.ROICalculatorApp?.exportToCSV(); }
function handleInvestmentAmountInput(input) { InputUtils.handleInvestmentAmountInput(input); }
2025-07-23 14:38:30 +02:00
function handleInvestmentAmountFocus(input) { InputUtils.handleInvestmentAmountFocus(input); }
function handleInvestmentAmountBlur(input) { InputUtils.handleInvestmentAmountBlur(input); }
2025-07-22 08:50:48 +02:00
function updateInvestmentAmount(value) { window.ROICalculatorApp?.updateInvestmentAmount(value); }
function updateRevenuePerInstance(value) { window.ROICalculatorApp?.updateRevenuePerInstance(value); }
function updateServalaShare(value) { window.ROICalculatorApp?.updateServalaShare(value); }
function updateGracePeriod(value) { window.ROICalculatorApp?.updateGracePeriod(value); }
function updateLoanRate(value) { window.ROICalculatorApp?.updateLoanRate(value); }
2025-07-23 14:29:54 +02:00
function updateCoreServiceRevenue(value) { window.ROICalculatorApp?.updateCoreServiceRevenue(value); }
2025-07-23 14:50:53 +02:00
function updateCurrency() {
const currencyElement = document.getElementById('currency');
const value = currencyElement ? currencyElement.value : 'CHF';
window.ROICalculatorApp?.updateCurrency(value);
}
2025-07-22 08:50:48 +02:00
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(); }
function toggleScenario(scenarioKey) { window.ROICalculatorApp?.toggleScenario(scenarioKey); }
2025-07-23 11:42:22 +02:00
function updateMonthlyBreakdownFilters() { window.ROICalculatorApp?.updateMonthlyBreakdownFilters(); }
2025-07-22 08:50:48 +02:00
function toggleCollapsible(elementId) { window.ROICalculatorApp?.toggleCollapsible(elementId); }
function resetCalculator() { window.ROICalculatorApp?.resetCalculator(); }
2025-07-23 11:16:15 +02:00
// toggleInvestmentModel function removed - both models calculated simultaneously
2025-07-22 08:50:48 +02:00
function logout() { window.ROICalculatorApp?.logout(); }
2025-07-22 17:30:37 +02:00
// Manual toggle functions for collapse elements
function toggleAdvancedControls() {
const element = document.getElementById('advancedControls');
const button = document.getElementById('advancedToggleBtn');
console.log('Toggling advanced controls, current classes:', element.className);
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
button.innerHTML = '< i class = "bi bi-gear" > < / i > Less';
console.log('Showing advanced controls');
} else {
element.style.display = 'none';
button.innerHTML = '< i class = "bi bi-gear" > < / i > More';
console.log('Hiding advanced controls');
}
}
function toggleDataCollapse() {
const element = document.getElementById('dataCollapse');
const button = document.getElementById('dataToggleBtn');
console.log('Toggling data collapse, current style:', element.style.display);
if (element.style.display === 'none' || element.style.display === '') {
element.style.display = 'block';
button.classList.remove('collapsed');
button.setAttribute('aria-expanded', 'true');
console.log('Showing data collapse');
} else {
element.style.display = 'none';
button.classList.add('collapsed');
button.setAttribute('aria-expanded', 'false');
console.log('Hiding data collapse');
}
}
// Initialize collapse states
document.addEventListener('DOMContentLoaded', function() {
// Ensure both sections start collapsed
const advancedControls = document.getElementById('advancedControls');
const dataCollapse = document.getElementById('dataCollapse');
if (advancedControls) {
advancedControls.style.display = 'none';
}
if (dataCollapse) {
dataCollapse.style.display = 'none';
}
console.log('Collapse elements initialized as hidden');
});
2025-07-22 08:50:48 +02:00
< / script >
2025-07-16 15:12:25 +02:00
{% endblock %}
{% block content %}
2025-07-22 17:30:37 +02:00
< div class = "container-fluid p-0" style = "min-height: 100vh;" >
<!-- Minimal Header with Controls -->
< div class = "bg-light border-bottom sticky-top" style = "z-index: 1030;" >
< div class = "container-fluid" >
<!-- Title Row -->
< div class = "row py-2 border-bottom" >
< div class = "col-md-6" >
< h4 class = "mb-0" > CSP ROI Calculator< / h4 >
< small class = "text-muted" > Real-time investment analysis< / small >
2025-07-16 15:12:25 +02:00
< / div >
2025-07-22 17:30:37 +02:00
< div class = "col-md-6 text-end" >
< button type = "button" class = "btn btn-sm btn-outline-primary me-1" onclick = "exportToPDF()" >
2025-07-21 16:14:32 +02:00
< i class = "bi bi-file-pdf" > < / i > PDF
< / button >
2025-07-22 17:30:37 +02:00
< button type = "button" class = "btn btn-sm btn-outline-success me-1" onclick = "exportToCSV()" >
2025-07-21 16:14:32 +02:00
< i class = "bi bi-file-csv" > < / i > CSV
< / button >
2025-07-23 11:48:10 +02:00
< button type = "button" class = "btn btn-sm btn-outline me-1" onclick = "resetCalculator()" >
2025-07-16 15:12:25 +02:00
< i class = "bi bi-arrow-clockwise" > < / i > Reset
< / button >
2025-07-22 17:30:37 +02:00
< button type = "button" class = "btn btn-sm btn-danger" onclick = "logout()" >
2025-07-16 15:12:25 +02:00
< i class = "bi bi-box-arrow-right" > < / i > Logout
< / button >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
2025-07-23 14:29:54 +02:00
<!-- Main Configuration Section -->
< div class = "py-4" >
< div class = "row" >
2025-07-23 14:35:15 +02:00
<!-- Left Column: Configuration Fields -->
< div class = "col-lg-8 col-xl-7" >
< div class = "main-config-fields" >
<!-- Investment Amount -->
< div class = "mb-4" >
< label class = "form-label fw-semibold mb-2" > Initial Investment< / label >
< div class = "input-group input-group-lg" >
2025-07-23 14:50:53 +02:00
< span class = "input-group-text" id = "investment-currency-prefix" > CHF< / span >
2025-07-23 14:35:15 +02:00
< input type = "text" class = "form-control" id = "investment-amount"
data-value="500000" value="500,000"
oninput="handleInvestmentAmountInput(this)"
2025-07-23 14:38:30 +02:00
onfocus="handleInvestmentAmountFocus(this)"
onblur="handleInvestmentAmountBlur(this)"
placeholder="Enter amount (100,000 - 2,000,000)">
2025-07-23 14:35:15 +02:00
< / div >
< input type = "range" class = "form-range mt-3" id = "investment-slider"
min="100000" max="2000000" step="50000" value="500000"
onchange="updateInvestmentAmount(this.value)">
< div class = "d-flex justify-content-between mt-1" >
2025-07-23 14:50:53 +02:00
< small class = "text-muted" id = "investment-min-label" > CHF 100K< / small >
< small class = "text-muted" id = "investment-max-label" > CHF 2M< / small >
2025-07-23 14:35:15 +02:00
< / div >
2025-07-23 11:16:15 +02:00
< / div >
2025-07-23 14:35:15 +02:00
2025-07-23 14:50:53 +02:00
<!-- Currency, Analysis Period & Service Revenue Row -->
2025-07-23 14:35:15 +02:00
< div class = "row mb-4" >
2025-07-23 14:50:53 +02:00
< div class = "col-md-3" >
< label class = "form-label fw-semibold mb-2" > Currency< / label >
< select class = "form-select form-select-lg" id = "currency" onchange = "updateCurrency()" >
< option value = "CHF" selected > CHF (Swiss Franc)< / option >
< option value = "EUR" > EUR (Euro)< / option >
< / select >
< / div >
< div class = "col-md-3" >
2025-07-23 14:35:15 +02:00
< label class = "form-label fw-semibold mb-2" > Analysis Period< / label >
< select class = "form-select form-select-lg" 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 >
< div class = "col-md-6" >
< label class = "form-label fw-semibold mb-2" > Service Revenue per Instance< / label >
< div class = "input-group input-group-lg" >
< input type = "number" class = "form-control" id = "revenue-per-instance"
min="20" max="200" step="5" value="50" onchange="updateCalculations()">
2025-07-23 14:50:53 +02:00
< span class = "input-group-text" id = "revenue-currency-suffix" > CHF/month< / span >
2025-07-23 14:35:15 +02:00
< / div >
< input type = "range" class = "form-range mt-3" id = "revenue-slider"
min="20" max="200" step="5" value="50"
onchange="updateRevenuePerInstance(this.value)">
< div class = "d-flex justify-content-between mt-1" >
2025-07-23 14:50:53 +02:00
< small class = "text-muted" id = "revenue-min-label" > CHF 20< / small >
< small class = "text-muted" id = "revenue-max-label" > CHF 200< / small >
2025-07-23 14:35:15 +02:00
< / div >
< / div >
2025-07-23 11:16:15 +02:00
< / div >
2025-07-23 14:35:15 +02:00
<!-- Growth Scenarios -->
< div class = "mb-4" >
< label class = "form-label fw-semibold mb-3" > Growth Scenarios< / label >
< div class = "d-flex gap-4 flex-wrap" >
< div class = "form-check form-check-lg" >
< input class = "form-check-input" type = "checkbox" id = "conservative-enabled" checked onchange = "toggleScenario('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-4" > ●< / span > Conservative
< / label >
< / div >
< div class = "form-check form-check-lg" >
< input class = "form-check-input" type = "checkbox" id = "moderate-enabled" checked onchange = "toggleScenario('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-4" > ●< / span > Moderate
< / label >
< / div >
< div class = "form-check form-check-lg" >
< input class = "form-check-input" type = "checkbox" id = "aggressive-enabled" checked onchange = "toggleScenario('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-4" > ●< / span > Aggressive
< / label >
< / div >
< / div >
< / div >
<!-- Action Buttons -->
< div class = "d-flex gap-3 flex-wrap" >
< button class = "btn btn-outline-info btn-lg" 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-secondary btn-lg" target = "_blank" >
< i class = "bi bi-question-circle" > < / i > Help
< / a >
2025-07-23 11:16:15 +02:00
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-23 11:16:15 +02:00
< / div >
2025-07-23 14:35:15 +02:00
<!-- Right Column: Real - Time Results -->
< div class = "col-lg-4 col-xl-5" >
< div class = "results-panel h-100" >
< div class = "card h-100 border-0 shadow" >
< div class = "card-header bg-primary text-white" >
< h5 class = "mb-0" > < i class = "bi bi-graph-up" > < / i > Real-Time Results< / h5 >
< small class = "opacity-75" > Live calculations based on your parameters< / small >
2025-07-23 11:16:15 +02:00
< / div >
2025-07-23 14:35:15 +02:00
< div class = "card-body d-flex flex-column justify-content-center" >
<!-- Direct Investment Results -->
< div class = "result-item mb-4" >
< div class = "d-flex align-items-center mb-2" >
< div class = "result-icon bg-success text-white rounded-circle me-3 d-flex align-items-center justify-content-center" style = "width: 40px; height: 40px;" >
< i class = "bi bi-rocket" > < / i >
< / div >
< div >
< h6 class = "mb-0 text-success fw-bold" > Direct Investment< / h6 >
< small class = "text-muted" > Performance-based returns< / small >
< / div >
< / div >
< div class = "result-metrics bg-light rounded p-3" >
< div class = "row text-center" >
< div class = "col-6" >
< div class = "h4 mb-1 text-success fw-bold" id = "net-position-direct" > CHF 0< / div >
< small class = "text-muted" > Net Profit< / small >
< / div >
< div class = "col-6" >
< div class = "h4 mb-1 text-primary fw-bold" id = "roi-percentage-direct" > 0%< / div >
< small class = "text-muted" > Total ROI< / small >
< / div >
< / div >
< / div >
< / div >
<!-- Loan Model Results -->
< div class = "result-item" >
< div class = "d-flex align-items-center mb-2" >
< div class = "result-icon bg-warning text-dark rounded-circle me-3 d-flex align-items-center justify-content-center" style = "width: 40px; height: 40px;" >
< i class = "bi bi-bank" > < / i >
< / div >
< div >
< h6 class = "mb-0 text-warning fw-bold" > Loan Model< / h6 >
< small class = "text-muted" > Fixed guaranteed returns< / small >
< / div >
< / div >
< div class = "result-metrics bg-light rounded p-3" >
< div class = "row text-center" >
< div class = "col-6" >
< div class = "h4 mb-1 text-success fw-bold" id = "net-position-loan" > CHF 0< / div >
< small class = "text-muted" > Net Profit< / small >
< / div >
< div class = "col-6" >
< div class = "h4 mb-1 text-primary fw-bold" id = "roi-percentage-loan" > 0%< / div >
< small class = "text-muted" > Total ROI< / small >
< / div >
< / div >
< / div >
2025-07-23 14:29:54 +02:00
< / div >
2025-07-23 11:16:15 +02:00
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-16 15:12:25 +02:00
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-23 14:29:54 +02:00
<!-- Advanced Controls Section -->
2025-07-22 17:30:37 +02:00
< div class = "collapse" id = "advancedControls" >
2025-07-23 14:29:54 +02:00
< 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 >
2025-07-16 15:31:29 +02:00
< / div >
2025-07-23 14:29:54 +02:00
<!-- 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 >
2025-07-16 15:31:29 +02:00
< / div >
2025-07-23 14:29:54 +02:00
<!-- 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-12 text-center" >
< button type = "button" class = "btn btn-outline-secondary" onclick = "resetAdvancedParameters()" >
< i class = "bi bi-arrow-clockwise" > < / i > Reset to Defaults
< / button >
2025-07-16 15:31:29 +02:00
< / div >
2025-07-16 15:12:25 +02:00
< / div >
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
2025-07-16 15:12:25 +02:00
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-16 15:12:25 +02:00
2025-07-22 17:30:37 +02:00
<!-- CHARTS - Maximum Space -->
< div class = "container-fluid px-3 py-3" style = "background: #f8f9fa;" >
<!-- Loading Spinner -->
< div class = "loading-spinner text-center py-5" id = "loading-spinner" style = "display: none;" >
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Calculating...< / span >
2025-07-16 15:12:25 +02:00
< / div >
2025-07-22 17:30:37 +02:00
< p class = "mt-2" > Calculating scenarios...< / p >
< / div >
2025-07-16 15:12:25 +02:00
2025-07-22 17:30:37 +02:00
<!-- PRIMARY CHART - Full Width, Large Height -->
< div class = "row mb-4" >
< div class = "col-12" >
< div class = "card border-0 shadow-sm" >
< div class = "card-header bg-white border-0 pb-0" >
< h5 class = "mb-1" > < i class = "bi bi-graph-up-arrow text-primary" > < / i > ROI Progression Over Time< / h5 >
< p class = "small text-muted mb-0" > Investment profitability timeline - when you'll break even and achieve target returns< / p >
< / div >
< div class = "card-body pt-3" >
< canvas id = "instanceGrowthChart" style = "height: 500px; width: 100%;" > < / canvas >
2025-07-16 15:12:25 +02:00
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-16 15:12:25 +02:00
2025-07-22 17:30:37 +02:00
<!-- SECONDARY CHARTS - Side by Side, Large -->
< div class = "row mb-4" >
< div class = "col-xl-6 mb-4" >
< div class = "card border-0 shadow-sm h-100" >
< div class = "card-header bg-white border-0 pb-0" >
< h5 class = "mb-1" > < i class = "bi bi-cash-stack text-success" > < / i > Net Financial Position< / h5 >
< p class = "small text-muted mb-0" > Cumulative profit/loss over time< / p >
2025-07-16 15:12:25 +02:00
< / div >
2025-07-22 17:30:37 +02:00
< div class = "card-body pt-3" >
< canvas id = "revenueChart" style = "height: 400px; width: 100%;" > < / canvas >
2025-07-16 15:12:25 +02:00
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< div class = "col-xl-6 mb-4" >
< div class = "card border-0 shadow-sm h-100" >
< div class = "card-header bg-white border-0 pb-0" >
< h5 class = "mb-1" > < i class = "bi bi-bar-chart text-warning" > < / i > Performance Comparison< / h5 >
< p class = "small text-muted mb-0" > ROI performance across growth scenarios< / p >
< / div >
< div class = "card-body pt-3" >
< canvas id = "cashFlowChart" style = "height: 400px; width: 100%;" > < / canvas >
2025-07-21 17:06:29 +02:00
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-21 17:06:29 +02:00
2025-07-23 15:45:40 +02:00
<!-- CSP REVENUE BREAKDOWN CHART - Full Width -->
2025-07-22 17:30:37 +02:00
< div class = "row mb-4" >
< div class = "col-12" >
< div class = "card border-0 shadow-sm" >
< div class = "card-header bg-white border-0 pb-0" >
2025-07-23 15:45:40 +02:00
< h5 class = "mb-1" > < i class = "bi bi-cash-stack text-success" > < / i > CSP Revenue Breakdown< / h5 >
< p class = "small text-muted mb-0" > Direct investment revenue breakdown: Service fees, core infrastructure sales, total CSP revenue, and Servala share over time< / p >
2025-07-22 17:30:37 +02:00
< / div >
< div class = "card-body pt-3" >
2025-07-23 15:45:40 +02:00
< canvas id = "cspRevenueChart" style = "height: 400px; width: 100%;" > < / canvas >
2025-07-16 15:12:25 +02:00
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
< / div >
2025-07-16 15:12:25 +02:00
2025-07-22 17:30:37 +02:00
<!-- DATA TABLE - Collapsible to Save Space -->
< div class = "row mb-4" >
< div class = "col-12" >
< div class = "accordion" id = "dataAccordion" >
< div class = "accordion-item border-0 shadow-sm" >
< h2 class = "accordion-header" id = "dataHeading" >
< button class = "accordion-button collapsed" type = "button" onclick = "toggleDataCollapse()" id = "dataToggleBtn" >
< i class = "bi bi-table me-2" > < / i > Detailed Financial Analysis
< / button >
< / h2 >
< div id = "dataCollapse" class = "accordion-collapse collapse" aria-labelledby = "dataHeading" data-bs-parent = "#dataAccordion" >
< div class = "accordion-body" >
2025-07-23 11:16:15 +02:00
<!-- Improved Scenario Performance Summary -->
< h6 class = "mb-3" > Investment Model Comparison by Scenario< / h6 >
2025-07-22 17:30:37 +02:00
< div class = "table-responsive mb-4" >
2025-07-23 11:16:15 +02:00
< table class = "table table-sm table-hover" id = "comparison-table" >
2025-07-22 17:30:37 +02:00
< thead class = "table-dark" >
< tr >
< th > Scenario< / th >
2025-07-23 11:16:15 +02:00
< th > Investment Model< / th >
2025-07-22 17:30:37 +02:00
< th > Final Scale< / th >
2025-07-23 11:16:15 +02:00
< th > Your Net Profit< / th >
< th > Total ROI< / th >
< th > Break-even Time< / th >
< th > Key Features< / th >
2025-07-22 17:30:37 +02:00
< / tr >
< / thead >
< tbody id = "comparison-tbody" >
<!-- Dynamic content -->
< / tbody >
< / table >
< / div >
2025-07-23 11:16:15 +02:00
<!-- Monthly Financial Flow -->
< h6 class = "mb-3" > Monthly Financial Breakdown< / h6 >
2025-07-23 11:42:22 +02:00
<!-- Breakdown Filter Controls -->
< div class = "row mb-3 breakdown-filters" >
< div class = "col-md-6" >
< label class = "form-label small fw-semibold mb-2" > Investment Models< / label >
< div class = "d-flex gap-3" >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "breakdown-direct-enabled" checked onchange = "updateMonthlyBreakdownFilters()" >
< label class = "form-check-label small fw-medium text-success" for = "breakdown-direct-enabled" >
Direct Investment
< / label >
< / div >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "breakdown-loan-enabled" checked onchange = "updateMonthlyBreakdownFilters()" >
< label class = "form-check-label small fw-medium text-warning" for = "breakdown-loan-enabled" >
Loan Model
< / label >
< / div >
< / div >
< / div >
< div class = "col-md-6" >
< label class = "form-label small fw-semibold mb-2" > Growth Scenarios< / label >
< div class = "d-flex gap-3" >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "breakdown-conservative-enabled" checked onchange = "updateMonthlyBreakdownFilters()" >
< label class = "form-check-label small fw-medium" for = "breakdown-conservative-enabled" >
< span class = "text-success" > •< / span > Conservative
< / label >
< / div >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "breakdown-moderate-enabled" checked onchange = "updateMonthlyBreakdownFilters()" >
< label class = "form-check-label small fw-medium" for = "breakdown-moderate-enabled" >
< span class = "text-warning" > •< / span > Moderate
< / label >
< / div >
< div class = "form-check" >
< input class = "form-check-input" type = "checkbox" id = "breakdown-aggressive-enabled" checked onchange = "updateMonthlyBreakdownFilters()" >
< label class = "form-check-label small fw-medium" for = "breakdown-aggressive-enabled" >
< span class = "text-danger" > •< / span > Aggressive
< / label >
< / div >
< / div >
< / div >
2025-07-23 11:16:15 +02:00
< / div >
< div class = "table-responsive" style = "max-height: 500px; overflow-y: auto;" >
2025-07-16 15:12:25 +02:00
< table class = "table table-sm table-striped" id = "monthly-table" >
< thead class = "table-dark sticky-top" >
< tr >
< th > Month< / th >
< th > Scenario< / th >
2025-07-23 11:16:15 +02:00
< th > Model< / th >
< th > Instances< / th >
2025-07-23 14:29:54 +02:00
< th > Service Revenue< / th >
< th > Core Revenue< / th >
< th > Total Revenue< / th >
2025-07-22 09:16:40 +02:00
< th > Your Share< / th >
2025-07-23 11:42:22 +02:00
< th > Servala Share< / th >
2025-07-23 11:16:15 +02:00
< th > Cumulative Net Position< / th >
2025-07-16 15:12:25 +02:00
< / tr >
< / thead >
< tbody id = "monthly-tbody" >
<!-- Dynamic content -->
< / tbody >
< / table >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
2025-07-22 17:30:37 +02:00
{% endblock %}