fix: round cost fields to 5 decimal places for cents

Andrey Nering created

Ensures consistent precision in pricing data across all provider
scripts by rounding cost values to 5 decimal places.

💘 Generated with Crush

Assisted-by: GLM-5 via Crush <crush@charm.land>

Change summary

cmd/aihubmix/main.go    |  7 ++++++-
cmd/huggingface/main.go |  5 +++--
cmd/ionet/main.go       | 10 ++++++----
cmd/openrouter/main.go  | 21 +++++++++++++--------
cmd/synthetic/main.go   | 13 +++++++++----
cmd/venice/main.go      |  5 +++--
cmd/vercel/main.go      | 10 ++++++----
7 files changed, 46 insertions(+), 25 deletions(-)

Detailed changes

cmd/aihubmix/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -95,11 +96,15 @@ func hasField(s, field string) bool {
 	return false
 }
 
+func roundCost(v float64) float64 {
+	return math.Round(v*1e5) / 1e5
+}
+
 func parseFloat(p *float64) float64 {
 	if p == nil {
 		return 0
 	}
-	return *p
+	return roundCost(*p)
 }
 
 func calculateMaxTokens(contextLength, maxOutput, factor int64) int64 {

cmd/huggingface/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -159,8 +160,8 @@ func main() {
 			// Calculate pricing (convert from per-token to per-1M tokens)
 			var costPer1MIn, costPer1MOut float64
 			if provider.Pricing != nil {
-				costPer1MIn = provider.Pricing.Input
-				costPer1MOut = provider.Pricing.Output
+				costPer1MIn = math.Round(provider.Pricing.Input*1e5) / 1e5
+				costPer1MOut = math.Round(provider.Pricing.Output*1e5) / 1e5
 			}
 
 			// Set default max tokens (conservative estimate)

cmd/ionet/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -79,10 +80,11 @@ func main() {
 		}
 
 		// Convert token prices (per token) to cost per 1M tokens
-		costPer1MIn := model.InputTokenPrice * 1_000_000
-		costPer1MOut := model.OutputTokenPrice * 1_000_000
-		costPer1MInCached := model.CacheReadTokenPrice * 1_000_000
-		costPer1MOutCached := model.CacheWriteTokenPrice * 1_000_000
+		roundCost := func(v float64) float64 { return math.Round(v*1e5) / 1e5 }
+		costPer1MIn := roundCost(model.InputTokenPrice * 1_000_000)
+		costPer1MOut := roundCost(model.OutputTokenPrice * 1_000_000)
+		costPer1MInCached := roundCost(model.CacheReadTokenPrice * 1_000_000)
+		costPer1MOutCached := roundCost(model.CacheWriteTokenPrice * 1_000_000)
 
 		m := catwalk.Model{
 			ID:                     model.ID,

cmd/openrouter/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -101,29 +102,33 @@ type ModelPricing struct {
 	CostPer1MOutCached float64 `json:"cost_per_1m_out_cached"`
 }
 
+func roundCost(v float64) float64 {
+	return math.Round(v*1e5) / 1e5
+}
+
 func getPricing(model Model) ModelPricing {
 	pricing := ModelPricing{}
 	costPrompt, err := strconv.ParseFloat(model.Pricing.Prompt, 64)
 	if err != nil {
 		costPrompt = 0.0
 	}
-	pricing.CostPer1MIn = costPrompt * 1_000_000
+	pricing.CostPer1MIn = roundCost(costPrompt * 1_000_000)
 	costCompletion, err := strconv.ParseFloat(model.Pricing.Completion, 64)
 	if err != nil {
 		costCompletion = 0.0
 	}
-	pricing.CostPer1MOut = costCompletion * 1_000_000
+	pricing.CostPer1MOut = roundCost(costCompletion * 1_000_000)
 
 	costPromptCached, err := strconv.ParseFloat(model.Pricing.InputCacheWrite, 64)
 	if err != nil {
 		costPromptCached = 0.0
 	}
-	pricing.CostPer1MInCached = costPromptCached * 1_000_000
+	pricing.CostPer1MInCached = roundCost(costPromptCached * 1_000_000)
 	costCompletionCached, err := strconv.ParseFloat(model.Pricing.InputCacheRead, 64)
 	if err != nil {
 		costCompletionCached = 0.0
 	}
-	pricing.CostPer1MOutCached = costCompletionCached * 1_000_000
+	pricing.CostPer1MOutCached = roundCost(costCompletionCached * 1_000_000)
 	return pricing
 }
 
@@ -334,23 +339,23 @@ func main() {
 		if err != nil {
 			costPrompt = 0.0
 		}
-		pricing.CostPer1MIn = costPrompt * 1_000_000
+		pricing.CostPer1MIn = roundCost(costPrompt * 1_000_000)
 		costCompletion, err := strconv.ParseFloat(bestEndpoint.Pricing.Completion, 64)
 		if err != nil {
 			costCompletion = 0.0
 		}
-		pricing.CostPer1MOut = costCompletion * 1_000_000
+		pricing.CostPer1MOut = roundCost(costCompletion * 1_000_000)
 
 		costPromptCached, err := strconv.ParseFloat(bestEndpoint.Pricing.InputCacheWrite, 64)
 		if err != nil {
 			costPromptCached = 0.0
 		}
-		pricing.CostPer1MInCached = costPromptCached * 1_000_000
+		pricing.CostPer1MInCached = roundCost(costPromptCached * 1_000_000)
 		costCompletionCached, err := strconv.ParseFloat(bestEndpoint.Pricing.InputCacheRead, 64)
 		if err != nil {
 			costCompletionCached = 0.0
 		}
-		pricing.CostPer1MOutCached = costCompletionCached * 1_000_000
+		pricing.CostPer1MOutCached = roundCost(costCompletionCached * 1_000_000)
 
 		canReason := slices.Contains(bestEndpoint.SupportedParams, "reasoning")
 		supportsImages := slices.Contains(model.Architecture.InputModalities, "image")

cmd/synthetic/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -64,12 +65,16 @@ func parsePrice(s string) float64 {
 	return v
 }
 
+func roundCost(v float64) float64 {
+	return math.Round(v*1e5) / 1e5
+}
+
 func getPricing(model Model) ModelPricing {
 	return ModelPricing{
-		CostPer1MIn:        parsePrice(model.Pricing.Prompt) * 1_000_000,
-		CostPer1MOut:       parsePrice(model.Pricing.Completion) * 1_000_000,
-		CostPer1MInCached:  parsePrice(model.Pricing.InputCacheReads) * 1_000_000,
-		CostPer1MOutCached: parsePrice(model.Pricing.InputCacheReads) * 1_000_000,
+		CostPer1MIn:        roundCost(parsePrice(model.Pricing.Prompt) * 1_000_000),
+		CostPer1MOut:       roundCost(parsePrice(model.Pricing.Completion) * 1_000_000),
+		CostPer1MInCached:  roundCost(parsePrice(model.Pricing.InputCacheReads) * 1_000_000),
+		CostPer1MOutCached: roundCost(parsePrice(model.Pricing.InputCacheReads) * 1_000_000),
 	}
 }
 

cmd/venice/main.go 🔗

@@ -225,11 +225,12 @@ func main() {
 			}
 		}
 
+		roundCost := func(v float64) float64 { return math.Round(v*1e5) / 1e5 }
 		m := catwalk.Model{
 			ID:                     model.ID,
 			Name:                   model.ModelSpec.Name,
-			CostPer1MIn:            model.ModelSpec.Pricing.Input.USD,
-			CostPer1MOut:           model.ModelSpec.Pricing.Output.USD,
+			CostPer1MIn:            roundCost(model.ModelSpec.Pricing.Input.USD),
+			CostPer1MOut:           roundCost(model.ModelSpec.Pricing.Output.USD),
 			CostPer1MInCached:      0,
 			CostPer1MOutCached:     0,
 			ContextWindow:          contextWindow,

cmd/vercel/main.go 🔗

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"slices"
@@ -107,6 +108,7 @@ func main() {
 		}
 
 		// Parse pricing
+		roundCost := func(v float64) float64 { return math.Round(v*1e5) / 1e5 }
 		costPer1MIn := 0.0
 		costPer1MOut := 0.0
 		costPer1MInCached := 0.0
@@ -115,28 +117,28 @@ func main() {
 		if model.Pricing.Input != "" {
 			costPrompt, err := strconv.ParseFloat(model.Pricing.Input, 64)
 			if err == nil {
-				costPer1MIn = costPrompt * 1_000_000
+				costPer1MIn = roundCost(costPrompt * 1_000_000)
 			}
 		}
 
 		if model.Pricing.Output != "" {
 			costCompletion, err := strconv.ParseFloat(model.Pricing.Output, 64)
 			if err == nil {
-				costPer1MOut = costCompletion * 1_000_000
+				costPer1MOut = roundCost(costCompletion * 1_000_000)
 			}
 		}
 
 		if model.Pricing.InputCacheRead != "" {
 			costCached, err := strconv.ParseFloat(model.Pricing.InputCacheRead, 64)
 			if err == nil {
-				costPer1MInCached = costCached * 1_000_000
+				costPer1MInCached = roundCost(costCached * 1_000_000)
 			}
 		}
 
 		if model.Pricing.InputCacheWrite != "" {
 			costCacheWrite, err := strconv.ParseFloat(model.Pricing.InputCacheWrite, 64)
 			if err == nil {
-				costPer1MOutCached = costCacheWrite * 1_000_000
+				costPer1MOutCached = roundCost(costCacheWrite * 1_000_000)
 			}
 		}