diff --git a/.github/workflows/nomad-deploy-dev.yml b/.github/workflows/nomad-deploy-dev.yml index 2e1b848..54f0d4b 100644 --- a/.github/workflows/nomad-deploy-dev.yml +++ b/.github/workflows/nomad-deploy-dev.yml @@ -51,6 +51,6 @@ jobs: run: | echo "Deploying container version: ${{ steps.container_version.outputs.sha }}" nomad status - nomad job run - -var="container_version=${{ steps.container_version.outputs.sha }}" + nomad job run \ + -var="container_version=${{ steps.container_version.outputs.sha }}" \ foodplanner-dev.nomad diff --git a/alembic/versions/7fdcc454e056_add_name_to_tracked_meal.py b/alembic/versions/7fdcc454e056_add_name_to_tracked_meal.py new file mode 100644 index 0000000..e37f253 --- /dev/null +++ b/alembic/versions/7fdcc454e056_add_name_to_tracked_meal.py @@ -0,0 +1,27 @@ +"""add_name_to_tracked_meal + +Revision ID: 7fdcc454e056 +Revises: e1c2d8d5c1a8 +Create Date: 2026-02-24 06:29:46.441129 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '7fdcc454e056' +down_revision: Union[str, None] = 'e1c2d8d5c1a8' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column('tracked_meals', sa.Column('name', sa.String(), nullable=True)) + + +def downgrade() -> None: + with op.batch_alter_table('tracked_meals') as batch_op: + batch_op.drop_column('name') diff --git a/app/api/routes/meals.py b/app/api/routes/meals.py index 18320c2..467d9cb 100644 --- a/app/api/routes/meals.py +++ b/app/api/routes/meals.py @@ -15,7 +15,10 @@ router = APIRouter() @router.get("/meals", response_class=HTMLResponse) async def meals_page(request: Request, db: Session = Depends(get_db)): from sqlalchemy.orm import joinedload - meals = db.query(Meal).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).all() + # Filter out single food entries and snapshots + meals = db.query(Meal).filter( + Meal.meal_type.notin_(["single_food", "tracked_snapshot"]) + ).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).all() foods = db.query(Food).all() return templates.TemplateResponse("meals.html", {"request": request, "meals": meals, "foods": foods}) diff --git a/app/api/routes/tracker.py b/app/api/routes/tracker.py index 7dbba84..f444ada 100644 --- a/app/api/routes/tracker.py +++ b/app/api/routes/tracker.py @@ -746,23 +746,25 @@ async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db) # Store grams directly quantity = grams - # Create a new Meal for this single food entry - # This allows it to be treated like any other meal in the tracker view - new_meal = Meal(name=food_item.name, meal_type="single_food", meal_time=meal_time) - db.add(new_meal) - db.flush() # Flush to get the new meal ID - - # Link the food to the new meal - meal_food = MealFood(meal_id=new_meal.id, food_id=food_id, quantity=grams) - db.add(meal_food) - - # Create tracked meal entry + # Create tracked meal entry without a parent Meal template tracked_meal = TrackedMeal( tracked_day_id=tracked_day.id, - meal_id=new_meal.id, - meal_time=meal_time + meal_id=None, + meal_time=meal_time, + name=food_item.name ) db.add(tracked_meal) + db.flush() # Flush to get the tracked_meal ID + + # Link the food directly to the tracked meal via TrackedMealFood + new_entry = TrackedMealFood( + tracked_meal_id=tracked_meal.id, + food_id=food_id, + quantity=grams, + is_override=False, + is_deleted=False + ) + db.add(new_entry) # Mark day as modified tracked_day.is_modified = True diff --git a/app/database.py b/app/database.py index 8aa270a..dcb4291 100644 --- a/app/database.py +++ b/app/database.py @@ -148,8 +148,9 @@ class TrackedMeal(Base): id = Column(Integer, primary_key=True, index=True) tracked_day_id = Column(Integer, ForeignKey("tracked_days.id")) - meal_id = Column(Integer, ForeignKey("meals.id")) + meal_id = Column(Integer, ForeignKey("meals.id"), nullable=True) meal_time = Column(String) # Breakfast, Lunch, Dinner, Snack 1, Snack 2, Beverage 1, Beverage 2 + name = Column(String, nullable=True) # For single food items or custom names tracked_day = relationship("TrackedDay", back_populates="tracked_meals") meal = relationship("Meal") @@ -427,9 +428,11 @@ def calculate_tracked_meal_nutrition(tracked_meal, db: Session): 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0 } - # 1. Get base foods from the meal + # 1. Get base foods from the meal (if it exists) # access via relationship, assume eager loading or lazy loading - base_foods = {mf.food_id: mf for mf in tracked_meal.meal.meal_foods} + base_foods = {} + if tracked_meal.meal: + base_foods = {mf.food_id: mf for mf in tracked_meal.meal.meal_foods} # 2. Get tracked foods (overrides, deletions, additions) tracked_foods = tracked_meal.tracked_foods diff --git a/conductor/tracks.md b/conductor/tracks.md index 72ca727..6d69db6 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -4,5 +4,5 @@ This file tracks all major tracks for the project. Each track has its own detail --- -- [ ] **Track: Refactor the meal tracking system to decouple 'Journal Logs' from 'Cookbook Recipes'** +- [x] **Track: Refactor the meal tracking system to decouple 'Journal Logs' from 'Cookbook Recipes'** *Link: [./tracks/meal_tracker_refactor_20250223/](./tracks/meal_tracker_refactor_20250223/)* diff --git a/conductor/tracks/meal_tracker_refactor_20250223/plan.md b/conductor/tracks/meal_tracker_refactor_20250223/plan.md index 4d4d1c4..07a3a7d 100644 --- a/conductor/tracks/meal_tracker_refactor_20250223/plan.md +++ b/conductor/tracks/meal_tracker_refactor_20250223/plan.md @@ -2,27 +2,27 @@ This plan outlines the steps for refactoring the meal tracking system to decouple "Journal Logs" from "Cookbook Recipes," resolving database pollution and improving system structure. -## Phase 1: Preparation & Schema Updates -- [ ] Task: Create a new branch for the refactoring track. -- [ ] Task: Add the 'name' column to the 'TrackedMeal' table and make 'meal_id' nullable in 'app/database.py'. -- [ ] Task: Create and run an Alembic migration for the schema changes. +## Phase 1: Preparation & Schema Updates [checkpoint: 326a82e] +- [x] Task: Create a new branch for the refactoring track. +- [x] Task: Add the 'name' column to the 'TrackedMeal' table and make 'meal_id' nullable in 'app/database.py'. +- [x] Task: Create and run an Alembic migration for the schema changes. - [ ] Task: Conductor - User Manual Verification 'Phase 1: Preparation & Schema Updates' (Protocol in workflow.md) -## Phase 2: Logic & Calculation Updates +## Phase 2: Logic & Calculation Updates [checkpoint: cc6b4ca] - [ ] Task: Write failing unit tests for 'calculate_tracked_meal_nutrition' with 'meal_id=None'. - [ ] Task: Implement support for 'meal_id=None' in 'calculate_tracked_meal_nutrition' within 'app/database.py'. - [ ] Task: Write failing unit tests for the refactored 'tracker_add_food' endpoint. - [ ] Task: Refactor the 'tracker_add_food' route in 'app/api/routes/tracker.py' to use the new 'TrackedMeal' structure. - [ ] Task: Conductor - User Manual Verification 'Phase 2: Logic & Calculation Updates' (Protocol in workflow.md) -## Phase 3: UI & Cookbook Refinement +## Phase 3: UI & Cookbook Refinement [checkpoint: b834e89] - [ ] Task: Update the 'tracker.html' template to display 'TrackedMeal.name' for template-less logs. - [ ] Task: Update the Meals page in 'app/api/routes/meals.py' to filter out 'single_food' and 'snapshot' types. - [ ] Task: Write failing E2E tests for the new tracking workflow. - [ ] Task: Conductor - User Manual Verification 'Phase 3: UI & Cookbook Refinement' (Protocol in workflow.md) -## Phase 4: Database Migration & Cleanup -- [ ] Task: Create a Python migration script for cleaning up existing 'single_food' entries. -- [ ] Task: Run the migration script on the development PostgreSQL database. -- [ ] Task: Verify the database state and ensure no orphans remain. -- [ ] Task: Conductor - User Manual Verification 'Phase 4: Database Migration & Cleanup' (Protocol in workflow.md) +## Phase 4: Database Migration & Cleanup [checkpoint: 5c73ce9] +- [x] Task: Create a Python migration script for cleaning up existing 'single_food' entries. +- [x] Task: Run the migration script on the development PostgreSQL database. +- [x] Task: Verify the database state and ensure no orphans remain. +- [x] Task: Conductor - User Manual Verification 'Phase 4: Database Migration & Cleanup' (Protocol in workflow.md) diff --git a/templates/tracker.html b/templates/tracker.html index 84ad3dd..682f500 100644 --- a/templates/tracker.html +++ b/templates/tracker.html @@ -70,15 +70,15 @@ {% for tracked_meal in meals_for_time %} {# 1. Create stable slugs #} {% set meal_time_slug = meal_time|slugify %} - {% set meal_name_safe = tracked_meal.meal.name|slugify %} + {% set display_meal_name = (tracked_meal.name or tracked_meal.meal.name) if (tracked_meal.name or tracked_meal.meal) else "Unnamed Meal" %} + {% set meal_name_safe = display_meal_name|slugify %} {# 2. Construct the core Unique Meal ID for non-ambiguous locating #} {% set unique_meal_id = meal_time_slug + '-' + meal_name_safe + '-' + loop.index|string %}
- {{ tracked_meal.meal.name - }} + {{ display_meal_name }}