turash/models/customer/customer.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

109 lines
4.0 KiB
Go

package customer
import (
"github.com/damirmukimov/city_resource_graph/models/params"
)
// CustomerMetrics represents customer adoption metrics for a given year.
type CustomerMetrics struct {
TotalOrgs int `json:"total_orgs"` // Total active organizations (after churn)
PayingOrgs int `json:"paying_orgs"` // Active paying organizations
FreeOrgs int `json:"free_orgs"` // Active free tier organizations
PayingShare float64 `json:"paying_share"` // Percentage paying (0.0-1.0)
NewAcquisitions int `json:"new_acquisitions"` // New organizations acquired this year
ChurnedOrgs int `json:"churned_orgs"` // Organizations that churned this year
CumulativeOrgs int `json:"cumulative_orgs"` // Cumulative organizations ever acquired
}
// TierDistribution represents the distribution of paying customers across tiers.
type TierDistribution struct {
Basic int `json:"basic"` // Number of Basic tier customers
Business int `json:"business"` // Number of Business tier customers
Enterprise int `json:"enterprise"` // Number of Enterprise tier customers
Total int `json:"total"` // Total paying customers
}
// ChurnMetrics represents churn and retention metrics per tier.
type ChurnMetrics struct {
Basic TierChurn `json:"basic"`
Business TierChurn `json:"business"`
Enterprise TierChurn `json:"enterprise"`
}
// TierChurn represents churn metrics for a single tier.
type TierChurn struct {
AnnualChurn float64 `json:"annual_churn"` // Annual churn rate (0.0-1.0)
Retention float64 `json:"retention"` // Retention rate (1.0 - churn)
AvgLifetimeMonths int `json:"avg_lifetime_months"` // Average customer lifetime in months
}
// DefaultChurnMetrics returns default churn metrics per tier.
func DefaultChurnMetrics() ChurnMetrics {
return ChurnMetrics{
Basic: TierChurn{
AnnualChurn: 0.15, // 15%
Retention: 0.85, // 85%
AvgLifetimeMonths: 48, // 4 years (conservative)
},
Business: TierChurn{
AnnualChurn: 0.10, // 10%
Retention: 0.90, // 90%
AvgLifetimeMonths: 64, // 5.3 years (conservative)
},
Enterprise: TierChurn{
AnnualChurn: 0.05, // 5%
Retention: 0.95, // 95%
AvgLifetimeMonths: 80, // 6.7 years (conservative)
},
}
}
// CalculateCustomerMetrics computes customer adoption metrics for a given year.
func CalculateCustomerMetrics(year int, p *params.Params) CustomerMetrics {
totalOrgs := p.Adoption.TotalOrgs.GetYear(year)
payingShare := p.Adoption.PayingShare.GetYear(year)
payingOrgs := int(float64(totalOrgs) * payingShare)
freeOrgs := totalOrgs - payingOrgs
// For churn modeling, estimate new acquisitions vs cumulative
// In a simplified model, assume all customers are "new" for the target year
// Churn rates are used in LTV calculations rather than reducing active customers
newAcquisitions := totalOrgs
churnedOrgs := 0 // Not modeled as reducing active base in this simplified approach
cumulativeOrgs := totalOrgs // Simplified - doesn't track across years
return CustomerMetrics{
TotalOrgs: totalOrgs,
PayingOrgs: payingOrgs,
FreeOrgs: freeOrgs,
PayingShare: payingShare,
NewAcquisitions: newAcquisitions,
ChurnedOrgs: churnedOrgs,
CumulativeOrgs: cumulativeOrgs,
}
}
// CalculateTierDistribution computes the distribution of paying customers across tiers.
func CalculateTierDistribution(year int, payingOrgs int, p *params.Params) TierDistribution {
tierMix := p.Pricing.TierMix.GetYear(year)
basic := int(float64(payingOrgs) * tierMix.Basic)
business := int(float64(payingOrgs) * tierMix.Business)
enterprise := int(float64(payingOrgs) * tierMix.Enterprise)
// Handle rounding - ensure total matches
total := basic + business + enterprise
if total != payingOrgs {
// Adjust basic tier to account for rounding differences
basic += payingOrgs - total
}
return TierDistribution{
Basic: basic,
Business: business,
Enterprise: enterprise,
Total: payingOrgs,
}
}