feat: pricing stat.
This commit is contained in:
@@ -4,6 +4,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context" // 在此处添加 context 导入
|
"context" // 在此处添加 context 导入
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -390,11 +391,35 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算费用
|
||||||
|
var pricing models.AIPricingConfig
|
||||||
|
var costJSON string
|
||||||
|
var totalCost float64
|
||||||
|
if err := config.DB.First(&pricing).Error; err == nil {
|
||||||
|
inputCost := float64(analysisResult.InputTokens) * pricing.InputPricePerMillion / 1_000_000
|
||||||
|
outputCost := float64(analysisResult.OutputTokens) * pricing.OutputPricePerMillion / 1_000_000
|
||||||
|
totalCost = inputCost + outputCost
|
||||||
|
|
||||||
|
costInfo := map[string]interface{}{
|
||||||
|
"pricingName": pricing.Name,
|
||||||
|
"provider": pricing.Provider,
|
||||||
|
"inputPricePerMillion": pricing.InputPricePerMillion,
|
||||||
|
"outputPricePerMillion": pricing.OutputPricePerMillion,
|
||||||
|
"inputCost": inputCost,
|
||||||
|
"outputCost": outputCost,
|
||||||
|
}
|
||||||
|
if b, err := json.Marshal(costInfo); err == nil {
|
||||||
|
costJSON = string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
record := models.AIAnalysisRecord{
|
record := models.AIAnalysisRecord{
|
||||||
RegionID: regionID,
|
RegionID: regionID,
|
||||||
SourceType: teachingPlanSource,
|
SourceType: teachingPlanSource,
|
||||||
AnalysisType: analysisType,
|
AnalysisType: analysisType,
|
||||||
AnalysisResult: analysisResult.Content,
|
AnalysisResult: analysisResult.Content,
|
||||||
|
CostJSON: costJSON,
|
||||||
|
TotalCost: totalCost,
|
||||||
InputTokens: analysisResult.InputTokens,
|
InputTokens: analysisResult.InputTokens,
|
||||||
OutputTokens: analysisResult.OutputTokens,
|
OutputTokens: analysisResult.OutputTokens,
|
||||||
InputSizeBytes: analysisResult.InputSizeBytes,
|
InputSizeBytes: analysisResult.InputSizeBytes,
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ type regionStatisticsItem struct {
|
|||||||
AvgDurationMs float64 `json:"avgDurationMs"`
|
AvgDurationMs float64 `json:"avgDurationMs"`
|
||||||
TotalOriginalFileSize int64 `json:"totalOriginalFileSize"`
|
TotalOriginalFileSize int64 `json:"totalOriginalFileSize"`
|
||||||
TotalCompressedSize int64 `json:"totalCompressedSize"`
|
TotalCompressedSize int64 `json:"totalCompressedSize"`
|
||||||
|
TotalCost float64 `json:"totalCost"`
|
||||||
|
TotalInputCost float64 `json:"totalInputCost"`
|
||||||
|
TotalOutputCost float64 `json:"totalOutputCost"`
|
||||||
AnalysisTypeCounts map[string]int64 `json:"analysisTypeCounts"`
|
AnalysisTypeCounts map[string]int64 `json:"analysisTypeCounts"`
|
||||||
SourceTypeCounts map[string]int64 `json:"sourceTypeCounts"`
|
SourceTypeCounts map[string]int64 `json:"sourceTypeCounts"`
|
||||||
FirstUsedAt *time.Time `json:"firstUsedAt"`
|
FirstUsedAt *time.Time `json:"firstUsedAt"`
|
||||||
@@ -161,6 +164,9 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
|
|||||||
TotalDurationMs int64
|
TotalDurationMs int64
|
||||||
TotalOriginalFileSize int64
|
TotalOriginalFileSize int64
|
||||||
TotalCompressedSize int64
|
TotalCompressedSize int64
|
||||||
|
TotalCost float64
|
||||||
|
TotalInputCost float64
|
||||||
|
TotalOutputCost float64
|
||||||
FirstUsedAt *int64
|
FirstUsedAt *int64
|
||||||
LastUsedAt *int64
|
LastUsedAt *int64
|
||||||
}
|
}
|
||||||
@@ -176,6 +182,9 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
|
|||||||
COALESCE(SUM(duration_ms), 0) as total_duration_ms,
|
COALESCE(SUM(duration_ms), 0) as total_duration_ms,
|
||||||
COALESCE(SUM(original_file_size), 0) as total_original_file_size,
|
COALESCE(SUM(original_file_size), 0) as total_original_file_size,
|
||||||
COALESCE(SUM(compressed_content_size), 0) as total_compressed_size,
|
COALESCE(SUM(compressed_content_size), 0) as total_compressed_size,
|
||||||
|
COALESCE(SUM(total_cost), 0) as total_cost,
|
||||||
|
COALESCE(SUM((cost_json::jsonb->>'inputCost')::float8), 0) as total_input_cost,
|
||||||
|
COALESCE(SUM((cost_json::jsonb->>'outputCost')::float8), 0) as total_output_cost,
|
||||||
MIN(upload_time) as first_used_at,
|
MIN(upload_time) as first_used_at,
|
||||||
MAX(upload_time) as last_used_at
|
MAX(upload_time) as last_used_at
|
||||||
`).Group("region_id").Scan(&rawResults).Error
|
`).Group("region_id").Scan(&rawResults).Error
|
||||||
@@ -290,6 +299,9 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
|
|||||||
AvgDurationMs: avgDuration,
|
AvgDurationMs: avgDuration,
|
||||||
TotalOriginalFileSize: r.TotalOriginalFileSize,
|
TotalOriginalFileSize: r.TotalOriginalFileSize,
|
||||||
TotalCompressedSize: r.TotalCompressedSize,
|
TotalCompressedSize: r.TotalCompressedSize,
|
||||||
|
TotalCost: r.TotalCost,
|
||||||
|
TotalInputCost: r.TotalInputCost,
|
||||||
|
TotalOutputCost: r.TotalOutputCost,
|
||||||
AnalysisTypeCounts: analysisTypeMap[regionID],
|
AnalysisTypeCounts: analysisTypeMap[regionID],
|
||||||
SourceTypeCounts: sourceTypeMap[regionID],
|
SourceTypeCounts: sourceTypeMap[regionID],
|
||||||
FirstUsedAt: firstUsedAt,
|
FirstUsedAt: firstUsedAt,
|
||||||
@@ -306,6 +318,9 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) {
|
|||||||
overall.TotalDurationMs += r.TotalDurationMs
|
overall.TotalDurationMs += r.TotalDurationMs
|
||||||
overall.TotalOriginalFileSize += r.TotalOriginalFileSize
|
overall.TotalOriginalFileSize += r.TotalOriginalFileSize
|
||||||
overall.TotalCompressedSize += r.TotalCompressedSize
|
overall.TotalCompressedSize += r.TotalCompressedSize
|
||||||
|
overall.TotalCost += r.TotalCost
|
||||||
|
overall.TotalInputCost += r.TotalInputCost
|
||||||
|
overall.TotalOutputCost += r.TotalOutputCost
|
||||||
|
|
||||||
if firstUsedAt != nil {
|
if firstUsedAt != nil {
|
||||||
if overall.FirstUsedAt == nil || firstUsedAt.Before(*overall.FirstUsedAt) {
|
if overall.FirstUsedAt == nil || firstUsedAt.Before(*overall.FirstUsedAt) {
|
||||||
@@ -363,6 +378,7 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
InputTokens int64 `json:"inputTokens"`
|
InputTokens int64 `json:"inputTokens"`
|
||||||
OutputTokens int64 `json:"outputTokens"`
|
OutputTokens int64 `json:"outputTokens"`
|
||||||
|
TotalCost float64 `json:"totalCost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rawRegionTimeline struct {
|
type rawRegionTimeline struct {
|
||||||
@@ -371,6 +387,7 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
Count int64
|
Count int64
|
||||||
InputTokens int64
|
InputTokens int64
|
||||||
OutputTokens int64
|
OutputTokens int64
|
||||||
|
TotalCost float64
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawResults []rawRegionTimeline
|
var rawResults []rawRegionTimeline
|
||||||
@@ -379,7 +396,8 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
DATE(TO_TIMESTAMP(upload_time / 1000.0)) as date,
|
DATE(TO_TIMESTAMP(upload_time / 1000.0)) as date,
|
||||||
COUNT(*) as count,
|
COUNT(*) as count,
|
||||||
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
||||||
COALESCE(SUM(output_tokens), 0) as output_tokens
|
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
||||||
|
COALESCE(SUM(total_cost), 0) as total_cost
|
||||||
`).Group("region_id, DATE(TO_TIMESTAMP(upload_time / 1000.0))").Order("region_id, date ASC").Scan(&rawResults).Error
|
`).Group("region_id, DATE(TO_TIMESTAMP(upload_time / 1000.0))").Order("region_id, date ASC").Scan(&rawResults).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -399,6 +417,7 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
overallMap[r.Date].Count += r.Count
|
overallMap[r.Date].Count += r.Count
|
||||||
overallMap[r.Date].InputTokens += r.InputTokens
|
overallMap[r.Date].InputTokens += r.InputTokens
|
||||||
overallMap[r.Date].OutputTokens += r.OutputTokens
|
overallMap[r.Date].OutputTokens += r.OutputTokens
|
||||||
|
overallMap[r.Date].TotalCost += r.TotalCost
|
||||||
|
|
||||||
regionID := uint32(0)
|
regionID := uint32(0)
|
||||||
if r.RegionID != nil {
|
if r.RegionID != nil {
|
||||||
@@ -410,6 +429,7 @@ func (sc *StatisticsController) TimelineStatistics(c *gin.Context) {
|
|||||||
Count: r.Count,
|
Count: r.Count,
|
||||||
InputTokens: r.InputTokens,
|
InputTokens: r.InputTokens,
|
||||||
OutputTokens: r.OutputTokens,
|
OutputTokens: r.OutputTokens,
|
||||||
|
TotalCost: r.TotalCost,
|
||||||
})
|
})
|
||||||
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
|
if _, ok := regionIDSet[regionID]; !ok && regionID > 0 {
|
||||||
regionIDSet[regionID] = struct{}{}
|
regionIDSet[regionID] = struct{}{}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ func main() {
|
|||||||
&models.MqttTrainingSessionRecord{},
|
&models.MqttTrainingSessionRecord{},
|
||||||
&models.Gateway{},
|
&models.Gateway{},
|
||||||
&models.AIAnalysisRecord{},
|
&models.AIAnalysisRecord{},
|
||||||
|
&models.AIPricingConfig{},
|
||||||
)
|
)
|
||||||
if err := models.BackfillLegacyUserPermissions(config.DB); err != nil {
|
if err := models.BackfillLegacyUserPermissions(config.DB); err != nil {
|
||||||
log.Printf("legacy user permission backfill failed: %v", err)
|
log.Printf("legacy user permission backfill failed: %v", err)
|
||||||
@@ -45,6 +46,9 @@ func main() {
|
|||||||
if err := models.EnsureDefaultAdmin(config.DB); err != nil {
|
if err := models.EnsureDefaultAdmin(config.DB); err != nil {
|
||||||
log.Printf("default admin init failed: %v", err)
|
log.Printf("default admin init failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := models.EnsureDefaultAIPricing(config.DB); err != nil {
|
||||||
|
log.Printf("default ai pricing init failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := mqtt.Start(config.DB, config.App.MQTT); err != nil {
|
if err := mqtt.Start(config.DB, config.App.MQTT); err != nil {
|
||||||
log.Printf("mqtt listener start failed: %v", err)
|
log.Printf("mqtt listener start failed: %v", err)
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
type AIAnalysisRecord struct {
|
type AIAnalysisRecord struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
@@ -8,6 +10,8 @@ type AIAnalysisRecord struct {
|
|||||||
SourceType string `gorm:"size:32" json:"sourceType"`
|
SourceType string `gorm:"size:32" json:"sourceType"`
|
||||||
AnalysisType string `gorm:"size:32" json:"analysisType"`
|
AnalysisType string `gorm:"size:32" json:"analysisType"`
|
||||||
AnalysisResult string `gorm:"type:text" json:"analysisResult"`
|
AnalysisResult string `gorm:"type:text" json:"analysisResult"`
|
||||||
|
CostJSON string `gorm:"type:jsonb" json:"costJson"`
|
||||||
|
TotalCost float64 `json:"totalCost"`
|
||||||
InputTokens int `json:"inputTokens"`
|
InputTokens int `json:"inputTokens"`
|
||||||
OutputTokens int `json:"outputTokens"`
|
OutputTokens int `json:"outputTokens"`
|
||||||
InputSizeBytes int `json:"inputSizeBytes"`
|
InputSizeBytes int `json:"inputSizeBytes"`
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AIPricingConfig struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string `gorm:"size:64;uniqueIndex" json:"name"`
|
||||||
|
Provider string `gorm:"size:64" json:"provider"`
|
||||||
|
InputPricePerMillion float64 `json:"inputPricePerMillion"`
|
||||||
|
OutputPricePerMillion float64 `json:"outputPricePerMillion"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnsureDefaultAIPricing(db *gorm.DB) error {
|
||||||
|
var count int64
|
||||||
|
if err := db.Model(&AIPricingConfig{}).Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Create(&AIPricingConfig{
|
||||||
|
Name: "deepseek-v4-flash",
|
||||||
|
Provider: "tencentmaas",
|
||||||
|
InputPricePerMillion: 1,
|
||||||
|
OutputPricePerMillion: 2,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user