package match import ( "fmt" "math" "github.com/damirmukimov/city_resource_graph/models/transport" ) // EconomicCalculation represents the economic viability calculations for a match // Based on economic_calculation.json schema type EconomicCalculation struct { MatchID string `json:"match_id"` SourceResource ResourceFlowSummary `json:"source_resource"` TargetResource ResourceFlowSummary `json:"target_resource"` Calculations MatchCalculations `json:"calculations"` Assumptions CalculationAssumptions `json:"assumptions"` } // ResourceFlowSummary represents a simplified resource flow for matching type ResourceFlowSummary struct { ID string `json:"id"` Type string `json:"type"` Direction string `json:"direction"` Quantity float64 `json:"quantity"` Unit string `json:"unit"` CostPerUnit float64 `json:"cost_per_unit"` } // MatchCalculations contains all economic calculations for a match type MatchCalculations struct { AnnualSavings float64 `json:"annual_savings"` PaybackPeriodYears float64 `json:"payback_period_years"` NPV10Years float64 `json:"npv_10_years"` IRRPercent float64 `json:"irr_percent"` TransportationCosts TransportationEstimate `json:"transportation_costs"` CO2ReductionTonnes float64 `json:"co2_reduction_tonnes"` ImplementationComplexity string `json:"implementation_complexity"` RegulatoryRequirements []string `json:"regulatory_requirements"` } // TransportationEstimate represents transportation cost estimates for a match type TransportationEstimate struct { AnnualCost float64 `json:"annual_cost"` DistanceKm float64 `json:"distance_km"` Method string `json:"method"` Feasibility float64 `json:"feasibility_score"` // 0-1 scale } // CalculationAssumptions contains the assumptions used in calculations type CalculationAssumptions struct { DiscountRate float64 `json:"discount_rate"` OperatingHoursYear int `json:"operating_hours_year"` MaintenanceCostFactor float64 `json:"maintenance_cost_factor"` EnergyCostInflation float64 `json:"energy_cost_inflation"` } // MatchEconomicsParams contains parameters for match economic calculations type MatchEconomicsParams struct { SourceResource ResourceFlowSummary TargetResource ResourceFlowSummary DistanceKm float64 InitialInvestment float64 // One-time setup costs SymbiosisType transport.SymbiosisType Complexity string RiskLevel string AnnualQuantity float64 // Units exchanged per year UnitValue float64 // € per unit exchanged CO2ReductionFactor float64 // tonnes CO2 per unit exchanged } // DefaultCalculationAssumptions returns standard assumptions for calculations func DefaultCalculationAssumptions() CalculationAssumptions { return CalculationAssumptions{ DiscountRate: 0.08, // 8% discount rate OperatingHoursYear: 8000, // 8000 hours/year MaintenanceCostFactor: 0.05, // 5% of capital annually EnergyCostInflation: 0.02, // 2% annual inflation } } // CalculateMatchEconomics computes all economic metrics for a potential match func CalculateMatchEconomics(params MatchEconomicsParams, assumptions CalculationAssumptions) (*EconomicCalculation, error) { if params.SourceResource.ID == "" || params.TargetResource.ID == "" { return nil, fmt.Errorf("source and target resource IDs are required") } // Calculate annual savings (cost reduction for source, value creation for target) annualSavings := calculateAnnualSavings(params) // Calculate transportation costs using the exchange cost calculator transportCost, err := calculateTransportationCost(params) if err != nil { return nil, fmt.Errorf("failed to calculate transportation cost: %w", err) } // Calculate CO2 reduction co2Reduction := params.AnnualQuantity * params.CO2ReductionFactor // Calculate implementation complexity complexity := assessImplementationComplexity(params) // Identify regulatory requirements regulatoryReqs := identifyRegulatoryRequirements(params) // Calculate NPV over 10 years npv10Years := calculateMatchNPV(params, assumptions, annualSavings, transportCost.AnnualCost) // Calculate IRR irrPercent := calculateMatchIRR(params, assumptions, annualSavings, transportCost.AnnualCost) // Calculate payback period paybackYears := calculatePaybackPeriod(params.InitialInvestment, annualSavings-transportCost.AnnualCost) calculations := MatchCalculations{ AnnualSavings: annualSavings, PaybackPeriodYears: paybackYears, NPV10Years: npv10Years, IRRPercent: irrPercent * 100, // Convert to percentage TransportationCosts: *transportCost, CO2ReductionTonnes: co2Reduction, ImplementationComplexity: complexity, RegulatoryRequirements: regulatoryReqs, } result := &EconomicCalculation{ MatchID: generateMatchID(params.SourceResource.ID, params.TargetResource.ID), SourceResource: params.SourceResource, TargetResource: params.TargetResource, Calculations: calculations, Assumptions: assumptions, } return result, nil } // calculateAnnualSavings calculates the annual cost savings or value creation from the match func calculateAnnualSavings(params MatchEconomicsParams) float64 { // For waste-to-resource matches, savings = source disposal cost reduction + target value creation sourceSavings := params.SourceResource.CostPerUnit * params.AnnualQuantity // Avoid disposal costs targetValue := params.TargetResource.CostPerUnit * params.AnnualQuantity // Value of resource to target return sourceSavings + targetValue } // calculateTransportationCost uses the exchange cost calculator to determine transport costs func calculateTransportationCost(params MatchEconomicsParams) (*TransportationEstimate, error) { exchangeParams := transport.ExchangeParams{ DistanceKm: params.DistanceKm, Value: params.UnitValue * params.AnnualQuantity, // Annual exchange value Volume: params.AnnualQuantity, SymbiosisType: params.SymbiosisType, Complexity: params.Complexity, RiskLevel: params.RiskLevel, } cost, err := transport.CalculateExchangeCost(exchangeParams) if err != nil { return nil, err } estimate := &TransportationEstimate{ AnnualCost: cost.TotalAnnualCost, DistanceKm: params.DistanceKm, Method: determineTransportMethod(params.SymbiosisType), Feasibility: feasibilityToScore(cost.Feasibility), } return estimate, nil } // calculateMatchNPV calculates Net Present Value for the match over 10 years func calculateMatchNPV(params MatchEconomicsParams, assumptions CalculationAssumptions, annualSavings, annualTransportCost float64) float64 { npv := -params.InitialInvestment // Initial investment (negative cash flow) annualNetBenefit := annualSavings - annualTransportCost maintenanceCost := params.InitialInvestment * assumptions.MaintenanceCostFactor for year := 1; year <= 10; year++ { // Apply energy cost inflation to benefits inflatedBenefit := annualNetBenefit * math.Pow(1+assumptions.EnergyCostInflation, float64(year-1)) annualMaintenance := maintenanceCost * math.Pow(1+assumptions.EnergyCostInflation, float64(year-1)) cashFlow := inflatedBenefit - annualMaintenance npv += cashFlow / math.Pow(1+assumptions.DiscountRate, float64(year)) } return npv } // calculateMatchIRR calculates Internal Rate of Return for the match func calculateMatchIRR(params MatchEconomicsParams, assumptions CalculationAssumptions, annualSavings, annualTransportCost float64) float64 { // Create cash flow array cashFlows := make([]float64, 11) // Year 0 to 10 cashFlows[0] = -params.InitialInvestment annualNetBenefit := annualSavings - annualTransportCost maintenanceCost := params.InitialInvestment * assumptions.MaintenanceCostFactor for year := 1; year <= 10; year++ { inflatedBenefit := annualNetBenefit * math.Pow(1+assumptions.EnergyCostInflation, float64(year-1)) annualMaintenance := maintenanceCost * math.Pow(1+assumptions.EnergyCostInflation, float64(year-1)) cashFlows[year] = inflatedBenefit - annualMaintenance } // Use Newton's method to find IRR return calculateIRR(cashFlows) } // calculateIRR implements Newton's method to find IRR func calculateIRR(cashFlows []float64) float64 { irr := 0.1 // Initial guess: 10% maxIterations := 1000 tolerance := 0.00001 for i := 0; i < maxIterations; i++ { npv := 0.0 derivative := 0.0 for t, cf := range cashFlows { if t == 0 { npv += cf derivative -= float64(t+1) * cf / math.Pow(1+irr, float64(t+2)) } else { npv += cf / math.Pow(1+irr, float64(t)) derivative -= float64(t) * cf / math.Pow(1+irr, float64(t+1)) } } if math.Abs(npv) < tolerance { return irr } if derivative == 0 { return 0.0 // Cannot converge } irr = irr - npv/derivative } return irr // Return best approximation } // calculatePaybackPeriod calculates the payback period in years func calculatePaybackPeriod(initialInvestment, annualNetCashFlow float64) float64 { if annualNetCashFlow <= 0 { return 999 // Never pays back } years := initialInvestment / annualNetCashFlow // Cap at 10 years for practicality if years > 10 { return 999 } return years } // assessImplementationComplexity determines implementation complexity func assessImplementationComplexity(params MatchEconomicsParams) string { score := 0.0 // Distance factor if params.DistanceKm > 25 { score += 2.0 } else if params.DistanceKm > 5 { score += 1.0 } // Resource type complexity switch params.SymbiosisType { case transport.SymbiosisWasteToResource, transport.SymbiosisMaterialRecycling: score += 1.5 // Complex regulatory requirements case transport.SymbiosisEnergyCascading, transport.SymbiosisUtilitySharing: score += 1.0 // Moderate technical complexity } // Investment size if params.InitialInvestment > 50000 { score += 1.0 } if score >= 3.0 { return "high" } else if score >= 1.5 { return "medium" } return "low" } // identifyRegulatoryRequirements determines required permits and approvals func identifyRegulatoryRequirements(params MatchEconomicsParams) []string { requirements := []string{} // Waste and material handling always requires permits if params.SymbiosisType == transport.SymbiosisWasteToResource || params.SymbiosisType == transport.SymbiosisMaterialRecycling { requirements = append(requirements, "waste_disposal_permit") requirements = append(requirements, "environmental_impact_assessment") } // Energy transfers may require grid connection permits if params.SymbiosisType == transport.SymbiosisEnergyCascading || params.SymbiosisType == transport.SymbiosisUtilitySharing { requirements = append(requirements, "energy_distribution_license") } // Long distance transport may require additional permits if params.DistanceKm > 50 { requirements = append(requirements, "transport_license") } // High value exchanges may require insurance if params.UnitValue*params.AnnualQuantity > 100000 { requirements = append(requirements, "liability_insurance") } return requirements } // determineTransportMethod suggests the appropriate transport method func determineTransportMethod(symbiosisType transport.SymbiosisType) string { switch symbiosisType { case transport.SymbiosisWasteToResource, transport.SymbiosisMaterialRecycling: return "truck" case transport.SymbiosisEnergyCascading, transport.SymbiosisUtilitySharing: if symbiosisType == transport.SymbiosisEnergyCascading { return "heat_pipe" } return "pipeline" case transport.SymbiosisDataSharing, transport.SymbiosisIoTNetwork, transport.SymbiosisSoftwareLicenses: return "network" default: return "truck" } } // feasibilityToScore converts feasibility string to numeric score func feasibilityToScore(feasibility string) float64 { switch feasibility { case "high": return 0.9 case "medium": return 0.6 case "low": return 0.3 default: return 0.5 } } // generateMatchID creates a unique match identifier func generateMatchID(sourceID, targetID string) string { return fmt.Sprintf("match_%s_%s", sourceID, targetID) } // ValidateEconomicCalculation validates the calculation results func ValidateEconomicCalculation(calc *EconomicCalculation) error { if calc.MatchID == "" { return fmt.Errorf("match ID is required") } if calc.Calculations.AnnualSavings < 0 { return fmt.Errorf("annual savings cannot be negative") } if calc.Calculations.PaybackPeriodYears < 0 { return fmt.Errorf("payback period cannot be negative") } if calc.Calculations.IRRPercent < -100 || calc.Calculations.IRRPercent > 1000 { return fmt.Errorf("IRR percentage out of reasonable range") } return nil }