Files
foodplanner/app/api/routes/charts.py
2026-01-12 15:13:50 -08:00

137 lines
5.1 KiB
Python

from fastapi import APIRouter, Depends, Query, Request
from starlette.responses import HTMLResponse
from sqlalchemy.orm import Session
from datetime import date, timedelta
from typing import List
from app.database import get_db, TrackedDay, TrackedMeal, calculate_day_nutrition_tracked, WeightLog
router = APIRouter(tags=["charts"])
@router.get("/charts", response_class=HTMLResponse)
async def charts_page(request: Request, person: str = "Sarah", db: Session = Depends(get_db)):
"""Render the charts page"""
from main import templates
return templates.TemplateResponse("charts.html", {
"request": request,
"person": person
})
@router.get("/api/charts", response_model=List[dict])
async def get_charts_data(
person: str = Query(..., description="Person name (e.g., Sarah)"),
days: int = Query(7, description="Number of past days to fetch data for", ge=1, le=30),
db: Session = Depends(get_db)
):
"""
Get daily calorie data for the last N days for a person.
Returns list of {"date": "YYYY-MM-DD", "calories": float} sorted by date descending.
"""
end_date = date.today()
start_date = end_date - timedelta(days=days - 1)
tracked_days = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date >= start_date,
TrackedDay.date <= end_date
).order_by(TrackedDay.date.desc()).all()
chart_data = []
# Fetch all tracked days and weight logs for the period
tracked_days_map = {
d.date: d for d in db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date >= start_date,
TrackedDay.date <= end_date
).all()
}
# Sort logs desc
weight_logs_map = {
w.date: w for w in db.query(WeightLog).filter(
WeightLog.date >= start_date,
WeightLog.date <= end_date
).order_by(WeightLog.date.desc()).all()
}
# Get last weight BEFORE start_date (for initial carry forward)
last_historical_weight_log = db.query(WeightLog).filter(
WeightLog.date < start_date
).order_by(WeightLog.date.desc()).first()
last_historical_weight_val = last_historical_weight_log.weight * 2.20462 if last_historical_weight_log else None
# Find the most recent weight available (either in range or history)
# This is for "Today" (end_date)
latest_weight_val = last_historical_weight_val
# Check if we have newer weights in the map
# Values in weight_logs_map are WeightLog objects.
# Find the one with max date <= end_date. Since map key is date, we can check.
# But filtering the map is tedious. Let's just iterate.
# Actually, we already have `weight_logs_map` (in range).
# If the range has weights, the newest one is the "latest" known weight relevant to the end of chart.
if weight_logs_map:
# Get max date
max_date = max(weight_logs_map.keys())
latest_weight_val = weight_logs_map[max_date].weight * 2.20462
chart_data = []
# Iterate dates. Note: i=0 is end_date (Today), i=days-1 is start_date (Oldest)
for i in range(days):
current_date = end_date - timedelta(days=i)
tracked_day = tracked_days_map.get(current_date)
weight_log = weight_logs_map.get(current_date)
calories = 0
protein = 0
fat = 0
net_carbs = 0
# Calculate nutrition
if tracked_day:
tracked_meals = db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).all()
day_totals = calculate_day_nutrition_tracked(tracked_meals, db)
calories = round(day_totals.get("calories", 0), 2)
protein = round(day_totals.get("protein", 0), 2)
fat = round(day_totals.get("fat", 0), 2)
net_carbs = round(day_totals.get("net_carbs", 0), 2)
weight_lbs = None
is_real = False
if weight_log:
weight_lbs = round(weight_log.weight * 2.20462, 2)
is_real = True
# Logic for Start and End Points (to ensure line connects across view)
# If this is the Oldest date in view (start_date) and no real weight
if i == days - 1 and weight_lbs is None:
# Use historical weight if available (to start the line)
if last_historical_weight_val is not None:
weight_lbs = round(last_historical_weight_val, 2)
# is_real remains False (inferred)
# If this is the Newest date in view (end_date/Today) and no real weight
if i == 0 and weight_lbs is None:
# Use latest known weight (to end the line)
if latest_weight_val is not None:
weight_lbs = round(latest_weight_val, 2)
# is_real remains False (inferred)
chart_data.append({
"date": current_date.isoformat(),
"calories": calories,
"protein": protein,
"fat": fat,
"net_carbs": net_carbs,
"weight_lbs": weight_lbs,
"weight_is_real": is_real
})
return chart_data