turash/models/calc_test.go
Damir Mukimov 4a2fda96cd
Initial commit: Repository setup with .gitignore, golangci-lint v2.6.0, and code quality checks
- 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
2025-11-01 07:36:22 +01:00

169 lines
6.6 KiB
Go

package models
import (
"testing"
"github.com/damirmukimov/city_resource_graph/models/cost"
"github.com/damirmukimov/city_resource_graph/models/customer"
"github.com/damirmukimov/city_resource_graph/models/impact"
"github.com/damirmukimov/city_resource_graph/models/params"
"github.com/damirmukimov/city_resource_graph/models/revenue"
"github.com/damirmukimov/city_resource_graph/models/validator"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCalculate(t *testing.T) {
p := createTestParamsForCalc()
result, err := Calculate(p)
require.NoError(t, err, "Calculate should not return an error")
assert.Equal(t, 3, len(result.Years), "Should calculate 3 years")
// Test Year 1
year1 := result.Years[0]
assert.Equal(t, 1, year1.Year, "Year 1 should have year=1")
assert.Equal(t, 500, year1.Customer.TotalOrgs, "Year 1 should have 500 total orgs")
assert.Equal(t, 150, year1.Customer.PayingOrgs, "Year 1 should have 150 paying orgs")
// Test that types are correct
assert.IsType(t, revenue.RevenueBreakdown{}, year1.Revenue, "Revenue should be RevenueBreakdown")
assert.IsType(t, cost.CostBreakdown{}, year1.Costs, "Costs should be CostBreakdown")
assert.IsType(t, impact.ImpactMetrics{}, year1.Impact, "Impact should be ImpactMetrics")
assert.IsType(t, customer.CustomerMetrics{}, year1.Customer, "Customer should be CustomerMetrics")
assert.IsType(t, customer.TierDistribution{}, year1.TierDist, "TierDist should be TierDistribution")
assert.IsType(t, validator.ValidationResult{}, year1.Validation, "Validation should be ValidationResult")
// Test ARPU range (B2B SaaS typical)
assert.True(t, year1.ARPU > 300, "Year 1 ARPU should be above 300")
assert.True(t, year1.ARPU < 6000, "Year 1 ARPU should be below 6000")
// Test summary
assert.True(t, result.Summary.TotalRevenue > 0, "Total revenue should be positive")
assert.True(t, result.Summary.TotalProfit > 0, "Total profit should be positive")
assert.True(t, result.Summary.TotalCO2Avoided > 0, "Total CO2 avoided should be positive")
}
func TestCalculateYear(t *testing.T) {
p := createTestParamsForCalc()
year1, err := CalculateYear(1, p)
require.NoError(t, err, "CalculateYear should not return an error")
assert.Equal(t, 1, year1.Year, "Should return year 1")
assert.Equal(t, 500, year1.Customer.TotalOrgs, "Should calculate customer metrics")
assert.True(t, year1.Revenue.Total > 0, "Should calculate revenue")
assert.True(t, year1.Costs.Total > 0, "Should calculate costs")
assert.True(t, year1.Impact.CO2Avoided > 0, "Should calculate impact")
}
func TestCalculateSummary(t *testing.T) {
// Create test data
years := []YearResult{
{
Revenue: revenue.RevenueBreakdown{Total: 100000.0},
Costs: cost.CostBreakdown{Total: 80000.0},
Profit: 20000.0,
Impact: impact.ImpactMetrics{
CO2Avoided: 1000.0,
WaterReused: 50000.0,
WasteDiverted: 2000.0,
},
},
{
Revenue: revenue.RevenueBreakdown{Total: 150000.0},
Costs: cost.CostBreakdown{Total: 100000.0},
Profit: 50000.0,
Impact: impact.ImpactMetrics{
CO2Avoided: 1500.0,
WaterReused: 75000.0,
WasteDiverted: 3000.0,
},
},
}
// Test the summary calculation directly
summary := calculateSummary(years)
assert.InDelta(t, 250000.0, summary.TotalRevenue, 0.01, "Total revenue should be sum of all years")
assert.InDelta(t, 180000.0, summary.TotalCosts, 0.01, "Total costs should be sum of all years")
assert.InDelta(t, 70000.0, summary.TotalProfit, 0.01, "Total profit should be sum of all years")
assert.InDelta(t, 2500.0, summary.TotalCO2Avoided, 0.01, "Total CO2 avoided should be sum of all years")
assert.InDelta(t, 125000.0, summary.TotalWaterReused, 0.01, "Total water reused should be sum of all years")
assert.InDelta(t, 5000.0, summary.TotalWasteDiverted, 0.01, "Total waste diverted should be sum of all years")
}
func createTestParamsForCalc() *params.Params {
return &params.Params{
Time: params.TimeParams{Years: []int{1, 2, 3}},
Adoption: params.AdoptionParams{
TotalOrgs: params.YearlyInt{"1": 500, "2": 2000, "3": 5000},
PayingShare: params.YearlyFloat{"1": 0.3, "2": 0.3, "3": 0.3},
},
Pricing: params.PricingParams{
Basic: 35.0,
Business: 120.0,
Enterprise: 400.0,
BlendedUplift: params.BlendedUplift{
Basic: 0.20,
Business: 0.25,
Enterprise: 0.25,
},
TierMix: params.YearlyTierMix{
"1": params.TierMix{Basic: 0.60, Business: 0.30, Enterprise: 0.10},
"2": params.TierMix{Basic: 0.60, Business: 0.30, Enterprise: 0.10},
"3": params.TierMix{Basic: 0.54, Business: 0.38, Enterprise: 0.08},
},
},
Transactions: params.TransactionParams{
AvgIntroFee: 550.0,
IntrosPerYear: params.YearlyInt{"1": 200, "2": 400, "3": 600},
IntroConversion: params.YearlyFloat{"1": 0.35, "2": 0.38, "3": 0.40},
ServiceGMV: params.YearlyFloat{"1": 300000, "2": 800000, "3": 1500000},
ServiceCommission: 0.15,
GroupGMV: params.YearlyFloat{"1": 200000, "2": 400000, "3": 800000},
GroupCommission: 0.04,
},
Municipal: params.MunicipalParams{
Cities: params.YearlyInt{"1": 1, "2": 2, "3": 4},
AvgLicense: params.YearlyFloat{"1": 60000, "2": 90000, "3": 110000},
DataLicensing: params.YearlyFloat{"1": 0, "2": 50000, "3": 150000},
},
ImplServices: params.ImplementationParams{
MatchesPerOrg: 0.5,
PaidShare: 0.25,
AvgFee: 5000.0,
},
Impact: params.ImpactParams{
HeatMWh: params.YearlyFloat{"1": 500000, "2": 1500000, "3": 3000000},
GridFactor: 0.3,
HXEff: 0.9,
Utilization: 0.7,
WaterPerOrg: 25000.0,
WaterReuseRate: params.YearlyFloat{"1": 0.20, "2": 0.25, "3": 0.30},
WastePerOrg: 100.0,
WasteDiversionRate: params.YearlyFloat{"1": 0.15, "2": 0.25, "3": 0.35},
},
Costs: params.CostParams{
Engineers: params.YearlyInt{"1": 8, "2": 12, "3": 15},
EngineerSalary: 100000.0,
Infrastructure: params.YearlyFloat{"1": 200000, "2": 250000, "3": 400000},
MarketingSales: params.YearlyFloat{"1": 300000, "2": 600000, "3": 900000},
Operations: params.YearlyFloat{"1": 100000, "2": 150000, "3": 200000},
},
Market: params.MarketParams{
TAM: 500000000000.0,
AddressableDigital: 3000000000.0,
PilotCityEconomicBenefit: 4000000.0,
ScalabilityPotential: 400000000.0,
EUIndustrialFacilities: 2100000,
EnergyWastePotential: 0.45,
ResourceCostReduction: 0.25,
ViableExchangeRate: 0.15,
PlatformCaptureRate: 0.50,
SOM: params.YearlyFloat{"1": 50000000, "2": 300000000, "3": 1500000000},
},
}
}