mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
fix(api): handle zero-baseline edge cases in trend calculation
Introduce `CalculateTrendFloatWithBaseline` to correctly handle scenarios where previous period metrics (Error Rate, Latency) are zero or missing. This prevents arithmetic errors and distinguishes between "new" data and actual increases ("up") when starting from zero.
Also updates the admin panel dashboard documentation to reflect current project status.
This commit is contained in:
@@ -89,6 +89,31 @@ func CalculateTrendFloat(current, previous float64) TrendInfo {
|
||||
return TrendInfo{Delta: &delta, Direction: direction}
|
||||
}
|
||||
|
||||
// CalculateTrendFloatWithBaseline calculates trend when baseline existence is explicit.
|
||||
func CalculateTrendFloatWithBaseline(current, previous float64, hasBaseline bool) TrendInfo {
|
||||
if !hasBaseline {
|
||||
if current == 0 {
|
||||
return TrendInfo{Direction: "stable"}
|
||||
}
|
||||
return TrendInfo{Direction: "new"}
|
||||
}
|
||||
if previous == 0 {
|
||||
if current == 0 {
|
||||
return TrendInfo{Direction: "stable"}
|
||||
}
|
||||
return TrendInfo{Direction: "up"}
|
||||
}
|
||||
delta := (current - previous) / previous * 100
|
||||
delta = math.Round(delta*10) / 10
|
||||
direction := "stable"
|
||||
if delta > 0.5 {
|
||||
direction = "up"
|
||||
} else if delta < -0.5 {
|
||||
direction = "down"
|
||||
}
|
||||
return TrendInfo{Delta: &delta, Direction: direction}
|
||||
}
|
||||
|
||||
// RequestStats contains request-related statistics
|
||||
type RequestStats struct {
|
||||
Total int64 `json:"total"`
|
||||
@@ -302,20 +327,21 @@ func (h *DashboardHandler) GetSummary(c *gin.Context) {
|
||||
if !prevStart.IsZero() && !prevEnd.IsZero() {
|
||||
prevStats, err := h.aggregateFromLogRecords(prevStart, prevEnd)
|
||||
if err == nil {
|
||||
hasBaseline := prevStats.Requests > 0
|
||||
prevErrorRate := 0.0
|
||||
if prevStats.Requests > 0 {
|
||||
if hasBaseline {
|
||||
prevErrorRate = float64(prevStats.Failed) / float64(prevStats.Requests)
|
||||
}
|
||||
prevAvgLatency := 0.0
|
||||
if prevStats.Requests > 0 {
|
||||
if hasBaseline {
|
||||
prevAvgLatency = float64(prevStats.LatencySumMs) / float64(prevStats.Requests)
|
||||
}
|
||||
|
||||
trends = &DashboardTrends{
|
||||
Requests: CalculateTrend(totalRequests, prevStats.Requests),
|
||||
Tokens: CalculateTrend(ts.TokensIn+ts.TokensOut, prevStats.TokensIn+prevStats.TokensOut),
|
||||
ErrorRate: CalculateTrendFloat(errorRate, prevErrorRate),
|
||||
Latency: CalculateTrendFloat(ts.AvgLatency, prevAvgLatency),
|
||||
ErrorRate: CalculateTrendFloatWithBaseline(errorRate, prevErrorRate, hasBaseline),
|
||||
Latency: CalculateTrendFloatWithBaseline(ts.AvgLatency, prevAvgLatency, hasBaseline),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,88 @@ func TestCalculateTrendFloat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateTrendFloatWithBaseline(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
current float64
|
||||
previous float64
|
||||
hasBaseline bool
|
||||
wantDelta *float64
|
||||
wantDirection string
|
||||
}{
|
||||
{
|
||||
name: "no baseline, zero current",
|
||||
current: 0,
|
||||
previous: 0,
|
||||
hasBaseline: false,
|
||||
wantDelta: nil,
|
||||
wantDirection: "stable",
|
||||
},
|
||||
{
|
||||
name: "no baseline, positive current",
|
||||
current: 0.2,
|
||||
previous: 0,
|
||||
hasBaseline: false,
|
||||
wantDelta: nil,
|
||||
wantDirection: "new",
|
||||
},
|
||||
{
|
||||
name: "baseline present, zero previous and current",
|
||||
current: 0,
|
||||
previous: 0,
|
||||
hasBaseline: true,
|
||||
wantDelta: nil,
|
||||
wantDirection: "stable",
|
||||
},
|
||||
{
|
||||
name: "baseline present, zero previous to positive",
|
||||
current: 0.2,
|
||||
previous: 0,
|
||||
hasBaseline: true,
|
||||
wantDelta: nil,
|
||||
wantDirection: "up",
|
||||
},
|
||||
{
|
||||
name: "baseline present, increase",
|
||||
current: 0.15,
|
||||
previous: 0.10,
|
||||
hasBaseline: true,
|
||||
wantDelta: ptr(50.0),
|
||||
wantDirection: "up",
|
||||
},
|
||||
{
|
||||
name: "baseline present, no change",
|
||||
current: 0.10,
|
||||
previous: 0.10,
|
||||
hasBaseline: true,
|
||||
wantDelta: ptr(0.0),
|
||||
wantDirection: "stable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CalculateTrendFloatWithBaseline(tt.current, tt.previous, tt.hasBaseline)
|
||||
|
||||
if tt.wantDelta == nil {
|
||||
if got.Delta != nil {
|
||||
t.Errorf("expected nil delta, got %v", *got.Delta)
|
||||
}
|
||||
} else {
|
||||
if got.Delta == nil {
|
||||
t.Errorf("expected delta %v, got nil", *tt.wantDelta)
|
||||
} else if *got.Delta != *tt.wantDelta {
|
||||
t.Errorf("expected delta %v, got %v", *tt.wantDelta, *got.Delta)
|
||||
}
|
||||
}
|
||||
|
||||
if got.Direction != tt.wantDirection {
|
||||
t.Errorf("expected direction %q, got %q", tt.wantDirection, got.Direction)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreviousPeriodWindow(t *testing.T) {
|
||||
// These tests verify the structure of the period calculation
|
||||
// Not testing exact timestamps since they depend on current time
|
||||
|
||||
Reference in New Issue
Block a user