mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Initialize git repository - Add comprehensive .gitignore for Go projects - Install golangci-lint v2.6.0 (latest v2) globally - Configure .golangci.yml with appropriate linters and formatters - Fix all formatting issues (gofmt) - Fix all errcheck issues (unchecked errors) - Adjust complexity threshold for validation functions - All checks passing: build, test, vet, lint
379 lines
13 KiB
Go
379 lines
13 KiB
Go
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
|
|
}
|