Files
ttstd-infinite-canvas/backend/internal/app/analytics_finance.go

91 lines
3.1 KiB
Go

package app
import "infinite-canvas/backend/internal/model"
type AnalyticsFinance struct {
SettledOrders int `json:"settledOrders"`
CostedOrders int `json:"costedOrders"`
RevenueMicrocredits int64 `json:"revenueMicrocredits"`
CostMicrocredits int64 `json:"costMicrocredits"`
ProfitMicrocredits *int64 `json:"profitMicrocredits"`
ProfitMargin *float64 `json:"profitMargin"`
}
type analyticsFinancialRecord struct {
Log model.ApiCallLog
Revenue int64
Cost *int64
}
// 一个订单的重试、轮询不能重复计入收入;只归属实际结算渠道。
// 历史订单使用下单价格快照,缺失成本时不反查当前价格。
func (s *Service) analyticsFinancialRecords(logs []model.ApiCallLog) (map[string]analyticsFinancialRecord, error) {
ids := make([]string, 0, len(logs))
for _, log := range logs {
if log.Billable && log.BillingOrderID != "" {
ids = append(ids, log.BillingOrderID)
}
}
orders, err := s.repo.BillingOrdersByIDs(uniqueNonEmpty(ids))
if err != nil {
return nil, err
}
byOrder := make(map[string]model.ApiCallLog)
for _, log := range logs {
order, exists := orders[log.BillingOrderID]
if !log.Billable || !exists || order.UserID != log.UserID || order.ChannelID != log.ChannelID || order.Status != model.BillingStatusSettled {
continue
}
switch log.RequestKind {
case "poll", "download", "upload", "workflow-schema", "cancel-query", "cancel":
continue
}
previous, exists := byOrder[order.ID]
if !exists || log.CreatedAt.Before(previous.CreatedAt) || (log.CreatedAt.Equal(previous.CreatedAt) && log.ID < previous.ID) {
byOrder[order.ID] = log
}
}
records := make(map[string]analyticsFinancialRecord, len(byOrder))
for id, log := range byOrder {
order := orders[id]
cost, err := billingCreditCost(order)
if err != nil {
return nil, err
}
records[log.ID] = analyticsFinancialRecord{Log: log, Revenue: order.ActualAmountMicrocredits, Cost: cost}
}
return records, nil
}
func (finance *AnalyticsFinance) add(record analyticsFinancialRecord) {
finance.SettledOrders++
finance.RevenueMicrocredits += record.Revenue
if record.Cost != nil {
finance.CostedOrders++
finance.CostMicrocredits += *record.Cost
}
finance.ProfitMicrocredits, finance.ProfitMargin = nil, nil
if finance.CostedOrders == finance.SettledOrders {
profit := finance.RevenueMicrocredits - finance.CostMicrocredits
finance.ProfitMicrocredits = &profit
if finance.RevenueMicrocredits > 0 {
margin := float64(profit) * 100 / float64(finance.RevenueMicrocredits)
finance.ProfitMargin = &margin
}
}
}
func applyAnalyticsFinance(overview *AnalyticsOverview, records map[string]analyticsFinancialRecord) {
rows := make(map[[2]string]*AnalyticsFinance, len(overview.Models))
for index := range overview.Models {
row := &overview.Models[index]
rows[[2]string{row.Model, row.Capability}] = &row.Finance
}
for _, record := range records {
overview.KPI.Finance.add(record)
if row := rows[[2]string{firstNonEmpty(record.Log.Model, "未识别"), record.Log.Capability}]; row != nil {
row.add(record)
}
}
}