Window dashboard Money Saved to 30-day average
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"veola/internal/crypto"
|
||||
"veola/internal/models"
|
||||
@@ -74,6 +75,51 @@ func TestDedupByItemAndURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardMoneySavedWindow(t *testing.T) {
|
||||
if os.Getenv("CI_SKIP_SQLITE") != "" {
|
||||
t.Skip()
|
||||
}
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
bp := 100.0
|
||||
id, err := s.CreateItem(ctx, &models.Item{
|
||||
Name: "Windowed", NtfyTopic: "veola", Active: true,
|
||||
PollIntervalMinutes: 60, NtfyPriority: "default",
|
||||
BestPrice: &bp,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
// A stale point well outside the 30-day window at a much higher price. If
|
||||
// the average were computed over all history this would inflate "saved".
|
||||
if err := s.InsertPricePoint(ctx, &models.PricePoint{
|
||||
ItemID: id, Price: 500, PolledAt: now.AddDate(0, 0, -60),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A recent point inside the window. Best price (100) is below it, so the
|
||||
// only legitimate saving is 120 - 100 = 20.
|
||||
if err := s.InsertPricePoint(ctx, &models.PricePoint{
|
||||
ItemID: id, Price: 120, PolledAt: now.AddDate(0, 0, -5),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stats, err := s.GetDashboardStats(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stats.SavedItemCount != 1 {
|
||||
t.Fatalf("expected 1 saved item, got %d", stats.SavedItemCount)
|
||||
}
|
||||
if got := stats.MoneySaved; got < 19.99 || got > 20.01 {
|
||||
t.Errorf("expected money saved ~20 (recent avg only), got %.2f", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCJKRoundTrip(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -1040,11 +1040,16 @@ func (s *Store) GetDashboardStats(ctx context.Context) (*DashboardStats, error)
|
||||
`).Scan(&d.UnpricedCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// "Money saved" compares each item's current best price against its recent
|
||||
// market average. The average is windowed to the last 30 days so stale
|
||||
// historical prices don't anchor the baseline; items whose only price
|
||||
// history predates the window simply don't contribute an estimate.
|
||||
rows, err := s.DB.QueryContext(ctx, `
|
||||
SELECT i.best_price, AVG(p.price) AS avg_price
|
||||
FROM items i
|
||||
JOIN price_history p ON p.item_id = i.id
|
||||
WHERE i.active = 1 AND i.best_price IS NOT NULL
|
||||
AND p.polled_at >= datetime('now', '-30 day')
|
||||
GROUP BY i.id
|
||||
`)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user