carlos / platform Public

economics: fleet box-month pricing with volume break, mem overage

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell de462941c5ad7038ff8e4a0b7f1e9aec831538d8 parent 39eee15
2 files changed, +71 −1
  • internal/economics/compute.go +16 −1
  • internal/economics/compute_test.go +55 −0
diff --git a/internal/economics/compute.go b/internal/economics/compute.go
index 35912fc55..5b389b24b 100644
--- a/internal/economics/compute.go
+++ b/internal/economics/compute.go
@@ -85,7 +85,8 @@ func billable(meters map[string]float64, meter string, allowance float64) float6
// 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.
+// with the server it loaded from. Fleet box and mem overage formulas are
+// also mirrored in the JS (Task 6).
func PriceAccount(sc Scenario, account string, meters map[string]float64, allocatedUSD float64) AccountEconomics {
rc := sc.RateCard
cost := allocatedUSD +
@@ -109,6 +110,9 @@ func PriceAccount(sc Scenario, account string, meters map[string]float64, alloca
bEgress*sc.Sheet.EgressGBUSD +
bStorage*sc.Sheet.StorageGBMonthUSD +
meters[usage.MeterInstanceHours]*sc.Sheet.InstanceHourUSD
+ // Mem overage (rate-sheet mode only)
+ memOverage := billable(meters, usage.MeterMemGBHours, ft.MemGBHours)
+ revenue += memOverage * sc.Sheet.MemGBHourUSD
} else { // cost-plus
billableCost := allocatedUSD*computeFrac +
bEgress*rc.EgressGBUSD +
@@ -117,6 +121,17 @@ func PriceAccount(sc Scenario, account string, meters map[string]float64, alloca
revenue = billableCost / (1 - sc.Margin)
}
+ // Fleet box revenue (both modes)
+ fleetRevenue := (meters[usage.MeterFleetBoxHoursServer]/HoursPerMonth)*sc.Sheet.BoxMonthServerUSD +
+ (meters[usage.MeterFleetBoxHoursDevice]/HoursPerMonth)*sc.Sheet.BoxMonthDeviceUSD
+
+ // Apply volume break if applicable
+ avgBoxes := (meters[usage.MeterFleetBoxHoursServer] + meters[usage.MeterFleetBoxHoursDevice]) / HoursPerMonth
+ if sc.FleetBreak.MinBoxes > 0 && avgBoxes >= sc.FleetBreak.MinBoxes {
+ fleetRevenue *= (1 - sc.FleetBreak.Discount)
+ }
+ revenue += fleetRevenue
+
margin := 0.0
if revenue > 0 {
margin = (revenue - cost) / revenue
diff --git a/internal/economics/compute_test.go b/internal/economics/compute_test.go
index 6494ab562..5d3206aec 100644
--- a/internal/economics/compute_test.go
+++ b/internal/economics/compute_test.go
@@ -105,3 +105,58 @@ func TestProject(t *testing.T) {
approx(t, "project", Project(10, 10, 30), 30)
approx(t, "zero covered", Project(10, 0, 30), 0)
}
+
+func TestPriceAccountFleetBoxes(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "rate-sheet"
+ sc.Sheet = RateSheet{BoxMonthServerUSD: 5, BoxMonthDeviceUSD: 1}
+ sc.FreeTier = FreeTier{}
+ // 2 server boxes + 5 device boxes, full month.
+ meters := map[string]float64{
+ usage.MeterFleetBoxHoursServer: 2 * 730,
+ usage.MeterFleetBoxHoursDevice: 5 * 730,
+ }
+ ae := PriceAccount(sc, "bae", meters, 0)
+ approx(t, "fleet revenue", ae.RevenueUSD, 2*5+5*1) // $15
+}
+
+func TestPriceAccountFleetVolumeBreak(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "rate-sheet"
+ sc.Sheet = RateSheet{BoxMonthServerUSD: 5}
+ sc.FleetBreak = FleetBreak{MinBoxes: 10, Discount: 0.4}
+ sc.FreeTier = FreeTier{}
+ meters := map[string]float64{usage.MeterFleetBoxHoursServer: 12 * 730}
+ ae := PriceAccount(sc, "bae", meters, 0)
+ approx(t, "discounted", ae.RevenueUSD, 12*5*0.6) // $36
+ // 9.99 avg boxes: no break.
+ meters[usage.MeterFleetBoxHoursServer] = 9.99 * 730
+ ae = PriceAccount(sc, "bae", meters, 0)
+ approx(t, "no break", ae.RevenueUSD, 9.99*5)
+}
+
+func TestPriceAccountFleetAddsInCostPlusToo(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "cost-plus"
+ sc.Margin = 0.5
+ sc.Sheet.BoxMonthServerUSD = 5
+ sc.RateCard.EgressGBUSD = 0.10
+ sc.RateCard.RequestsPer1kUSD, sc.RateCard.StorageGBMonthUSD = 0, 0
+ sc.FreeTier = FreeTier{}
+ meters := map[string]float64{
+ usage.MeterEgressGB: 10, // $1 cost -> $2 cost-plus revenue
+ usage.MeterFleetBoxHoursServer: 1 * 730, // + $5 box-month
+ }
+ ae := PriceAccount(sc, "bae", meters, 0)
+ approx(t, "combined", ae.RevenueUSD, 2+5)
+}
+
+func TestPriceAccountMemOverage(t *testing.T) {
+ sc := DefaultScenario()
+ sc.Mode = "rate-sheet"
+ sc.Sheet = RateSheet{MemGBHourUSD: 0.01}
+ sc.FreeTier = FreeTier{MemGBHours: 100}
+ meters := map[string]float64{usage.MeterMemGBHours: 400}
+ ae := PriceAccount(sc, "bab", meters, 0)
+ approx(t, "overage", ae.RevenueUSD, 3.0) // 300 over * $0.01
+}