turash/bugulma/backend/internal/financial/calculator.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

654 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package financial
import (
"fmt"
"math"
"time"
)
// FinancialCalculator implements the Calculator interface with proper separation of concerns
type FinancialCalculator struct {
config *Config
npvCalc NPVCalculator
irrCalc IRRCalculator
paybackCalc PaybackCalculator
sensitivityCalc SensitivityAnalyzer
riskAssessor RiskAssessor
co2Calc CO2Calculator
capexEstimator CapexEstimator
transportCalc TransportCostCalculator
}
// NewFinancialCalculator creates a new financial calculator with all dependencies
func NewFinancialCalculator(
config *Config,
npvCalc NPVCalculator,
irrCalc IRRCalculator,
paybackCalc PaybackCalculator,
sensitivityCalc SensitivityAnalyzer,
riskAssessor RiskAssessor,
co2Calc CO2Calculator,
capexEstimator CapexEstimator,
transportCalc TransportCostCalculator,
) *FinancialCalculator {
return &FinancialCalculator{
config: config,
npvCalc: npvCalc,
irrCalc: irrCalc,
paybackCalc: paybackCalc,
sensitivityCalc: sensitivityCalc,
riskAssessor: riskAssessor,
co2Calc: co2Calc,
capexEstimator: capexEstimator,
transportCalc: transportCalc,
}
}
// Calculate performs economic analysis based on the specified type
func (fc *FinancialCalculator) Calculate(
analysisType AnalysisType,
data *ResourceFlowData,
assumptions *EconomicAssumptions,
) (interface{}, error) {
// Ensure assumptions have defaults
if assumptions == nil {
assumptions = fc.getDefaultAssumptions()
}
// Perform basic economic analysis
basicAnalysis, err := fc.calculateBasicAnalysis(data, assumptions)
if err != nil {
return nil, fmt.Errorf("failed to calculate basic analysis: %w", err)
}
// Add analysis metadata
basicAnalysis.AnalysisType = analysisType
basicAnalysis.AnalysisDate = time.Now()
basicAnalysis.Version = "1.0.0"
switch analysisType {
case AnalysisTypeBasic:
return basicAnalysis, nil
case AnalysisTypeAdvanced:
return fc.calculateAdvancedAnalysis(basicAnalysis, data, assumptions)
case AnalysisTypeSensitivity:
return fc.calculateSensitivityAnalysis(basicAnalysis, assumptions)
default:
return nil, fmt.Errorf("unsupported analysis type: %s", analysisType)
}
}
// calculateBasicAnalysis performs the core economic calculations
func (fc *FinancialCalculator) calculateBasicAnalysis(
data *ResourceFlowData,
assumptions *EconomicAssumptions,
) (*EconomicAnalysis, error) {
// Calculate transport cost per unit
transportCostPerUnit := fc.transportCalc.CalculateTransportCost(data.ResourceType, data.DistanceKm)
// Annual savings calculation
// savings = (buyer_cost - seller_cost - transport) × annual_volume
unitSavings := data.CostIn - data.CostOut - transportCostPerUnit
annualSavings := unitSavings * data.AnnualVolume
// Estimate CAPEX
capex := fc.capexEstimator.EstimateCapex(data.ResourceType, data.DistanceKm, data.AnnualVolume)
// Estimate OPEX
opex := capex * fc.config.OpexPctOfCapex
// Calculate net annual cash flow
netAnnualCashFlow := annualSavings - opex
// Calculate NPV
npv := fc.npvCalc.CalculateNPV(capex, netAnnualCashFlow, assumptions.DiscountRate, assumptions.ProjectLifeYears)
// Calculate IRR
irr := fc.irrCalc.CalculateIRR(capex, netAnnualCashFlow, assumptions.ProjectLifeYears)
// Calculate simple payback period
payback := fc.paybackCalc.CalculatePaybackPeriod(capex, netAnnualCashFlow)
// Calculate CO₂ avoided
co2Avoided := fc.co2Calc.CalculateCO2Reduction(data.ResourceType, data.AnnualVolume)
// Determine viability
isViable := fc.assessViability(npv, irr, payback, assumptions)
// Calculate confidence score based on data completeness and risk
confidenceScore := fc.calculateConfidenceScore(data, assumptions)
return &EconomicAnalysis{
NPV: npv,
IRR: irr,
PaybackYears: payback,
AnnualSavings: annualSavings,
CapexRequired: capex,
OpexPerYear: opex,
CO2AvoidedTonnes: co2Avoided,
IsViable: isViable,
ConfidenceScore: confidenceScore,
}, nil
}
// calculateAdvancedAnalysis performs comprehensive advanced economic analysis
func (fc *FinancialCalculator) calculateAdvancedAnalysis(
basic *EconomicAnalysis,
data *ResourceFlowData,
assumptions *EconomicAssumptions,
) (*AdvancedEconomicAnalysis, error) {
// Perform sensitivity analysis
sensitivityScenarios := fc.sensitivityCalc.AnalyzeSensitivity(basic, assumptions)
// Perform risk assessment
riskAssessment := fc.riskAssessor.AssessRisk(data)
// Calculate risk-adjusted metrics
riskAdjustedNPV := fc.calculateRiskAdjustedNPV(basic.NPV, riskAssessment)
riskAdjustedIRR := fc.calculateRiskAdjustedIRR(basic.IRR, riskAssessment)
// Assess implementation complexity
implementationComplexity := fc.assessImplementationComplexity(data)
// Identify regulatory requirements
regulatoryRequirements := fc.identifyRegulatoryRequirements(data.ResourceType)
// Calculate detailed CO2 reduction breakdown
co2Breakdown := fc.calculateCO2ReductionBreakdown(data.ResourceType, data.AnnualVolume, data.DistanceKm)
// Generate mitigation strategies
mitigationStrategies := fc.generateMitigationStrategies(riskAssessment)
// Generate optimization recommendations
recommendedActions := fc.generateOptimizationRecommendations(basic, sensitivityScenarios, riskAssessment)
// Adjust confidence score based on risk and sensitivity
confidenceScore := fc.calculateAdvancedConfidenceScore(basic.ConfidenceScore, sensitivityScenarios, riskAssessment)
return &AdvancedEconomicAnalysis{
EconomicAnalysis: EconomicAnalysis{
NPV: basic.NPV,
IRR: basic.IRR,
PaybackYears: basic.PaybackYears,
AnnualSavings: basic.AnnualSavings,
CapexRequired: basic.CapexRequired,
OpexPerYear: basic.OpexPerYear,
CO2AvoidedTonnes: basic.CO2AvoidedTonnes,
IsViable: basic.IsViable,
ConfidenceScore: confidenceScore,
AnalysisDate: time.Now(),
AnalysisType: AnalysisTypeAdvanced,
Version: "2.0.0",
},
SensitivityScenarios: sensitivityScenarios,
RiskAdjustedNPV: riskAdjustedNPV,
RiskAdjustedIRR: riskAdjustedIRR,
ImplementationComplexity: implementationComplexity,
RegulatoryRequirements: regulatoryRequirements,
CO2ReductionBreakdown: co2Breakdown,
RiskProfile: *riskAssessment,
MitigationStrategies: mitigationStrategies,
RecommendedActions: recommendedActions,
}, nil
}
// calculateSensitivityAnalysis performs detailed sensitivity analysis
func (fc *FinancialCalculator) calculateSensitivityAnalysis(
basic *EconomicAnalysis,
assumptions *EconomicAssumptions,
) (*EconomicAnalysis, error) {
// Start with basic analysis
sensitivity := *basic
sensitivity.AnalysisType = AnalysisTypeSensitivity
// Perform sensitivity analysis
scenarios := fc.sensitivityCalc.AnalyzeSensitivity(basic, assumptions)
// Calculate confidence based on sensitivity results
sensitivity.ConfidenceScore = fc.calculateSensitivityConfidence(scenarios)
return &sensitivity, nil
}
// assessViability determines if the investment is economically viable
func (fc *FinancialCalculator) assessViability(npv, irr, payback float64, assumptions *EconomicAssumptions) bool {
// NPV must be positive
if npv <= 0 {
return false
}
// IRR must be greater than discount rate
if irr <= assumptions.DiscountRate*100 {
return false
}
// Payback period must be within project life
if payback >= float64(assumptions.ProjectLifeYears) {
return false
}
return true
}
// calculateConfidenceScore calculates confidence in the analysis based on data quality
func (fc *FinancialCalculator) calculateConfidenceScore(data *ResourceFlowData, assumptions *EconomicAssumptions) float64 {
confidence := 1.0
// Reduce confidence if distance is very long (higher uncertainty)
if data.DistanceKm > 50 {
confidence *= 0.9
}
// Reduce confidence if volume is very low (higher per-unit cost uncertainty)
if data.AnnualVolume < 100 {
confidence *= 0.95
}
// Reduce confidence if project life is very long (higher uncertainty)
if assumptions.ProjectLifeYears > 20 {
confidence *= 0.9
}
return confidence
}
// adjustConfidenceForRisk adjusts confidence based on risk assessment
func (fc *FinancialCalculator) adjustConfidenceForRisk(confidence float64, risk *RiskAssessment) float64 {
// Reduce confidence based on overall risk level
switch risk.RiskLevel {
case "low":
return confidence * 1.0
case "medium":
return confidence * 0.9
case "high":
return confidence * 0.7
case "critical":
return confidence * 0.5
default:
return confidence * 0.8
}
}
// calculateSensitivityConfidence calculates confidence based on sensitivity analysis
func (fc *FinancialCalculator) calculateSensitivityConfidence(scenarios []SensitivityScenario) float64 {
// Analyze how NPV changes with parameter variations
var totalVariation float64
var variationCount int
for _, scenario := range scenarios {
if scenario.VariableName == "discount_rate" || scenario.VariableName == "capex" {
totalVariation += scenario.ImpactOnNPV
variationCount++
}
}
if variationCount == 0 {
return 0.8 // Default confidence
}
avgVariation := totalVariation / float64(variationCount)
// Lower confidence if NPV is highly sensitive to key parameters
if avgVariation > 0.3 { // More than 30% variation
return 0.6
} else if avgVariation > 0.1 { // More than 10% variation
return 0.8
}
return 0.9
}
// calculateRiskAdjustedNPV adjusts NPV for risk
func (fc *FinancialCalculator) calculateRiskAdjustedNPV(npv float64, risk *RiskAssessment) float64 {
riskAdjustment := 1.0
switch risk.RiskLevel {
case "low":
riskAdjustment = 0.95
case "medium":
riskAdjustment = 0.85
case "high":
riskAdjustment = 0.7
case "critical":
riskAdjustment = 0.5
}
return npv * riskAdjustment
}
// calculateRiskAdjustedIRR adjusts IRR for risk
func (fc *FinancialCalculator) calculateRiskAdjustedIRR(irr float64, risk *RiskAssessment) float64 {
riskAdjustment := 0.0
switch risk.RiskLevel {
case "low":
riskAdjustment = 0.02 // +2%
case "medium":
riskAdjustment = -0.01 // -1%
case "high":
riskAdjustment = -0.05 // -5%
case "critical":
riskAdjustment = -0.10 // -10%
}
return irr - riskAdjustment
}
// assessImplementationComplexity evaluates how complex it would be to implement this match
func (fc *FinancialCalculator) assessImplementationComplexity(data *ResourceFlowData) ImplementationComplexity {
complexity := ImplementationComplexity{
Score: 1.0, // Base score
TechnicalChallenges: []string{},
ResourceRequirements: []string{},
TimelineEstimate: "3-6 months",
}
// Distance affects complexity
if data.DistanceKm > 100 {
complexity.Score += 0.3
complexity.TechnicalChallenges = append(complexity.TechnicalChallenges, "Long-distance transport logistics")
}
if data.DistanceKm > 500 {
complexity.Score += 0.5
complexity.TechnicalChallenges = append(complexity.TechnicalChallenges, "International/cross-border requirements")
complexity.TimelineEstimate = "6-12 months"
}
// Volume affects scale
if data.AnnualVolume > 10000 {
complexity.Score += 0.2
complexity.ResourceRequirements = append(complexity.ResourceRequirements, "Large-scale infrastructure investment")
}
// Resource type affects technical requirements
switch data.ResourceType {
case "heat", "steam":
complexity.TechnicalChallenges = append(complexity.TechnicalChallenges, "Thermal energy transfer systems")
case "wastewater":
complexity.TechnicalChallenges = append(complexity.TechnicalChallenges, "Water quality monitoring and treatment")
case "biowaste":
complexity.TechnicalChallenges = append(complexity.TechnicalChallenges, "Organic waste processing equipment")
}
return complexity
}
// identifyRegulatoryRequirements identifies applicable regulations for the resource type
func (fc *FinancialCalculator) identifyRegulatoryRequirements(resourceType string) []RegulatoryRequirement {
var requirements []RegulatoryRequirement
switch resourceType {
case "heat", "steam":
requirements = append(requirements, RegulatoryRequirement{
Type: "Energy Trading License",
Description: "Required for commercial energy exchange",
EstimatedCost: 50000,
TimeToObtain: "3-6 months",
})
case "wastewater":
requirements = append(requirements, RegulatoryRequirement{
Type: "Environmental Permit",
Description: "Wastewater discharge and quality standards",
EstimatedCost: 25000,
TimeToObtain: "2-4 months",
})
case "biowaste":
requirements = append(requirements, RegulatoryRequirement{
Type: "Waste Management License",
Description: "Organic waste processing authorization",
EstimatedCost: 15000,
TimeToObtain: "1-3 months",
})
}
// Common requirements
requirements = append(requirements, RegulatoryRequirement{
Type: "Contract Registration",
Description: "Legal contract registration with local authorities",
EstimatedCost: 5000,
TimeToObtain: "1 month",
})
return requirements
}
// calculateCO2ReductionBreakdown provides detailed CO2 reduction analysis
func (fc *FinancialCalculator) calculateCO2ReductionBreakdown(resourceType string, annualVolume, distanceKm float64) CO2ReductionBreakdown {
breakdown := CO2ReductionBreakdown{
TotalTonnes: 0,
Categories: []CO2Category{},
}
switch resourceType {
case "heat", "steam":
// Avoided grid electricity for heating
gridAvoided := annualVolume * fc.config.GridEmissionFactor * fc.config.HeatEfficiency
breakdown.Categories = append(breakdown.Categories, CO2Category{
Name: "Grid Electricity Avoidance",
Tonnes: gridAvoided,
Percentage: 0,
})
// Transport emissions (diesel trucks vs pipeline)
transportAvoided := fc.calculateTransportEmissionAvoidance(resourceType, annualVolume, distanceKm)
breakdown.Categories = append(breakdown.Categories, CO2Category{
Name: "Transport Emission Reduction",
Tonnes: transportAvoided,
Percentage: 0,
})
case "wastewater":
// Water treatment energy savings
treatmentEnergy := annualVolume * fc.config.WaterTreatmentKwh / 1000 // Convert to MWh
treatmentAvoided := treatmentEnergy * fc.config.GridEmissionFactor
breakdown.Categories = append(breakdown.Categories, CO2Category{
Name: "Water Treatment Energy Savings",
Tonnes: treatmentAvoided,
Percentage: 0,
})
case "biowaste":
// Landfill methane avoidance
methaneAvoided := annualVolume * fc.config.WasteDiversionCo2
breakdown.Categories = append(breakdown.Categories, CO2Category{
Name: "Landfill Methane Reduction",
Tonnes: methaneAvoided,
Percentage: 0,
})
// Compost benefits (carbon sequestration)
sequestration := annualVolume * fc.config.CompostCarbonSeq // Assume 0.1 t CO2/tonne compost
breakdown.Categories = append(breakdown.Categories, CO2Category{
Name: "Carbon Sequestration",
Tonnes: sequestration,
Percentage: 0,
})
}
// Calculate total and percentages
for i := range breakdown.Categories {
breakdown.TotalTonnes += breakdown.Categories[i].Tonnes
}
for i := range breakdown.Categories {
if breakdown.TotalTonnes > 0 {
breakdown.Categories[i].Percentage = breakdown.Categories[i].Tonnes / breakdown.TotalTonnes * 100
}
}
return breakdown
}
// calculateTransportEmissionAvoidance calculates CO2 savings from reduced transport
func (fc *FinancialCalculator) calculateTransportEmissionAvoidance(resourceType string, annualVolume, distanceKm float64) float64 {
// Assume traditional transport would be by truck
// Estimate truck trips needed for traditional transport
var truckEfficiency float64
switch resourceType {
case "heat", "steam":
truckEfficiency = 0.1 // 10% of energy lost in transport
default:
truckEfficiency = 0.05 // 5% loss for other resources
}
// CO2 per km for truck transport (assume diesel truck: 0.0005 tonnes CO2 per km)
truckEmissionFactor := 0.0005
traditionalTransport := annualVolume * truckEfficiency * distanceKm * truckEmissionFactor * 365 // Daily transport
return traditionalTransport
}
// generateMitigationStrategies provides risk mitigation recommendations
func (fc *FinancialCalculator) generateMitigationStrategies(risk *RiskAssessment) []string {
strategies := []string{}
switch risk.RiskLevel {
case "high", "critical":
strategies = append(strategies,
"Implement comprehensive monitoring and control systems",
"Establish contingency budgets (15-20% of CAPEX)",
"Develop detailed risk management plan with regular reviews",
"Consider phased implementation to reduce exposure",
)
case "medium":
strategies = append(strategies,
"Regular performance monitoring and reporting",
"Maintain contingency reserves",
"Establish clear success metrics and KPIs",
)
default:
strategies = append(strategies,
"Standard project management practices",
"Regular progress reviews",
)
}
// Add resource-type specific strategies
if risk.PrimaryRisks != nil {
for _, riskFactor := range risk.PrimaryRisks {
switch riskFactor {
case "technical_failure":
strategies = append(strategies, "Redundant system design with backup capacity")
case "regulatory_changes":
strategies = append(strategies, "Legal consultation and regulatory monitoring service")
case "market_volatility":
strategies = append(strategies, "Flexible contract terms with price adjustment clauses")
}
}
}
return strategies
}
// generateOptimizationRecommendations provides actionable optimization suggestions
func (fc *FinancialCalculator) generateOptimizationRecommendations(
basic *EconomicAnalysis,
sensitivityScenarios []SensitivityScenario,
risk *RiskAssessment,
) []RecommendedAction {
recommendations := []RecommendedAction{}
// NPV optimization
if basic.NPV > 0 {
recommendations = append(recommendations, RecommendedAction{
Priority: "high",
Action: "Maximize NPV through operational efficiency improvements",
ExpectedImpact: "5-15% NPV increase",
Timeframe: "6-12 months",
})
}
// Payback period optimization
if basic.PaybackYears > 5 {
recommendations = append(recommendations, RecommendedAction{
Priority: "high",
Action: "Accelerate payback through phased implementation",
ExpectedImpact: "20-30% faster payback",
Timeframe: "3-6 months",
})
}
// Risk mitigation
if risk.RiskLevel == "high" || risk.RiskLevel == "critical" {
recommendations = append(recommendations, RecommendedAction{
Priority: "high",
Action: "Implement comprehensive risk mitigation plan",
ExpectedImpact: "30-50% risk reduction",
Timeframe: "1-3 months",
})
}
// Cost reduction based on sensitivity analysis
for _, scenario := range sensitivityScenarios {
if scenario.VariableName == "capex" && scenario.RiskLevel == "high" {
recommendations = append(recommendations, RecommendedAction{
Priority: "medium",
Action: "Explore alternative technology options to reduce CAPEX",
ExpectedImpact: "10-25% CAPEX reduction",
Timeframe: "2-4 months",
})
}
}
return recommendations
}
// calculateAdvancedConfidenceScore provides more sophisticated confidence calculation
func (fc *FinancialCalculator) calculateAdvancedConfidenceScore(
baseConfidence float64,
sensitivityScenarios []SensitivityScenario,
risk *RiskAssessment,
) float64 {
confidence := baseConfidence
// Reduce confidence based on sensitivity volatility
var totalSensitivityImpact float64
var highImpactCount int
for _, scenario := range sensitivityScenarios {
totalSensitivityImpact += math.Abs(scenario.ImpactOnNPV)
if scenario.RiskLevel == "high" || scenario.RiskLevel == "critical" {
highImpactCount++
}
}
if len(sensitivityScenarios) > 0 {
avgSensitivityImpact := totalSensitivityImpact / float64(len(sensitivityScenarios))
confidence *= (1 - avgSensitivityImpact*0.5) // Reduce confidence by sensitivity impact
}
// Further reduce confidence based on high-impact scenarios
if highImpactCount > 0 {
confidence *= (1 - float64(highImpactCount)/float64(len(sensitivityScenarios))*0.3)
}
// Adjust for risk level
switch risk.RiskLevel {
case "medium":
confidence *= 0.9
case "high":
confidence *= 0.7
case "critical":
confidence *= 0.5
}
// Ensure confidence doesn't go below 0.1
if confidence < 0.1 {
confidence = 0.1
}
return confidence
}
// getDefaultAssumptions returns default economic assumptions
func (fc *FinancialCalculator) getDefaultAssumptions() *EconomicAssumptions {
return &EconomicAssumptions{
DiscountRate: fc.config.DefaultDiscountRate,
ProjectLifeYears: fc.config.DefaultProjectLife,
OperatingHoursYear: 8760, // 24/7 operation
}
}