feat(default): add real rankings data
This commit is contained in:
@@ -0,0 +1,599 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
)
|
||||
|
||||
const (
|
||||
rankingCacheTTL = 5 * time.Minute
|
||||
rankingLeaderboardLimit = 20
|
||||
rankingHistoryLimit = 10
|
||||
rankingVendorLimit = 5
|
||||
rankingMoverLimit = 6
|
||||
rankingOthersLabel = "Others"
|
||||
rankingUnknownVendor = "Unknown"
|
||||
)
|
||||
|
||||
type RankingsResponse struct {
|
||||
Models []RankedModel `json:"models"`
|
||||
Vendors []RankedVendor `json:"vendors"`
|
||||
TopMovers []RankingMover `json:"top_movers"`
|
||||
TopDroppers []RankingMover `json:"top_droppers"`
|
||||
ModelsHistory ModelHistorySeries `json:"models_history"`
|
||||
VendorShareHistory VendorShareSeries `json:"vendor_share_history"`
|
||||
}
|
||||
|
||||
type RankedModel struct {
|
||||
Rank int `json:"rank"`
|
||||
PreviousRank *int `json:"previous_rank,omitempty"`
|
||||
ModelName string `json:"model_name"`
|
||||
Vendor string `json:"vendor"`
|
||||
VendorIcon string `json:"vendor_icon,omitempty"`
|
||||
Category string `json:"category"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
Share float64 `json:"share"`
|
||||
GrowthPct float64 `json:"growth_pct"`
|
||||
}
|
||||
|
||||
type RankedVendor struct {
|
||||
Rank int `json:"rank"`
|
||||
Vendor string `json:"vendor"`
|
||||
VendorIcon string `json:"vendor_icon,omitempty"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
Share float64 `json:"share"`
|
||||
GrowthPct float64 `json:"growth_pct"`
|
||||
ModelsCount int `json:"models_count"`
|
||||
TopModel string `json:"top_model"`
|
||||
}
|
||||
|
||||
type RankingMover struct {
|
||||
ModelName string `json:"model_name"`
|
||||
Vendor string `json:"vendor"`
|
||||
VendorIcon string `json:"vendor_icon,omitempty"`
|
||||
RankDelta int `json:"rank_delta"`
|
||||
CurrentRank int `json:"current_rank"`
|
||||
GrowthPct float64 `json:"growth_pct"`
|
||||
}
|
||||
|
||||
type ModelHistoryPoint struct {
|
||||
Ts string `json:"ts"`
|
||||
Label string `json:"label"`
|
||||
Model string `json:"model"`
|
||||
Vendor string `json:"vendor"`
|
||||
Tokens int64 `json:"tokens"`
|
||||
}
|
||||
|
||||
type ModelHistoryModel struct {
|
||||
Name string `json:"name"`
|
||||
Vendor string `json:"vendor"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type ModelHistorySeries struct {
|
||||
Points []ModelHistoryPoint `json:"points"`
|
||||
Models []ModelHistoryModel `json:"models"`
|
||||
Buckets int `json:"buckets"`
|
||||
}
|
||||
|
||||
type VendorSharePoint struct {
|
||||
Ts string `json:"ts"`
|
||||
Label string `json:"label"`
|
||||
Vendor string `json:"vendor"`
|
||||
Share float64 `json:"share"`
|
||||
Tokens int64 `json:"tokens"`
|
||||
}
|
||||
|
||||
type VendorShareVendor struct {
|
||||
Name string `json:"name"`
|
||||
Total int64 `json:"total"`
|
||||
Share float64 `json:"share"`
|
||||
}
|
||||
|
||||
type VendorShareSeries struct {
|
||||
Points []VendorSharePoint `json:"points"`
|
||||
Vendors []VendorShareVendor `json:"vendors"`
|
||||
Buckets int `json:"buckets"`
|
||||
}
|
||||
|
||||
type rankingPeriodConfig struct {
|
||||
id string
|
||||
duration time.Duration
|
||||
bucketSize int64
|
||||
labelLayout string
|
||||
hasPrevious bool
|
||||
}
|
||||
|
||||
type rankingCacheItem struct {
|
||||
expiresAt time.Time
|
||||
data *RankingsResponse
|
||||
}
|
||||
|
||||
type rankingModelMeta struct {
|
||||
vendor string
|
||||
vendorIcon string
|
||||
}
|
||||
|
||||
type vendorAggregate struct {
|
||||
name string
|
||||
icon string
|
||||
totalTokens int64
|
||||
previousTokens int64
|
||||
models map[string]struct{}
|
||||
topModel string
|
||||
topModelTokens int64
|
||||
}
|
||||
|
||||
var (
|
||||
rankingCacheMu sync.Mutex
|
||||
rankingCache = map[string]rankingCacheItem{}
|
||||
)
|
||||
|
||||
func GetRankingsSnapshot(period string) (*RankingsResponse, error) {
|
||||
config, err := rankingConfig(period)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
rankingCacheMu.Lock()
|
||||
if item, ok := rankingCache[config.id]; ok && now.Before(item.expiresAt) {
|
||||
rankingCacheMu.Unlock()
|
||||
return item.data, nil
|
||||
}
|
||||
rankingCacheMu.Unlock()
|
||||
|
||||
data, err := buildRankingsSnapshot(config, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rankingCacheMu.Lock()
|
||||
rankingCache[config.id] = rankingCacheItem{
|
||||
expiresAt: now.Add(rankingCacheTTL),
|
||||
data: data,
|
||||
}
|
||||
rankingCacheMu.Unlock()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func rankingConfig(period string) (rankingPeriodConfig, error) {
|
||||
switch period {
|
||||
case "", "week":
|
||||
return rankingPeriodConfig{id: "week", duration: 7 * 24 * time.Hour, bucketSize: 24 * 3600, labelLayout: "Jan 2", hasPrevious: true}, nil
|
||||
case "today":
|
||||
return rankingPeriodConfig{id: "today", duration: 24 * time.Hour, bucketSize: 3600, labelLayout: "15:04", hasPrevious: true}, nil
|
||||
case "month":
|
||||
return rankingPeriodConfig{id: "month", duration: 30 * 24 * time.Hour, bucketSize: 24 * 3600, labelLayout: "Jan 2", hasPrevious: true}, nil
|
||||
case "year":
|
||||
return rankingPeriodConfig{id: "year", duration: 365 * 24 * time.Hour, bucketSize: 7 * 24 * 3600, labelLayout: "Jan 2", hasPrevious: true}, nil
|
||||
case "all":
|
||||
return rankingPeriodConfig{id: "all", bucketSize: 30 * 24 * 3600, labelLayout: "Jan 2006"}, nil
|
||||
default:
|
||||
return rankingPeriodConfig{}, fmt.Errorf("invalid ranking period: %s", period)
|
||||
}
|
||||
}
|
||||
|
||||
func buildRankingsSnapshot(config rankingPeriodConfig, now time.Time) (*RankingsResponse, error) {
|
||||
startTime, endTime := rankingTimeRange(config, now)
|
||||
currentTotals, err := model.GetRankingQuotaTotals(startTime, endTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentBuckets, err := model.GetRankingQuotaBuckets(startTime, endTime, config.bucketSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var previousTotals []model.RankingQuotaTotal
|
||||
if config.hasPrevious {
|
||||
previousStart, previousEnd := previousRankingTimeRange(config, startTime)
|
||||
previousTotals, err = model.GetRankingQuotaTotals(previousStart, previousEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
meta := buildRankingModelMeta()
|
||||
totalTokens := sumRankingTokens(currentTotals)
|
||||
previousRankByModel := rankingRankMap(previousTotals)
|
||||
previousTokensByModel := rankingTokenMap(previousTotals)
|
||||
|
||||
rankedModels := buildRankedModels(currentTotals, totalTokens, previousRankByModel, previousTokensByModel, meta, config.hasPrevious)
|
||||
vendors := buildRankedVendors(currentTotals, previousTotals, totalTokens, meta, config.hasPrevious)
|
||||
modelHistory := buildModelHistory(currentBuckets, currentTotals, meta, config)
|
||||
vendorHistory := buildVendorShareHistory(currentBuckets, vendors, totalTokens, meta, config)
|
||||
movers, droppers := buildRankingMovers(rankedModels)
|
||||
|
||||
return &RankingsResponse{
|
||||
Models: limitRankedModels(rankedModels, rankingLeaderboardLimit),
|
||||
Vendors: vendors,
|
||||
TopMovers: movers,
|
||||
TopDroppers: droppers,
|
||||
ModelsHistory: modelHistory,
|
||||
VendorShareHistory: vendorHistory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func rankingTimeRange(config rankingPeriodConfig, now time.Time) (int64, int64) {
|
||||
endTime := now.Unix()
|
||||
if config.duration <= 0 {
|
||||
return 0, endTime
|
||||
}
|
||||
return now.Add(-config.duration).Unix(), endTime
|
||||
}
|
||||
|
||||
func previousRankingTimeRange(config rankingPeriodConfig, currentStart int64) (int64, int64) {
|
||||
previousEnd := currentStart - 1
|
||||
previousStart := time.Unix(currentStart, 0).Add(-config.duration).Unix()
|
||||
return previousStart, previousEnd
|
||||
}
|
||||
|
||||
func buildRankingModelMeta() map[string]rankingModelMeta {
|
||||
vendorByID := make(map[int]model.PricingVendor)
|
||||
for _, vendor := range model.GetVendors() {
|
||||
vendorByID[vendor.ID] = vendor
|
||||
}
|
||||
|
||||
meta := make(map[string]rankingModelMeta)
|
||||
for _, pricing := range model.GetPricing() {
|
||||
item := rankingModelMeta{vendor: rankingUnknownVendor}
|
||||
if vendor, ok := vendorByID[pricing.VendorID]; ok {
|
||||
item.vendor = vendor.Name
|
||||
item.vendorIcon = vendor.Icon
|
||||
} else if pricing.OwnerBy != "" {
|
||||
item.vendor = pricing.OwnerBy
|
||||
}
|
||||
meta[pricing.ModelName] = item
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func modelMeta(modelName string, meta map[string]rankingModelMeta) rankingModelMeta {
|
||||
if item, ok := meta[modelName]; ok && item.vendor != "" {
|
||||
return item
|
||||
}
|
||||
return rankingModelMeta{vendor: rankingUnknownVendor}
|
||||
}
|
||||
|
||||
func buildRankedModels(totals []model.RankingQuotaTotal, totalTokens int64, previousRanks map[string]int, previousTokens map[string]int64, meta map[string]rankingModelMeta, showGrowth bool) []RankedModel {
|
||||
rows := make([]RankedModel, 0, len(totals))
|
||||
for idx, item := range totals {
|
||||
modelMeta := modelMeta(item.ModelName, meta)
|
||||
var previousRank *int
|
||||
if rank, ok := previousRanks[item.ModelName]; ok {
|
||||
rankCopy := rank
|
||||
previousRank = &rankCopy
|
||||
}
|
||||
growth := 0.0
|
||||
if showGrowth {
|
||||
growth = rankingGrowthPct(item.TotalTokens, previousTokens[item.ModelName])
|
||||
}
|
||||
rows = append(rows, RankedModel{
|
||||
Rank: idx + 1,
|
||||
PreviousRank: previousRank,
|
||||
ModelName: item.ModelName,
|
||||
Vendor: modelMeta.vendor,
|
||||
VendorIcon: modelMeta.vendorIcon,
|
||||
Category: "all",
|
||||
TotalTokens: item.TotalTokens,
|
||||
Share: rankingShare(item.TotalTokens, totalTokens),
|
||||
GrowthPct: growth,
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func buildRankedVendors(currentTotals []model.RankingQuotaTotal, previousTotals []model.RankingQuotaTotal, totalTokens int64, meta map[string]rankingModelMeta, showGrowth bool) []RankedVendor {
|
||||
aggregates := make(map[string]*vendorAggregate)
|
||||
for _, item := range currentTotals {
|
||||
modelMeta := modelMeta(item.ModelName, meta)
|
||||
agg := ensureVendorAggregate(aggregates, modelMeta)
|
||||
agg.totalTokens += item.TotalTokens
|
||||
agg.models[item.ModelName] = struct{}{}
|
||||
if item.TotalTokens > agg.topModelTokens {
|
||||
agg.topModel = item.ModelName
|
||||
agg.topModelTokens = item.TotalTokens
|
||||
}
|
||||
}
|
||||
for _, item := range previousTotals {
|
||||
modelMeta := modelMeta(item.ModelName, meta)
|
||||
agg := ensureVendorAggregate(aggregates, modelMeta)
|
||||
agg.previousTokens += item.TotalTokens
|
||||
}
|
||||
|
||||
rows := make([]RankedVendor, 0, len(aggregates))
|
||||
for _, agg := range aggregates {
|
||||
if agg.totalTokens <= 0 {
|
||||
continue
|
||||
}
|
||||
growth := 0.0
|
||||
if showGrowth {
|
||||
growth = rankingGrowthPct(agg.totalTokens, agg.previousTokens)
|
||||
}
|
||||
rows = append(rows, RankedVendor{
|
||||
Vendor: agg.name,
|
||||
VendorIcon: agg.icon,
|
||||
TotalTokens: agg.totalTokens,
|
||||
Share: rankingShare(agg.totalTokens, totalTokens),
|
||||
GrowthPct: growth,
|
||||
ModelsCount: len(agg.models),
|
||||
TopModel: agg.topModel,
|
||||
})
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if rows[i].TotalTokens == rows[j].TotalTokens {
|
||||
return rows[i].Vendor < rows[j].Vendor
|
||||
}
|
||||
return rows[i].TotalTokens > rows[j].TotalTokens
|
||||
})
|
||||
for idx := range rows {
|
||||
rows[idx].Rank = idx + 1
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func ensureVendorAggregate(aggregates map[string]*vendorAggregate, meta rankingModelMeta) *vendorAggregate {
|
||||
name := meta.vendor
|
||||
if name == "" {
|
||||
name = rankingUnknownVendor
|
||||
}
|
||||
agg, ok := aggregates[name]
|
||||
if !ok {
|
||||
agg = &vendorAggregate{
|
||||
name: name,
|
||||
icon: meta.vendorIcon,
|
||||
models: make(map[string]struct{}),
|
||||
}
|
||||
aggregates[name] = agg
|
||||
}
|
||||
if agg.icon == "" && meta.vendorIcon != "" {
|
||||
agg.icon = meta.vendorIcon
|
||||
}
|
||||
return agg
|
||||
}
|
||||
|
||||
func buildModelHistory(buckets []model.RankingQuotaBucket, totals []model.RankingQuotaTotal, meta map[string]rankingModelMeta, config rankingPeriodConfig) ModelHistorySeries {
|
||||
topModels := make(map[string]struct{})
|
||||
models := make([]ModelHistoryModel, 0, minInt(len(totals), rankingHistoryLimit)+1)
|
||||
otherTotal := int64(0)
|
||||
for idx, item := range totals {
|
||||
if idx < rankingHistoryLimit {
|
||||
topModels[item.ModelName] = struct{}{}
|
||||
modelMeta := modelMeta(item.ModelName, meta)
|
||||
models = append(models, ModelHistoryModel{Name: item.ModelName, Vendor: modelMeta.vendor, Total: item.TotalTokens})
|
||||
continue
|
||||
}
|
||||
otherTotal += item.TotalTokens
|
||||
}
|
||||
if otherTotal > 0 {
|
||||
models = append(models, ModelHistoryModel{Name: rankingOthersLabel, Vendor: "Various", Total: otherTotal})
|
||||
}
|
||||
|
||||
bucketSet := make(map[int64]struct{})
|
||||
tokensByBucketAndModel := make(map[int64]map[string]int64)
|
||||
for _, item := range buckets {
|
||||
modelName := item.ModelName
|
||||
if _, ok := topModels[modelName]; !ok {
|
||||
modelName = rankingOthersLabel
|
||||
}
|
||||
bucketSet[item.Bucket] = struct{}{}
|
||||
if _, ok := tokensByBucketAndModel[item.Bucket]; !ok {
|
||||
tokensByBucketAndModel[item.Bucket] = make(map[string]int64)
|
||||
}
|
||||
tokensByBucketAndModel[item.Bucket][modelName] += item.Tokens
|
||||
}
|
||||
|
||||
sortedBuckets := sortedRankingBuckets(bucketSet)
|
||||
points := make([]ModelHistoryPoint, 0, len(sortedBuckets)*len(models))
|
||||
for _, bucket := range sortedBuckets {
|
||||
for _, historyModel := range models {
|
||||
tokens := tokensByBucketAndModel[bucket][historyModel.Name]
|
||||
if tokens <= 0 {
|
||||
continue
|
||||
}
|
||||
points = append(points, ModelHistoryPoint{
|
||||
Ts: rankingBucketTs(bucket),
|
||||
Label: rankingBucketLabel(bucket, config),
|
||||
Model: historyModel.Name,
|
||||
Vendor: historyModel.Vendor,
|
||||
Tokens: tokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ModelHistorySeries{
|
||||
Points: points,
|
||||
Models: models,
|
||||
Buckets: len(sortedBuckets),
|
||||
}
|
||||
}
|
||||
|
||||
func buildVendorShareHistory(buckets []model.RankingQuotaBucket, vendors []RankedVendor, totalTokens int64, meta map[string]rankingModelMeta, config rankingPeriodConfig) VendorShareSeries {
|
||||
topVendors := make(map[string]struct{})
|
||||
vendorRows := make([]VendorShareVendor, 0, minInt(len(vendors), rankingVendorLimit)+1)
|
||||
otherTotal := int64(0)
|
||||
for idx, vendor := range vendors {
|
||||
if idx < rankingVendorLimit {
|
||||
topVendors[vendor.Vendor] = struct{}{}
|
||||
vendorRows = append(vendorRows, VendorShareVendor{Name: vendor.Vendor, Total: vendor.TotalTokens, Share: vendor.Share})
|
||||
continue
|
||||
}
|
||||
otherTotal += vendor.TotalTokens
|
||||
}
|
||||
if otherTotal > 0 {
|
||||
vendorRows = append(vendorRows, VendorShareVendor{Name: rankingOthersLabel, Total: otherTotal, Share: rankingShare(otherTotal, totalTokens)})
|
||||
}
|
||||
|
||||
bucketSet := make(map[int64]struct{})
|
||||
tokensByBucketAndVendor := make(map[int64]map[string]int64)
|
||||
totalsByBucket := make(map[int64]int64)
|
||||
for _, item := range buckets {
|
||||
modelMeta := modelMeta(item.ModelName, meta)
|
||||
vendorName := modelMeta.vendor
|
||||
if _, ok := topVendors[vendorName]; !ok {
|
||||
vendorName = rankingOthersLabel
|
||||
}
|
||||
bucketSet[item.Bucket] = struct{}{}
|
||||
if _, ok := tokensByBucketAndVendor[item.Bucket]; !ok {
|
||||
tokensByBucketAndVendor[item.Bucket] = make(map[string]int64)
|
||||
}
|
||||
tokensByBucketAndVendor[item.Bucket][vendorName] += item.Tokens
|
||||
totalsByBucket[item.Bucket] += item.Tokens
|
||||
}
|
||||
|
||||
sortedBuckets := sortedRankingBuckets(bucketSet)
|
||||
points := make([]VendorSharePoint, 0, len(sortedBuckets)*len(vendorRows))
|
||||
for _, bucket := range sortedBuckets {
|
||||
for _, vendor := range vendorRows {
|
||||
tokens := tokensByBucketAndVendor[bucket][vendor.Name]
|
||||
if tokens <= 0 {
|
||||
continue
|
||||
}
|
||||
points = append(points, VendorSharePoint{
|
||||
Ts: rankingBucketTs(bucket),
|
||||
Label: rankingBucketLabel(bucket, config),
|
||||
Vendor: vendor.Name,
|
||||
Share: rankingShare(tokens, totalsByBucket[bucket]),
|
||||
Tokens: tokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return VendorShareSeries{
|
||||
Points: points,
|
||||
Vendors: vendorRows,
|
||||
Buckets: len(sortedBuckets),
|
||||
}
|
||||
}
|
||||
|
||||
func buildRankingMovers(models []RankedModel) ([]RankingMover, []RankingMover) {
|
||||
movers := make([]RankingMover, 0)
|
||||
droppers := make([]RankingMover, 0)
|
||||
for _, item := range models {
|
||||
if item.PreviousRank == nil {
|
||||
continue
|
||||
}
|
||||
delta := *item.PreviousRank - item.Rank
|
||||
if delta == 0 {
|
||||
continue
|
||||
}
|
||||
row := RankingMover{
|
||||
ModelName: item.ModelName,
|
||||
Vendor: item.Vendor,
|
||||
VendorIcon: item.VendorIcon,
|
||||
RankDelta: delta,
|
||||
CurrentRank: item.Rank,
|
||||
GrowthPct: item.GrowthPct,
|
||||
}
|
||||
if delta > 0 {
|
||||
movers = append(movers, row)
|
||||
} else {
|
||||
droppers = append(droppers, row)
|
||||
}
|
||||
}
|
||||
sort.Slice(movers, func(i, j int) bool {
|
||||
if movers[i].RankDelta == movers[j].RankDelta {
|
||||
return movers[i].GrowthPct > movers[j].GrowthPct
|
||||
}
|
||||
return movers[i].RankDelta > movers[j].RankDelta
|
||||
})
|
||||
sort.Slice(droppers, func(i, j int) bool {
|
||||
if droppers[i].RankDelta == droppers[j].RankDelta {
|
||||
return droppers[i].GrowthPct < droppers[j].GrowthPct
|
||||
}
|
||||
return droppers[i].RankDelta < droppers[j].RankDelta
|
||||
})
|
||||
return limitRankingMovers(movers, rankingMoverLimit), limitRankingMovers(droppers, rankingMoverLimit)
|
||||
}
|
||||
|
||||
func sortedRankingBuckets(bucketSet map[int64]struct{}) []int64 {
|
||||
buckets := make([]int64, 0, len(bucketSet))
|
||||
for bucket := range bucketSet {
|
||||
buckets = append(buckets, bucket)
|
||||
}
|
||||
sort.Slice(buckets, func(i, j int) bool {
|
||||
return buckets[i] < buckets[j]
|
||||
})
|
||||
return buckets
|
||||
}
|
||||
|
||||
func rankingBucketTs(bucket int64) string {
|
||||
return time.Unix(bucket, 0).UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func rankingBucketLabel(bucket int64, config rankingPeriodConfig) string {
|
||||
return time.Unix(bucket, 0).Format(config.labelLayout)
|
||||
}
|
||||
|
||||
func rankingRankMap(totals []model.RankingQuotaTotal) map[string]int {
|
||||
ranks := make(map[string]int, len(totals))
|
||||
for idx, item := range totals {
|
||||
ranks[item.ModelName] = idx + 1
|
||||
}
|
||||
return ranks
|
||||
}
|
||||
|
||||
func rankingTokenMap(totals []model.RankingQuotaTotal) map[string]int64 {
|
||||
tokens := make(map[string]int64, len(totals))
|
||||
for _, item := range totals {
|
||||
tokens[item.ModelName] = item.TotalTokens
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
func sumRankingTokens(totals []model.RankingQuotaTotal) int64 {
|
||||
total := int64(0)
|
||||
for _, item := range totals {
|
||||
total += item.TotalTokens
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func rankingShare(value int64, total int64) float64 {
|
||||
if total <= 0 || value <= 0 {
|
||||
return 0
|
||||
}
|
||||
return roundRankingFloat(float64(value) / float64(total))
|
||||
}
|
||||
|
||||
func rankingGrowthPct(current int64, previous int64) float64 {
|
||||
if previous <= 0 {
|
||||
if current > 0 {
|
||||
return 100
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return roundRankingFloat((float64(current-previous) / float64(previous)) * 100)
|
||||
}
|
||||
|
||||
func roundRankingFloat(value float64) float64 {
|
||||
return math.Round(value*10000) / 10000
|
||||
}
|
||||
|
||||
func limitRankedModels(rows []RankedModel, limit int) []RankedModel {
|
||||
if limit <= 0 || len(rows) <= limit {
|
||||
return rows
|
||||
}
|
||||
return rows[:limit]
|
||||
}
|
||||
|
||||
func limitRankingMovers(rows []RankingMover, limit int) []RankingMover {
|
||||
if limit <= 0 || len(rows) <= limit {
|
||||
return rows
|
||||
}
|
||||
return rows[:limit]
|
||||
}
|
||||
|
||||
func minInt(a int, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user