Fix cost/egg window to use later of first egg or first feed event
All checks were successful
Deploy / deploy (push) Successful in 1m39s
All checks were successful
Deploy / deploy (push) Successful in 1m39s
When egg data is imported but feed data starts later, cost/egg was incorrectly using the egg window (e.g., 30 days) instead of the period where both data types exist. Now cost/egg uses max(first_egg, first_feed) to ensure accurate cost calculation. Each metric now displays its own window: "4.7 eggs/day (30d) | €0.28/egg (7d)" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,7 +184,8 @@ def _get_global_cost_per_egg(db: Any, now_ms: int) -> tuple[float | None, int]:
|
||||
"""Calculate global cost per egg over dynamic window.
|
||||
|
||||
Aggregates feed costs and egg counts across all locations.
|
||||
Uses a dynamic window based on the first egg collection event.
|
||||
Uses a dynamic window based on the later of first egg event or first feed event,
|
||||
ensuring we only calculate cost for periods with complete data.
|
||||
|
||||
Args:
|
||||
db: Database connection.
|
||||
@@ -195,9 +196,22 @@ def _get_global_cost_per_egg(db: Any, now_ms: int) -> tuple[float | None, int]:
|
||||
"""
|
||||
from animaltrack.events import FEED_GIVEN
|
||||
|
||||
# Calculate dynamic window based on first egg event
|
||||
# Calculate dynamic window based on the later of first egg or first feed event
|
||||
# This ensures we only calculate cost/egg for periods with both data types
|
||||
first_egg_ts = _get_first_event_ts(db, "ProductCollected", product_prefix="egg.")
|
||||
window_start, window_end, window_days = _calculate_window(now_ms, first_egg_ts)
|
||||
first_feed_ts = _get_first_event_ts(db, "FeedGiven")
|
||||
|
||||
# Use the later timestamp (max) to ensure complete data for both metrics
|
||||
if first_egg_ts is None and first_feed_ts is None:
|
||||
first_event_ts = None
|
||||
elif first_egg_ts is None:
|
||||
first_event_ts = first_feed_ts
|
||||
elif first_feed_ts is None:
|
||||
first_event_ts = first_egg_ts
|
||||
else:
|
||||
first_event_ts = max(first_egg_ts, first_feed_ts)
|
||||
|
||||
window_start, window_end, window_days = _calculate_window(now_ms, first_event_ts)
|
||||
|
||||
event_store = EventStore(db)
|
||||
|
||||
@@ -300,17 +314,18 @@ def _get_eggs_display_data(db: Any, locations: list) -> dict:
|
||||
|
||||
Returns:
|
||||
Dict with harvest_events, sell_events, eggs_per_day, cost_per_egg,
|
||||
eggs_window_days, sales_stats, location_names.
|
||||
eggs_window_days, cost_window_days, sales_stats, location_names.
|
||||
"""
|
||||
now_ms = int(time.time() * 1000)
|
||||
eggs_per_day, eggs_window_days = _get_eggs_per_day(db, now_ms)
|
||||
cost_per_egg, _ = _get_global_cost_per_egg(db, now_ms)
|
||||
cost_per_egg, cost_window_days = _get_global_cost_per_egg(db, now_ms)
|
||||
return {
|
||||
"harvest_events": _get_recent_events(db, PRODUCT_COLLECTED, limit=10),
|
||||
"sell_events": _get_recent_events(db, PRODUCT_SOLD, limit=10),
|
||||
"eggs_per_day": eggs_per_day,
|
||||
"cost_per_egg": cost_per_egg,
|
||||
"eggs_window_days": eggs_window_days,
|
||||
"cost_window_days": cost_window_days,
|
||||
"sales_stats": _get_sales_stats(db, now_ms),
|
||||
"location_names": {loc.id: loc.name for loc in locations},
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ def eggs_page(
|
||||
eggs_per_day: float | None = None,
|
||||
cost_per_egg: float | None = None,
|
||||
eggs_window_days: int = 30,
|
||||
cost_window_days: int = 30,
|
||||
sales_stats: dict | None = None,
|
||||
location_names: dict[str, str] | None = None,
|
||||
# Field value preservation on errors
|
||||
@@ -62,7 +63,8 @@ def eggs_page(
|
||||
sell_events: Recent ProductSold events (most recent first).
|
||||
eggs_per_day: Average eggs per day over window.
|
||||
cost_per_egg: Average cost per egg in EUR over window.
|
||||
eggs_window_days: Actual window size in days for the metrics.
|
||||
eggs_window_days: Actual window size in days for eggs_per_day.
|
||||
cost_window_days: Actual window size in days for cost_per_egg.
|
||||
sales_stats: Dict with 'total_qty', 'total_cents', and 'avg_price_per_egg_cents'.
|
||||
location_names: Dict mapping location_id to location name for display.
|
||||
harvest_quantity: Preserved quantity value on error.
|
||||
@@ -100,6 +102,7 @@ def eggs_page(
|
||||
eggs_per_day=eggs_per_day,
|
||||
cost_per_egg=cost_per_egg,
|
||||
eggs_window_days=eggs_window_days,
|
||||
cost_window_days=cost_window_days,
|
||||
location_names=location_names,
|
||||
default_quantity=harvest_quantity,
|
||||
default_notes=harvest_notes,
|
||||
@@ -135,6 +138,7 @@ def harvest_form(
|
||||
eggs_per_day: float | None = None,
|
||||
cost_per_egg: float | None = None,
|
||||
eggs_window_days: int = 30,
|
||||
cost_window_days: int = 30,
|
||||
location_names: dict[str, str] | None = None,
|
||||
default_quantity: str | None = None,
|
||||
default_notes: str | None = None,
|
||||
@@ -149,7 +153,8 @@ def harvest_form(
|
||||
recent_events: Recent (Event, is_deleted) tuples, most recent first.
|
||||
eggs_per_day: Average eggs per day over window.
|
||||
cost_per_egg: Average cost per egg in EUR over window.
|
||||
eggs_window_days: Actual window size in days for the metrics.
|
||||
eggs_window_days: Actual window size in days for eggs_per_day.
|
||||
cost_window_days: Actual window size in days for cost_per_egg.
|
||||
location_names: Dict mapping location_id to location name for display.
|
||||
default_quantity: Preserved quantity value on error.
|
||||
default_notes: Preserved notes value on error.
|
||||
@@ -193,13 +198,13 @@ def harvest_form(
|
||||
loc_name = location_names.get(loc_id, "Unknown")
|
||||
return f"{quantity} eggs from {loc_name}", event.id
|
||||
|
||||
# Build stats text - combine eggs/day and cost/egg
|
||||
# Build stats text - each metric shows its own window
|
||||
stat_parts = []
|
||||
if eggs_per_day is not None:
|
||||
stat_parts.append(f"{eggs_per_day:.1f} eggs/day")
|
||||
stat_parts.append(f"{eggs_per_day:.1f} eggs/day ({eggs_window_days}d)")
|
||||
if cost_per_egg is not None:
|
||||
stat_parts.append(f"€{cost_per_egg:.3f}/egg cost")
|
||||
stat_text = " | ".join(stat_parts) + f" ({eggs_window_days}-day avg)" if stat_parts else None
|
||||
stat_parts.append(f"€{cost_per_egg:.3f}/egg ({cost_window_days}d)")
|
||||
stat_text = " | ".join(stat_parts) if stat_parts else None
|
||||
|
||||
form = Form(
|
||||
H2("Harvest Eggs", cls="text-xl font-bold mb-4"),
|
||||
|
||||
Reference in New Issue
Block a user