Dynamic window metrics for cold start scenarios
All checks were successful
Deploy / deploy (push) Successful in 2m37s

Calculate metrics from first relevant event to now (capped at 30 days)
instead of a fixed 30-day window. This fixes inaccurate metrics for new
users who have only a few days of data.

Changes:
- Add _get_first_event_ts() and _calculate_window() helpers to stats.py
- Add window_days field to EggStats dataclass
- Update routes/eggs.py and routes/feed.py to use dynamic window
- Update templates to display "N-day avg" instead of "30-day avg"
- Use ceiling division for window_days to ensure first event is included

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 19:06:00 +00:00
parent 4c62840cdf
commit 86dc3a13d2
8 changed files with 456 additions and 59 deletions

View File

@@ -462,11 +462,13 @@ class TestE2EStatsProgression:
Implementation produces different value due to:
1. Integer bird-day truncation
2. Timeline differences (1 day advance for Strip 2 bird-days)
3. Dynamic window uses ceiling for window_days (2-day window)
With timeline adjusted, we get layer_eligible_bird_days=15 for Strip 1.
With timeline adjusted, we get layer_eligible_bird_days=14 for Strip 1.
share = 14/35 = 0.4, feed_layers_g = int(20000 * 0.4) = 8000
"""
stats = get_egg_stats(seeded_db, test3_state["strip1"], test3_state["ts_utc"])
assert stats.feed_layers_g == 8570
assert stats.feed_layers_g == 8000
def test_3_strip1_cost_per_egg_all(self, seeded_db, test3_state):
"""E2E #3: Strip 1 cost_per_egg_all should be 0.889 +/- 0.001."""
@@ -479,9 +481,12 @@ class TestE2EStatsProgression:
Spec value: 0.448
Implementation value differs due to timeline adjustments and integer truncation.
Dynamic window with ceiling gives share = 14/35 = 0.4.
layer_cost = 24 EUR * 0.4 = 9.60 EUR
cost_per_egg_layers = 9.60 / 27 = 0.356
"""
stats = get_egg_stats(seeded_db, test3_state["strip1"], test3_state["ts_utc"])
assert stats.cost_per_egg_layers_eur == pytest.approx(0.381, abs=0.001)
assert stats.cost_per_egg_layers_eur == pytest.approx(0.356, abs=0.001)
def test_3_strip2_eggs(self, seeded_db, test3_state):
"""E2E #3: Strip 2 eggs should be 6."""
@@ -581,9 +586,12 @@ class TestE2EStatsProgression:
Spec value: 0.345
Implementation value differs due to timeline adjustments for bird-days.
Dynamic window with ceiling gives share = 14/35 = 0.4.
layer_cost = 24 EUR * 0.4 = 9.60 EUR
cost_per_egg_layers = 9.60 / 35 = 0.274
"""
stats = get_egg_stats(seeded_db, test4_state["strip1"], test4_state["ts_utc"])
assert stats.cost_per_egg_layers_eur == pytest.approx(0.294, abs=0.001)
assert stats.cost_per_egg_layers_eur == pytest.approx(0.274, abs=0.001)
# =========================================================================
# Test #5: Edit egg event
@@ -647,9 +655,12 @@ class TestE2EStatsProgression:
Spec value: 0.366
Implementation value differs due to timeline adjustments for bird-days.
Dynamic window with ceiling gives share = 14/35 = 0.4.
layer_cost = 24 EUR * 0.4 = 9.60 EUR
cost_per_egg_layers = 9.60 / 33 = 0.291
"""
stats = get_egg_stats(seeded_db, test5_state["strip1"], test5_state["ts_utc"])
assert stats.cost_per_egg_layers_eur == pytest.approx(0.312, abs=0.001)
assert stats.cost_per_egg_layers_eur == pytest.approx(0.291, abs=0.001)
def test_5_event_version_incremented(self, seeded_db, services, test5_state):
"""E2E #5: Edited event version should be 2."""