package financial import "math" // SensitivityAnalyzerImpl implements SensitivityAnalyzer interface type SensitivityAnalyzerImpl struct { config *Config npvCalc NPVCalculator } // NewSensitivityAnalyzer creates a new sensitivity analyzer func NewSensitivityAnalyzer(config *Config, npvCalc NPVCalculator) SensitivityAnalyzer { return &SensitivityAnalyzerImpl{ config: config, npvCalc: npvCalc, } } // AnalyzeSensitivity performs comprehensive sensitivity analysis on key parameters func (sa *SensitivityAnalyzerImpl) AnalyzeSensitivity( baseAnalysis *EconomicAnalysis, assumptions *EconomicAssumptions, ) []SensitivityScenario { var scenarios []SensitivityScenario // Sensitivity on discount rate for _, variation := range sa.config.SensitivityVariations { newDiscountRate := assumptions.DiscountRate * (1 + variation) newNPV := sa.npvCalc.CalculateNPV( baseAnalysis.CapexRequired, baseAnalysis.AnnualSavings-baseAnalysis.OpexPerYear, newDiscountRate, assumptions.ProjectLifeYears, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "discount_rate", BaseValue: assumptions.DiscountRate, VariationPct: variation, ImpactOnNPV: (newNPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((newNPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } // Sensitivity on CAPEX for _, variation := range sa.config.SensitivityVariations { newCapex := baseAnalysis.CapexRequired * (1 + variation) newNPV := sa.npvCalc.CalculateNPV( newCapex, baseAnalysis.AnnualSavings-baseAnalysis.OpexPerYear, assumptions.DiscountRate, assumptions.ProjectLifeYears, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "capex", BaseValue: baseAnalysis.CapexRequired, VariationPct: variation, ImpactOnNPV: (newNPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((newNPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } // Sensitivity on annual savings (revenue/pricing sensitivity) for _, variation := range sa.config.SensitivityVariations { newSavings := baseAnalysis.AnnualSavings * (1 + variation) newNPV := sa.npvCalc.CalculateNPV( baseAnalysis.CapexRequired, newSavings-baseAnalysis.OpexPerYear, assumptions.DiscountRate, assumptions.ProjectLifeYears, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "annual_savings", BaseValue: baseAnalysis.AnnualSavings, VariationPct: variation, ImpactOnNPV: (newNPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((newNPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } // Sensitivity on OPEX for _, variation := range sa.config.SensitivityVariations { newOpex := baseAnalysis.OpexPerYear * (1 + variation) newNPV := sa.npvCalc.CalculateNPV( baseAnalysis.CapexRequired, baseAnalysis.AnnualSavings-newOpex, assumptions.DiscountRate, assumptions.ProjectLifeYears, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "opex", BaseValue: baseAnalysis.OpexPerYear, VariationPct: variation, ImpactOnNPV: (newNPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((newNPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } // Sensitivity on project life for _, variation := range []float64{-0.2, 0.2} { // -20% and +20% variations newLife := int(float64(assumptions.ProjectLifeYears) * (1 + variation)) if newLife < 1 { newLife = 1 } newNPV := sa.npvCalc.CalculateNPV( baseAnalysis.CapexRequired, baseAnalysis.AnnualSavings-baseAnalysis.OpexPerYear, assumptions.DiscountRate, newLife, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "project_life", BaseValue: float64(assumptions.ProjectLifeYears), VariationPct: variation, ImpactOnNPV: (newNPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((newNPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } // Sensitivity on CO2 pricing (for carbon credit value) for _, variation := range []float64{-0.5, 0.5, 1.0} { // -50%, +50%, +100% variations co2Value := baseAnalysis.CO2AvoidedTonnes * 25 * (1 + variation) // Assume €25/tonne CO2 baseline co2NPV := sa.npvCalc.CalculateNPV( baseAnalysis.CapexRequired, baseAnalysis.AnnualSavings-baseAnalysis.OpexPerYear+co2Value, assumptions.DiscountRate, assumptions.ProjectLifeYears, ) scenarios = append(scenarios, SensitivityScenario{ VariableName: "co2_price", BaseValue: 25.0, // €25/tonne VariationPct: variation, ImpactOnNPV: (co2NPV - baseAnalysis.NPV) / math.Abs(baseAnalysis.NPV), RiskLevel: sa.calculateRiskLevel(math.Abs((co2NPV - baseAnalysis.NPV) / baseAnalysis.NPV)), }) } return scenarios } // calculateRiskLevel determines risk level based on NPV impact func (sa *SensitivityAnalyzerImpl) calculateRiskLevel(impact float64) string { switch { case impact < sa.config.RiskThresholds.Low: return "low" case impact < sa.config.RiskThresholds.Medium: return "medium" case impact < sa.config.RiskThresholds.High: return "high" default: return "critical" } }