show both models at the same time

This commit is contained in:
Tobias Brunner 2025-07-23 11:16:15 +02:00
parent aa57082a1b
commit 4746cfac25
Signed by: tobru
SSH key fingerprint: SHA256:kOXg1R6c11XW3/Pt9dbLdQvOJGFAy+B2K6v6PtRWBGQ
6 changed files with 488 additions and 326 deletions

View file

@ -175,98 +175,98 @@ class ChartManager {
aggressive: '#dc3545'
};
const modelColors = {
direct: { border: '', background: '80' },
loan: { border: '', background: '40' }
};
// Get month labels
const maxMonths = Math.max(...scenarios.map(s => this.calculator.monthlyData[s].length));
const monthLabels = Array.from({ length: maxMonths }, (_, i) => `M${i + 1}`);
// Update ROI Progression Chart
// Update ROI Progression Chart with both models
this.charts.roiProgression.data.labels = monthLabels;
this.charts.roiProgression.data.datasets = scenarios.filter(s => this.calculator.results[s]).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,
pointBackgroundColor: this.calculator.monthlyData[scenario].map(d =>
d.roiPercent >= 0 ? colors[scenario] : '#dc3545'
)
}));
this.charts.roiProgression.data.datasets = scenarios.filter(s => this.calculator.results[s]).map(scenario => {
const scenarioBase = scenario.replace('_direct', '').replace('_loan', '');
const model = scenario.includes('_loan') ? 'loan' : 'direct';
const isDirect = model === 'direct';
const scenarioName = this.calculator.scenarios[scenarioBase]?.name || scenarioBase;
return {
label: `${scenarioName} (${model.charAt(0).toUpperCase() + model.slice(1)})`,
data: this.calculator.monthlyData[scenario].map(d => d.roiPercent),
borderColor: colors[scenarioBase],
backgroundColor: colors[scenarioBase] + (isDirect ? '30' : '15'),
borderDash: isDirect ? [] : [5, 5],
borderWidth: isDirect ? 3 : 2,
tension: 0.4,
pointBackgroundColor: this.calculator.monthlyData[scenario].map(d =>
d.roiPercent >= 0 ? colors[scenarioBase] : '#dc3545'
)
};
});
this.charts.roiProgression.update();
// Update Net Position Chart (Break-Even Analysis)
// Update Net Position Chart (Break-Even Analysis) with both models
this.charts.netPosition.data.labels = monthLabels;
this.charts.netPosition.data.datasets = scenarios.filter(s => this.calculator.results[s]).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,
fill: {
target: 'origin',
above: colors[scenario] + '10',
below: '#dc354510'
}
}));
this.charts.netPosition.data.datasets = scenarios.filter(s => this.calculator.results[s]).map(scenario => {
const scenarioBase = scenario.replace('_direct', '').replace('_loan', '');
const model = scenario.includes('_loan') ? 'loan' : 'direct';
const isDirect = model === 'direct';
const scenarioName = this.calculator.scenarios[scenarioBase]?.name || scenarioBase;
return {
label: `${scenarioName} (${model.charAt(0).toUpperCase() + model.slice(1)}) Net Position`,
data: this.calculator.monthlyData[scenario].map(d => d.netPosition),
borderColor: colors[scenarioBase],
backgroundColor: colors[scenarioBase] + (isDirect ? '30' : '15'),
borderDash: isDirect ? [] : [5, 5],
borderWidth: isDirect ? 3 : 2,
tension: 0.4,
fill: {
target: 'origin',
above: colors[scenarioBase] + (isDirect ? '20' : '10'),
below: '#dc354510'
}
};
});
this.charts.netPosition.update();
// Update Model Comparison Chart - Side-by-side comparison of both models
// Update Model Comparison Chart - Direct comparison of both models
const inputs = this.calculator.getInputValues();
// Calculate loan model net profit (fixed return regardless of scenario)
const loanMonthlyPayment = this.calculateLoanPayment(inputs.investmentAmount, inputs.loanInterestRate, inputs.timeframe);
const loanTotalPayments = loanMonthlyPayment * (inputs.timeframe * 12);
const loanNetProfit = loanTotalPayments - inputs.investmentAmount;
// Get unique scenario names (without model suffix)
const baseScenarios = ['conservative', 'moderate', 'aggressive'].filter(s =>
this.calculator.scenarios[s].enabled
);
const comparisonLabels = baseScenarios.map(s => this.calculator.scenarios[s].name);
// Prepare scenario-based comparison
const enabledScenarios = scenarios.filter(s => this.calculator.scenarios[s].enabled);
const comparisonLabels = enabledScenarios.map(s => this.calculator.scenarios[s].name);
// Loan model data (same profit for all scenarios since it's fixed)
const loanModelData = enabledScenarios.map(() => loanNetProfit);
// Direct investment data (varies by scenario performance)
const directInvestmentData = enabledScenarios.map(scenario => {
const scenarioResult = this.calculator.results[scenario];
if (!scenarioResult) return 0;
return scenarioResult.cspRevenue - inputs.investmentAmount;
// Get net profit data for both models
const directInvestmentData = baseScenarios.map(scenario => {
const scenarioResult = this.calculator.results[scenario + '_direct'];
return scenarioResult ? scenarioResult.netPosition : 0;
});
// Performance bonus data (shows the additional revenue from performance bonuses)
const performanceBonusData = enabledScenarios.map(scenario => {
const monthlyData = this.calculator.monthlyData[scenario] || [];
const totalPerformanceBonus = monthlyData.reduce((sum, month) => {
// Calculate the bonus revenue (difference from standard share)
const standardRevenue = month.monthlyRevenue * inputs.servalaShare;
const actualServalaRevenue = month.servalaRevenue;
const bonusAmount = Math.max(0, standardRevenue - actualServalaRevenue); // CSP gets this as bonus
return sum + bonusAmount;
}, 0);
return totalPerformanceBonus;
const loanInvestmentData = baseScenarios.map(scenario => {
const scenarioResult = this.calculator.results[scenario + '_loan'];
return scenarioResult ? scenarioResult.netPosition : 0;
});
this.charts.modelComparison.data.labels = comparisonLabels;
this.charts.modelComparison.data.datasets = [
{
label: `Loan Model (${(inputs.loanInterestRate * 100).toFixed(1)}% fixed return)`,
data: loanModelData,
backgroundColor: '#ffc107',
label: 'Direct Investment Model',
data: directInvestmentData,
backgroundColor: baseScenarios.map(scenario => colors[scenario] + '80'),
borderColor: baseScenarios.map(scenario => colors[scenario]),
borderWidth: 2
},
{
label: `Loan Model (${(inputs.loanInterestRate * 100).toFixed(1)}% fixed rate)`,
data: loanInvestmentData,
backgroundColor: '#ffc10780',
borderColor: '#e0a800',
borderWidth: 2
},
{
label: 'Direct Investment (base return)',
data: directInvestmentData,
backgroundColor: enabledScenarios.map(scenario => colors[scenario] + '80'),
borderColor: enabledScenarios.map(scenario => colors[scenario]),
borderWidth: 2
},
{
label: 'Performance Bonus Impact',
data: performanceBonusData,
backgroundColor: enabledScenarios.map(scenario => colors[scenario] + '40'),
borderColor: enabledScenarios.map(scenario => colors[scenario]),
borderWidth: 2,
borderDash: [5, 5]
}
];
@ -275,29 +275,30 @@ class ChartManager {
// Update Performance Comparison Chart (ROI comparison for both models)
this.charts.performance.data.labels = comparisonLabels;
// Calculate ROI for loan model (same for all scenarios)
const loanROI = (loanNetProfit / inputs.investmentAmount) * 100;
const loanROIData = enabledScenarios.map(() => loanROI);
// Get ROI data for both models
const directROIData = baseScenarios.map(scenario => {
const result = this.calculator.results[scenario + '_direct'];
return result ? result.roi : 0;
});
// Get ROI for direct investment (varies by scenario)
const directROIData = enabledScenarios.map(scenario => {
const result = this.calculator.results[scenario];
const loanROIData = baseScenarios.map(scenario => {
const result = this.calculator.results[scenario + '_loan'];
return result ? result.roi : 0;
});
this.charts.performance.data.datasets = [
{
label: `Loan Model ROI (${(inputs.loanInterestRate * 100).toFixed(1)}% fixed)`,
data: loanROIData,
backgroundColor: '#ffc107',
borderColor: '#e0a800',
label: 'Direct Investment ROI',
data: directROIData,
backgroundColor: baseScenarios.map(scenario => colors[scenario] + '80'),
borderColor: baseScenarios.map(scenario => colors[scenario]),
borderWidth: 2
},
{
label: 'Direct Investment ROI',
data: directROIData,
backgroundColor: enabledScenarios.map(scenario => colors[scenario] + '80'),
borderColor: enabledScenarios.map(scenario => colors[scenario]),
label: `Loan Model ROI (${(inputs.loanInterestRate * 100).toFixed(1)}% fixed)`,
data: loanROIData,
backgroundColor: '#ffc10780',
borderColor: '#e0a800',
borderWidth: 2
}
];