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

153 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit
import (
"github.com/damirmukimov/city_resource_graph/models/customer"
"github.com/damirmukimov/city_resource_graph/models/params"
"github.com/damirmukimov/city_resource_graph/models/revenue"
)
// UnitEconomics contains all unit economics metrics for a year.
type UnitEconomics struct {
LTV TierMetrics `json:"ltv"` // Lifetime Value by tier
CAC float64 `json:"cac"` // Customer Acquisition Cost
LTVToCACRatio float64 `json:"ltv_cac_ratio"` // LTV/CAC ratio
ChurnRate float64 `json:"churn_rate"` // Blended annual churn rate
PaybackPeriod float64 `json:"payback_period"` // Months to recover CAC
}
// TierMetrics contains metrics for each tier.
type TierMetrics struct {
Basic float64 `json:"basic"`
Business float64 `json:"business"`
Enterprise float64 `json:"enterprise"`
Blended float64 `json:"blended"`
}
// CalculateUnitEconomics computes unit economics for a given year.
func CalculateUnitEconomics(year int, custMetrics customer.CustomerMetrics, tierDist customer.TierDistribution, revBreakdown revenue.RevenueBreakdown, p *params.Params) UnitEconomics {
uep := p.UnitEconomics
// Calculate LTV for each tier
ltv := calculateLTVByTier(p)
// Calculate blended LTV based on tier distribution
totalCustomers := float64(custMetrics.PayingOrgs)
if totalCustomers == 0 {
totalCustomers = 1 // Avoid division by zero
}
blendedLTV := (ltv.Basic*float64(tierDist.Basic) +
ltv.Business*float64(tierDist.Business) +
ltv.Enterprise*float64(tierDist.Enterprise)) / totalCustomers
// Calculate CAC (marketing & sales costs / new customers)
marketingSalesCosts := p.Costs.MarketingSales.GetYear(year)
cac := marketingSalesCosts / float64(custMetrics.PayingOrgs)
if custMetrics.PayingOrgs == 0 {
cac = 0
}
// Calculate LTV/CAC ratio
ltvToCACRatio := 0.0
if cac > 0 {
ltvToCACRatio = blendedLTV / cac
}
// Calculate blended churn rate (weighted by tier distribution)
blendedChurn := (uep.ChurnRates.Basic*float64(tierDist.Basic) +
uep.ChurnRates.Business*float64(tierDist.Business) +
uep.ChurnRates.Enterprise*float64(tierDist.Enterprise)) / totalCustomers
// Calculate payback period (months to recover CAC)
avgMonthlyRevenue := revBreakdown.Total / float64(custMetrics.PayingOrgs) / 12
paybackPeriod := 0.0
if avgMonthlyRevenue > 0 {
paybackPeriod = cac / avgMonthlyRevenue
}
return UnitEconomics{
LTV: TierMetrics{
Basic: ltv.Basic,
Business: ltv.Business,
Enterprise: ltv.Enterprise,
Blended: blendedLTV,
},
CAC: cac,
LTVToCACRatio: ltvToCACRatio,
ChurnRate: blendedChurn,
PaybackPeriod: paybackPeriod,
}
}
// calculateLTVByTier calculates Lifetime Value for each tier.
func calculateLTVByTier(p *params.Params) TierMetrics {
uep := p.UnitEconomics
// Get blended monthly subscription prices (including transaction uplifts)
basicMonthly := p.Pricing.Basic * (1.0 + p.Pricing.BlendedUplift.Basic)
businessMonthly := p.Pricing.Business * (1.0 + p.Pricing.BlendedUplift.Business)
enterpriseMonthly := p.Pricing.Enterprise * (1.0 + p.Pricing.BlendedUplift.Enterprise)
// Calculate LTV for Basic tier
basicLTV := calculateTierLTV(
basicMonthly,
uep.RetentionMonths.Basic,
uep.TransactionFees.Basic,
0, // No upsell revenue for basic (it's the entry tier)
uep.PlatformCosts,
)
// Calculate LTV for Business tier
businessUpsellRevenue := uep.UpsellRates.BusinessToEnterprise * calculateTierLTV(
enterpriseMonthly,
uep.RetentionMonths.Enterprise,
uep.TransactionFees.Enterprise,
0, // No further upsell
uep.PlatformCosts,
)
businessLTV := calculateTierLTV(
businessMonthly,
uep.RetentionMonths.Business,
uep.TransactionFees.Business,
businessUpsellRevenue,
uep.PlatformCosts,
)
// Calculate LTV for Enterprise tier
enterpriseExpansionRevenue := uep.EnterpriseExpansion.MultiSiteRate *
uep.EnterpriseExpansion.AdditionalSites *
uep.EnterpriseExpansion.SiteRevenue * 12 * uep.RetentionMonths.Enterprise // Annual expansion revenue
enterpriseLTV := calculateTierLTV(
enterpriseMonthly,
uep.RetentionMonths.Enterprise,
uep.TransactionFees.Enterprise,
enterpriseExpansionRevenue,
uep.PlatformCosts,
)
return TierMetrics{
Basic: basicLTV,
Business: businessLTV,
Enterprise: enterpriseLTV,
}
}
// calculateTierLTV calculates LTV for a specific tier.
func calculateTierLTV(monthlyRevenue, retentionMonths, transactionFees, upsellRevenue, platformCosts float64) float64 {
// Gross subscription revenue over lifetime
grossSubscriptionRevenue := monthlyRevenue * retentionMonths
// Transaction revenue over lifetime (annual fees × years)
years := retentionMonths / 12
grossTransactionRevenue := transactionFees * years
// Total gross revenue
totalGrossRevenue := grossSubscriptionRevenue + grossTransactionRevenue + upsellRevenue
// Net of platform costs
totalNetRevenue := totalGrossRevenue * (1.0 - platformCosts)
return totalNetRevenue
}