carlos / platform Public

economics: allocation, pricing and free-tier maths with golden tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell e0d761abcc33f075ab26968560a70f6e45497f7c parent 6aa3dc6
2 files changed, +255 −0
  • internal/economics/compute.go +148 −0
  • internal/economics/compute_test.go +107 −0
diff --git a/internal/economics/compute.go b/internal/economics/compute.go
new file mode 100644
index 000000000..d72adc3ee
--- /dev/null
+++ b/internal/economics/compute.go
@@ -0,0 +1,148 @@
+package economics
+
+import "github.com/carlosframework/platform/internal/usage"
+
+// HoursPerMonth prorates GB-month rates over hour-denominated periods.
+const HoursPerMonth = 730.0
+
+// BoxAccountLoad is one account's consumption on one box over the period.
+type BoxAccountLoad struct {
+ CPUCoreHours float64
+ MemGBHours float64
+}
+
+// BoxLoad is one box over the period: metered hours and per-account load.
+type BoxLoad struct {
+ Box string
+ Hours float64
+ ByAccount map[string]BoxAccountLoad
+}
+
+// AllocateBox splits a box's period cost across accounts by resource share.
+// The unallocated remainder — idle headroom — is returned separately and
+// must be shown as its own line, never smeared across tenants (spec).
+func AllocateBox(rc RateCard, al Allocation, b BoxLoad) (perAccount map[string]float64, idleUSD, totalUSD float64) {
+ spec, ok := rc.Boxes[b.Box]
+ if !ok {
+ spec = rc.Boxes[""]
+ }
+ totalUSD = spec.HourUSD*b.Hours + spec.EBSGB*rc.EBSGBMonthUSD*(b.Hours/HoursPerMonth)
+ perAccount = map[string]float64{}
+ if b.Hours <= 0 {
+ return perAccount, totalUSD, totalUSD
+ }
+ allocated := 0.0
+ for acct, load := range b.ByAccount {
+ var cpuShare, memShare float64
+ if spec.Cores > 0 {
+ cpuShare = load.CPUCoreHours / (spec.Cores * b.Hours)
+ }
+ if spec.MemGB > 0 {
+ memShare = load.MemGBHours / (spec.MemGB * b.Hours)
+ }
+ var share float64
+ switch al.Policy {
+ case "cpu":
+ share = cpuShare
+ case "mem":
+ share = memShare
+ default: // blend
+ share = al.CPUWeight*cpuShare + (1-al.CPUWeight)*memShare
+ }
+ usd := share * totalUSD
+ perAccount[acct] = usd
+ allocated += usd
+ }
+ idleUSD = totalUSD - allocated
+ if idleUSD < 0 {
+ idleUSD = 0
+ }
+ return perAccount, idleUSD, totalUSD
+}
+
+// AccountEconomics is one account priced under one scenario.
+type AccountEconomics struct {
+ Account string
+ CostUSD float64
+ RevenueUSD float64
+ MarginFrac float64 // (revenue-cost)/revenue, 0 when revenue is 0
+ FreeTierPos string // "inside" | "partial" | "paying"
+}
+
+func billable(meters map[string]float64, meter string, allowance float64) float64 {
+ v := meters[meter] - allowance
+ if v < 0 {
+ return 0
+ }
+ return v
+}
+
+// PriceAccount prices one account's meters plus its allocated box cost
+// under sc. These formulas are mirrored verbatim by the dashboard's inline
+// knob JS (economics.html) — change both together or the page will disagree
+// with the server it loaded from.
+func PriceAccount(sc Scenario, account string, meters map[string]float64, allocatedUSD float64) AccountEconomics {
+ rc := sc.RateCard
+ cost := allocatedUSD +
+ meters[usage.MeterEgressGB]*rc.EgressGBUSD +
+ meters[usage.MeterRequests1k]*rc.RequestsPer1kUSD +
+ meters[usage.MeterStorageGBMonth]*rc.StorageGBMonthUSD
+
+ ft := sc.FreeTier
+ bEgress := billable(meters, usage.MeterEgressGB, ft.EgressGB)
+ bStorage := billable(meters, usage.MeterStorageGBMonth, ft.StorageGB)
+ bApps := billable(meters, usage.MeterAppMonths, ft.Apps)
+ bMem := billable(meters, usage.MeterMemGBHours, ft.MemGBHours)
+ computeFrac := 1.0
+ if m := meters[usage.MeterMemGBHours]; m > 0 {
+ computeFrac = bMem / m
+ }
+
+ var revenue float64
+ if sc.Mode == "rate-sheet" {
+ revenue = bApps*sc.Sheet.AppMonthUSD +
+ bEgress*sc.Sheet.EgressGBUSD +
+ bStorage*sc.Sheet.StorageGBMonthUSD +
+ meters[usage.MeterInstanceHours]*sc.Sheet.InstanceHourUSD
+ } else { // cost-plus
+ billableCost := allocatedUSD*computeFrac +
+ bEgress*rc.EgressGBUSD +
+ meters[usage.MeterRequests1k]*rc.RequestsPer1kUSD +
+ bStorage*rc.StorageGBMonthUSD
+ revenue = billableCost / (1 - sc.Margin)
+ }
+
+ margin := 0.0
+ if revenue > 0 {
+ margin = (revenue - cost) / revenue
+ }
+
+ anyUsage := false
+ for _, v := range meters {
+ if v > 0 {
+ anyUsage = true
+ break
+ }
+ }
+ clipped := bEgress < meters[usage.MeterEgressGB] ||
+ bStorage < meters[usage.MeterStorageGBMonth] ||
+ bApps < meters[usage.MeterAppMonths] ||
+ bMem < meters[usage.MeterMemGBHours]
+ pos := "paying"
+ switch {
+ case revenue == 0 && anyUsage:
+ pos = "inside"
+ case clipped:
+ pos = "partial"
+ }
+
+ return AccountEconomics{Account: account, CostUSD: cost, RevenueUSD: revenue, MarginFrac: margin, FreeTierPos: pos}
+}
+
+// Project scales a month-to-date figure to a full month by covered days.
+func Project(mtd float64, coveredDays, daysInMonth int) float64 {
+ if coveredDays <= 0 {
+ return 0
+ }
+ return mtd * float64(daysInMonth) / float64(coveredDays)
+}
diff --git a/internal/economics/compute_test.go b/internal/economics/compute_test.go
new file mode 100644
index 000000000..6494ab562
--- /dev/null
+++ b/internal/economics/compute_test.go
@@ -0,0 +1,107 @@
+package economics
+
+import (
+ "math"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/usage"
+)
+
+func approx(t *testing.T, name string, got, want float64) {
+ t.Helper()
+ if math.Abs(got-want) > 1e-9 {
+ t.Fatalf("%s = %v, want %v", name, got, want)
+ }
+}
+
+// A 2-core/2GB box at $0.10/h with no EBS, 100h period, one tenant using
+// 50 core-hours (25% of 200 available) and 100 GB-hours (50% of 200).
+func testBox() (RateCard, BoxLoad) {
+ rc := RateCard{Boxes: map[string]BoxSpec{"": {HourUSD: 0.10, Cores: 2, MemGB: 2}}}
+ b := BoxLoad{Box: "box1", Hours: 100, ByAccount: map[string]BoxAccountLoad{
+ "bab": {CPUCoreHours: 50, MemGBHours: 100},
+ }}
+ return rc, b
+}
+
+func TestAllocateBoxPolicies(t *testing.T) {
+ rc, b := testBox()
+ per, idle, total := AllocateBox(rc, Allocation{Policy: "cpu"}, b)
+ approx(t, "total", total, 10.0)
+ approx(t, "cpu share", per["bab"], 2.5) // 25% of $10
+ approx(t, "cpu idle", idle, 7.5)
+
+ per, idle, _ = AllocateBox(rc, Allocation{Policy: "mem"}, b)
+ approx(t, "mem share", per["bab"], 5.0) // 50% of $10
+ approx(t, "mem idle", idle, 5.0)
+
+ per, idle, _ = AllocateBox(rc, Allocation{Policy: "blend", CPUWeight: 0.5}, b)
+ approx(t, "blend share", per["bab"], 3.75) // (0.5*25% + 0.5*50%) of $10
+ approx(t, "blend idle", idle, 6.25)
+}
+
+func TestAllocateBoxEBSAndFallbackSpec(t *testing.T) {
+ rc := RateCard{
+ Boxes: map[string]BoxSpec{"": {HourUSD: 0.10, Cores: 2, MemGB: 2, EBSGB: 73}},
+ EBSGBMonthUSD: 0.10,
+ }
+ b := BoxLoad{Box: "unlisted-box", Hours: 730, ByAccount: nil}
+ _, idle, total := AllocateBox(rc, Allocation{Policy: "cpu"}, b)
+ // 730h of $0.10 = $73, plus 73GB*$0.10/GB-month * (730/730) = $7.30.
+ approx(t, "total", total, 80.3)
+ approx(t, "all idle", idle, 80.3)
+}
+
+func TestPriceAccountCostPlusEightyMarginIsFiveX(t *testing.T) {
+ sc := DefaultScenario()
+ sc.RateCard.EgressGBUSD = 0.10
+ sc.RateCard.RequestsPer1kUSD = 0
+ sc.RateCard.StorageGBMonthUSD = 0
+ sc.FreeTier = FreeTier{} // no free tier: pure margin check
+ meters := map[string]float64{usage.MeterEgressGB: 10} // $1 of egress cost
+ ae := PriceAccount(sc, "bab", meters, 0)
+ approx(t, "cost", ae.CostUSD, 1.0)
+ approx(t, "revenue", ae.RevenueUSD, 5.0) // 80% margin = 5x cost
+ approx(t, "margin", ae.MarginFrac, 0.8)
+ if ae.FreeTierPos != "paying" {
+ t.Fatalf("pos = %q", ae.FreeTierPos)
+ }
+}
+
+func TestPriceAccountFreeTierSubtraction(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "rate-sheet"
+ sc.Sheet = RateSheet{AppMonthUSD: 5, EgressGBUSD: 0.15}
+ sc.FreeTier = FreeTier{Apps: 1, EgressGB: 10}
+ meters := map[string]float64{usage.MeterAppMonths: 2, usage.MeterEgressGB: 14}
+ ae := PriceAccount(sc, "bab", meters, 0)
+ // Billable: 1 app-month, 4 GB egress = $5 + $0.60.
+ approx(t, "revenue", ae.RevenueUSD, 5.6)
+ if ae.FreeTierPos != "partial" {
+ t.Fatalf("pos = %q", ae.FreeTierPos)
+ }
+
+ inside := PriceAccount(sc, "tiny", map[string]float64{usage.MeterAppMonths: 1, usage.MeterEgressGB: 2}, 0)
+ approx(t, "inside revenue", inside.RevenueUSD, 0)
+ if inside.FreeTierPos != "inside" {
+ t.Fatalf("inside pos = %q", inside.FreeTierPos)
+ }
+}
+
+func TestPriceAccountComputeFraction(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "cost-plus"
+ sc.Margin = 0.5
+ sc.RateCard.EgressGBUSD, sc.RateCard.RequestsPer1kUSD, sc.RateCard.StorageGBMonthUSD = 0, 0, 0
+ sc.FreeTier = FreeTier{MemGBHours: 100}
+ meters := map[string]float64{usage.MeterMemGBHours: 400}
+ // $8 allocated, 300/400 billable -> $6 billable cost -> $12 revenue at 50%.
+ ae := PriceAccount(sc, "bab", meters, 8)
+ approx(t, "cost", ae.CostUSD, 8)
+ approx(t, "revenue", ae.RevenueUSD, 12)
+}
+
+func TestProject(t *testing.T) {
+ approx(t, "project", Project(10, 10, 30), 30)
+ approx(t, "zero covered", Project(10, 0, 30), 0)
+}