Initial commit
This commit is contained in:
76
internal/scheduler/badge.go
Normal file
76
internal/scheduler/badge.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"veola/internal/models"
|
||||
"veola/templates"
|
||||
)
|
||||
|
||||
// PickBadge returns the highest-priority deal-quality badge that applies to
|
||||
// an item, or an empty BadgeData if none match. Order:
|
||||
// 1. All-time low
|
||||
// 2. X% below 30-day avg (only when at least 10% below)
|
||||
// 3. X% below target
|
||||
func PickBadge(it models.Item, history []models.PricePoint, now time.Time) templates.BadgeData {
|
||||
if it.BestPrice == nil {
|
||||
return templates.BadgeData{}
|
||||
}
|
||||
best := *it.BestPrice
|
||||
|
||||
// 1. All-time low
|
||||
if isAllTimeLow(best, history) {
|
||||
return templates.BadgeData{Label: "All-time low", Class: "v-badge-low"}
|
||||
}
|
||||
|
||||
// 2. X% below 30-day average
|
||||
if avg, ok := windowedMean(history, now, 30*24*time.Hour); ok && best > 0 && avg > 0 {
|
||||
pct := (avg - best) / avg * 100
|
||||
if pct >= 10 {
|
||||
return templates.BadgeData{
|
||||
Label: fmt.Sprintf("%d%% below 30-day avg", int(pct+0.5)),
|
||||
Class: "v-badge-avg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. X% below target
|
||||
if it.TargetPrice != nil && *it.TargetPrice > 0 && best < *it.TargetPrice {
|
||||
pct := (*it.TargetPrice - best) / *it.TargetPrice * 100
|
||||
return templates.BadgeData{
|
||||
Label: fmt.Sprintf("%d%% below target", int(pct+0.5)),
|
||||
Class: "v-badge-target",
|
||||
}
|
||||
}
|
||||
|
||||
return templates.BadgeData{}
|
||||
}
|
||||
|
||||
func isAllTimeLow(best float64, history []models.PricePoint) bool {
|
||||
if len(history) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, p := range history {
|
||||
if p.Price > 0 && p.Price < best {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func windowedMean(history []models.PricePoint, now time.Time, window time.Duration) (float64, bool) {
|
||||
cutoff := now.Add(-window)
|
||||
sum, n := 0.0, 0
|
||||
for _, p := range history {
|
||||
if p.PolledAt.Before(cutoff) {
|
||||
continue
|
||||
}
|
||||
sum += p.Price
|
||||
n++
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return sum / float64(n), true
|
||||
}
|
||||
Reference in New Issue
Block a user