Implement performance improvements across backend and frontend

- models.py: add composite (user_id, date) indexes to flock_history,
  feed_purchases, and other_purchases for faster date-filtered queries
  (egg_collections already had one via its unique constraint)
- main.py: add v2.2 migration to create the three composite indexes on
  existing installs at startup
- stats.py: fix N+1 query in monthly_stats — flock history is now fetched
  once and looked up per month using bisect_right instead of one DB query
  per month row; also remove unnecessary Decimal(str(...)) round-trips
  since SQLAlchemy already returns Numeric columns as Decimal
- eggs.py: add limit parameter (default 500, max 1000) to list_eggs to
  cap unbounded fetches on large datasets
- dashboard.js: pass start= (30 days ago) when fetching eggs so the
  dashboard only loads the data it actually needs for the chart and
  recent collections list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 00:02:58 -07:00
parent 37f19a83ed
commit 60fed6d464
5 changed files with 46 additions and 18 deletions

View File

@@ -1,4 +1,5 @@
import calendar
from bisect import bisect_right
from datetime import date, datetime, timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
@@ -170,20 +171,24 @@ def monthly_stats(
other_map = {(r.year, r.month): r.other_cost for r in other_rows}
# Fetch all flock history once (ascending) to avoid N+1 per month
flock_all = db.scalars(
select(FlockHistory)
.where(FlockHistory.user_id == uid)
.order_by(FlockHistory.date)
).all()
flock_dates = [f.date for f in flock_all]
flock_counts = [f.chicken_count for f in flock_all]
def flock_at(month_end: date) -> int | None:
idx = bisect_right(flock_dates, month_end) - 1
return flock_counts[idx] if idx >= 0 else None
results = []
for row in egg_rows:
y, m = int(row.year), int(row.month)
last_day = calendar.monthrange(y, m)[1]
month_end = date(y, m, last_day)
flock_row = db.scalars(
select(FlockHistory)
.where(FlockHistory.user_id == uid)
.where(FlockHistory.date <= month_end)
.order_by(FlockHistory.date.desc())
.limit(1)
).first()
flock = flock_row.chicken_count if flock_row else None
month_end = date(y, m, calendar.monthrange(y, m)[1])
flock = flock_at(month_end)
total_eggs = int(row.total_eggs)
days_logged = int(row.days_logged)
@@ -192,11 +197,11 @@ def monthly_stats(
raw_feed_cost = feed_map.get((y, m))
raw_other_cost = other_map.get((y, m))
feed_cost = round(Decimal(str(raw_feed_cost)), 2) if raw_feed_cost else None
other_cost = round(Decimal(str(raw_other_cost)), 2) if raw_other_cost else None
feed_cost = round(raw_feed_cost, 2) if raw_feed_cost else None
other_cost = round(raw_other_cost, 2) if raw_other_cost else None
raw_total_cost = (raw_feed_cost or 0) + (raw_other_cost or 0)
cpe = round(Decimal(str(raw_total_cost)) / Decimal(total_eggs), 4) if (raw_total_cost and total_eggs) else None
total_cost = (raw_feed_cost or Decimal(0)) + (raw_other_cost or Decimal(0))
cpe = round(total_cost / total_eggs, 4) if (total_cost and total_eggs) else None
cpd = round(cpe * 12, 4) if cpe else None
results.append(MonthlySummary(