package match import ( "testing" "github.com/damirmukimov/city_resource_graph/models/transport" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCalculateMatchEconomics(t *testing.T) { params := MatchEconomicsParams{ SourceResource: ResourceFlowSummary{ ID: "source_001", Type: "waste_heat", Direction: "output", Quantity: 1000, Unit: "MWh", CostPerUnit: 20, // €20/MWh disposal cost avoided }, TargetResource: ResourceFlowSummary{ ID: "target_001", Type: "process_heat", Direction: "input", Quantity: 1000, Unit: "MWh", CostPerUnit: 50, // €50/MWh value to target }, DistanceKm: 5.0, InitialInvestment: 25000, // €25k setup cost SymbiosisType: transport.SymbiosisEnergyCascading, Complexity: "medium", RiskLevel: "low", AnnualQuantity: 8000, // 8000 MWh/year UnitValue: 35, // €35/MWh exchange value CO2ReductionFactor: 0.0005, // 0.0005 tonnes CO2/MWh } assumptions := DefaultCalculationAssumptions() result, err := CalculateMatchEconomics(params, assumptions) require.NoError(t, err) require.NotNil(t, result) // Validate basic structure assert.Equal(t, "match_source_001_target_001", result.MatchID) assert.Equal(t, params.SourceResource, result.SourceResource) assert.Equal(t, params.TargetResource, result.TargetResource) // Validate calculations are reasonable assert.Greater(t, result.Calculations.AnnualSavings, 0.0) assert.Greater(t, result.Calculations.NPV10Years, -params.InitialInvestment) assert.LessOrEqual(t, result.Calculations.PaybackPeriodYears, 10.0) assert.GreaterOrEqual(t, result.Calculations.IRRPercent, -100.0) assert.LessOrEqual(t, result.Calculations.IRRPercent, 3000.0) // Allow higher IRR for good investments assert.Greater(t, result.Calculations.CO2ReductionTonnes, 0.0) // Validate transportation costs assert.Greater(t, result.Calculations.TransportationCosts.AnnualCost, 0.0) assert.Equal(t, 5.0, result.Calculations.TransportationCosts.DistanceKm) assert.Equal(t, "heat_pipe", result.Calculations.TransportationCosts.Method) // Validate implementation complexity assert.Contains(t, []string{"low", "medium", "high"}, result.Calculations.ImplementationComplexity) // Validate regulatory requirements assert.Contains(t, result.Calculations.RegulatoryRequirements, "energy_distribution_license") } func TestCalculateAnnualSavings(t *testing.T) { params := MatchEconomicsParams{ SourceResource: ResourceFlowSummary{ CostPerUnit: 20, // €20/unit disposal cost }, TargetResource: ResourceFlowSummary{ CostPerUnit: 50, // €50/unit value }, AnnualQuantity: 1000, // 1000 units/year } savings := calculateAnnualSavings(params) expected := float64((20 + 50) * 1000) // €70,000 assert.Equal(t, expected, savings) } func TestCalculatePaybackPeriod(t *testing.T) { tests := []struct { name string initialInvestment float64 annualNetCashFlow float64 expected float64 }{ { name: "normal payback", initialInvestment: 25000, annualNetCashFlow: 12500, expected: 2.0, }, { name: "never pays back", initialInvestment: 25000, annualNetCashFlow: 0, expected: 999, }, { name: "pays back in first year", initialInvestment: 5000, annualNetCashFlow: 10000, expected: 0.5, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := calculatePaybackPeriod(tt.initialInvestment, tt.annualNetCashFlow) assert.Equal(t, tt.expected, result) }) } } func TestAssessImplementationComplexity(t *testing.T) { tests := []struct { name string params MatchEconomicsParams expected string }{ { name: "low complexity", params: MatchEconomicsParams{ DistanceKm: 1.0, InitialInvestment: 10000, SymbiosisType: transport.SymbiosisDataSharing, }, expected: "low", }, { name: "medium complexity", params: MatchEconomicsParams{ DistanceKm: 10.0, InitialInvestment: 30000, SymbiosisType: transport.SymbiosisEnergyCascading, }, expected: "medium", }, { name: "high complexity", params: MatchEconomicsParams{ DistanceKm: 50.0, InitialInvestment: 100000, SymbiosisType: transport.SymbiosisWasteToResource, }, expected: "high", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := assessImplementationComplexity(tt.params) assert.Equal(t, tt.expected, result) }) } } func TestIdentifyRegulatoryRequirements(t *testing.T) { tests := []struct { name string params MatchEconomicsParams expected []string }{ { name: "waste handling requires permits", params: MatchEconomicsParams{ SymbiosisType: transport.SymbiosisWasteToResource, }, expected: []string{"waste_disposal_permit", "environmental_impact_assessment"}, }, { name: "energy transfer requires license", params: MatchEconomicsParams{ SymbiosisType: transport.SymbiosisEnergyCascading, }, expected: []string{"energy_distribution_license"}, }, { name: "long distance requires transport license", params: MatchEconomicsParams{ DistanceKm: 60.0, SymbiosisType: transport.SymbiosisDataSharing, }, expected: []string{"transport_license"}, }, { name: "high value requires insurance", params: MatchEconomicsParams{ UnitValue: 100, AnnualQuantity: 2000, // €200k annual value SymbiosisType: transport.SymbiosisDataSharing, }, expected: []string{"liability_insurance"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := identifyRegulatoryRequirements(tt.params) for _, expectedReq := range tt.expected { assert.Contains(t, result, expectedReq) } }) } } func TestDetermineTransportMethod(t *testing.T) { tests := []struct { symbiosisType transport.SymbiosisType expected string }{ {transport.SymbiosisWasteToResource, "truck"}, {transport.SymbiosisEnergyCascading, "heat_pipe"}, {transport.SymbiosisUtilitySharing, "pipeline"}, {transport.SymbiosisDataSharing, "network"}, {transport.SymbiosisKnowledgeSharing, "truck"}, // default } for _, tt := range tests { result := determineTransportMethod(tt.symbiosisType) assert.Equal(t, tt.expected, result) } } func TestFeasibilityToScore(t *testing.T) { tests := []struct { feasibility string expected float64 }{ {"high", 0.9}, {"medium", 0.6}, {"low", 0.3}, {"unknown", 0.5}, } for _, tt := range tests { result := feasibilityToScore(tt.feasibility) assert.Equal(t, tt.expected, result) } } func TestValidateEconomicCalculation(t *testing.T) { validCalc := &EconomicCalculation{ MatchID: "test_match", Calculations: MatchCalculations{ AnnualSavings: 50000, PaybackPeriodYears: 2.0, IRRPercent: 15.0, }, } err := ValidateEconomicCalculation(validCalc) assert.NoError(t, err) // Test invalid cases invalidCalc := &EconomicCalculation{ Calculations: MatchCalculations{ AnnualSavings: -1000, // Negative savings }, } err = ValidateEconomicCalculation(invalidCalc) assert.Error(t, err) emptyIDCalc := &EconomicCalculation{ Calculations: MatchCalculations{ AnnualSavings: 50000, }, } err = ValidateEconomicCalculation(emptyIDCalc) assert.Error(t, err) } func TestCalculateMatchNPV(t *testing.T) { params := MatchEconomicsParams{ InitialInvestment: 100000, // High investment } assumptions := DefaultCalculationAssumptions() npv := calculateMatchNPV(params, assumptions, 5000, 3000) // Only €2k net annual benefit // Should be negative (high investment, low returns) assert.Less(t, npv, 0.0) } func TestCalculateMatchIRR(t *testing.T) { params := MatchEconomicsParams{ InitialInvestment: 25000, } assumptions := DefaultCalculationAssumptions() irr := calculateMatchIRR(params, assumptions, 12500, 2500) // €10k net annual benefit // IRR should be reasonable (positive but not too high) assert.Greater(t, irr, 0.0) assert.Less(t, irr, 0.5) // Less than 50% } func TestGenerateMatchID(t *testing.T) { id := generateMatchID("source_123", "target_456") assert.Equal(t, "match_source_123_target_456", id) } func TestCalculateMatchEconomics_InvalidParams(t *testing.T) { params := MatchEconomicsParams{ // Missing resource IDs SourceResource: ResourceFlowSummary{ID: ""}, TargetResource: ResourceFlowSummary{ID: ""}, } assumptions := DefaultCalculationAssumptions() _, err := CalculateMatchEconomics(params, assumptions) assert.Error(t, err) assert.Contains(t, err.Error(), "source and target resource IDs are required") }