ccm/ocm: Add by_user_and_week cost summary

This commit is contained in:
世界
2026-03-10 20:58:59 +08:00
parent e0be8743f6
commit a09ffe6a0f
2 changed files with 68 additions and 6 deletions

View File

@@ -65,9 +65,10 @@ type CostCombinationJSON struct {
}
type CostsSummaryJSON struct {
TotalUSD float64 `json:"total_usd"`
ByUser map[string]float64 `json:"by_user"`
ByWeek map[string]float64 `json:"by_week,omitempty"`
TotalUSD float64 `json:"total_usd"`
ByUser map[string]float64 `json:"by_user"`
ByWeek map[string]float64 `json:"by_week,omitempty"`
ByUserAndWeek map[string]map[string]float64 `json:"by_user_and_week,omitempty"`
}
type AggregatedUsageJSON struct {
@@ -492,6 +493,31 @@ func buildByWeekCost(combinations []CostCombination) map[string]float64 {
return byWeek
}
func buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 {
byUserAndWeek := make(map[string]map[string]float64)
for _, combination := range combinations {
if combination.WeekStartUnix <= 0 {
continue
}
weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()
weekKey := formatWeekStartKey(weekStartAt)
for user, userStats := range combination.ByUser {
userWeeks, exists := byUserAndWeek[user]
if !exists {
userWeeks = make(map[string]float64)
byUserAndWeek[user] = userWeeks
}
userWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ContextWindow)
}
}
for _, weekCosts := range byUserAndWeek {
for weekKey, cost := range weekCosts {
weekCosts[weekKey] = roundCost(cost)
}
}
return byUserAndWeek
}
func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {
if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {
return 0
@@ -522,6 +548,11 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
result.Costs.ByWeek = nil
}
result.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations)
if len(result.Costs.ByUserAndWeek) == 0 {
result.Costs.ByUserAndWeek = nil
}
for user, cost := range result.Costs.ByUser {
result.Costs.ByUser[user] = roundCost(cost)
}

View File

@@ -80,9 +80,10 @@ type CostCombinationJSON struct {
}
type CostsSummaryJSON struct {
TotalUSD float64 `json:"total_usd"`
ByUser map[string]float64 `json:"by_user"`
ByWeek map[string]float64 `json:"by_week,omitempty"`
TotalUSD float64 `json:"total_usd"`
ByUser map[string]float64 `json:"by_user"`
ByWeek map[string]float64 `json:"by_week,omitempty"`
ByUserAndWeek map[string]map[string]float64 `json:"by_user_and_week,omitempty"`
}
type AggregatedUsageJSON struct {
@@ -864,6 +865,31 @@ func buildByWeekCost(combinations []CostCombination) map[string]float64 {
return byWeek
}
func buildByUserAndWeekCost(combinations []CostCombination) map[string]map[string]float64 {
byUserAndWeek := make(map[string]map[string]float64)
for _, combination := range combinations {
if combination.WeekStartUnix <= 0 {
continue
}
weekStartAt := time.Unix(combination.WeekStartUnix, 0).UTC()
weekKey := formatWeekStartKey(weekStartAt)
for user, userStats := range combination.ByUser {
userWeeks, exists := byUserAndWeek[user]
if !exists {
userWeeks = make(map[string]float64)
byUserAndWeek[user] = userWeeks
}
userWeeks[weekKey] += calculateCost(userStats, combination.Model, combination.ServiceTier)
}
}
for _, weekCosts := range byUserAndWeek {
for weekKey, cost := range weekCosts {
weekCosts[weekKey] = roundCost(cost)
}
}
return byUserAndWeek
}
func deriveWeekStartUnix(cycleHint *WeeklyCycleHint) int64 {
if cycleHint == nil || cycleHint.WindowMinutes <= 0 || cycleHint.ResetAt.IsZero() {
return 0
@@ -894,6 +920,11 @@ func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON {
result.Costs.ByWeek = nil
}
result.Costs.ByUserAndWeek = buildByUserAndWeekCost(u.Combinations)
if len(result.Costs.ByUserAndWeek) == 0 {
result.Costs.ByUserAndWeek = nil
}
for user, cost := range result.Costs.ByUser {
result.Costs.ByUser[user] = roundCost(cost)
}