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 }