Skip to content

Fix overlapping nutrition source totals - #2220

Merged
Asherlc merged 11 commits into
mainfrom
codex/issue-2059
Jul 27, 2026
Merged

Asherlc merged 11 commits into
mainfrom
codex/issue-2059

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • preserve raw itemized and daily-aggregate nutrition rows with explicit ingestion grain
  • keep fitness.v_nutrition_daily as the canonical one-row-per-user/date serving contract, resolving a single contribution set or an explicit source conflict
  • preserve the installed-client food.byDate v1 DTO and fail conflicts with an actionable precondition error
  • add food.byDateV2 for conflict-aware web/mobile clients and version the mobile persisted-query contract so stale v1 data is discarded
  • expose raw per-provider provenance through fitness.v_nutrition_provider_daily for provider detail, statistics, and exports
  • route server analytics, Slack, MCP, web, and mobile through canonical totals while retaining raw provider data
  • add executable PostgreSQL coverage for source selection, conflicts, isolation, provenance, and downstream analytics

Validation

  • Node 26.5.0 focused compatibility/client suite: 55 passed
  • Node 26.5.0 full Docker-free suite: 14,154 passed, 21 skipped
  • root TypeScript and focused Biome checks passed
  • exact CI migration SQLFluff lint and raw PostgreSQL parse passed on the prior head
  • local PostgreSQL integration and dbt/ClickHouse analytics lint await exact-head CI because Docker could not create this worktree volume (no space left on device)

Closes #2059

Preserve provider rows while selecting one canonical contribution set at
query time. Ambiguous overlaps return explicit provenance instead of
double-counted totals.

Refs #2059
Copilot AI review requested due to automatic review settings July 27, 2026 19:56

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @Asherlc, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Asherlc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1bd6e09c-76de-4228-aa27-15e1e9ff0b63

📥 Commits

Reviewing files that changed from the base of the PR and between a926444 and 9c6ae53.

📒 Files selected for processing (66)
  • AGENTS.md
  • README.md
  • cypress/e2e/nutrition.cy.ts
  • docs/apple-health.md
  • docs/nutrition-ai-input.md
  • docs/production-incident-baseline.md
  • docs/schema.dbml
  • docs/schema.md
  • docs/schema.puml
  • drizzle/0060_canonical_nutrition_contribution_set.sql
  • drizzle/_views/07_provider_stats.sql
  • drizzle/meta/_journal.json
  • packages/mobile/app/(tabs)/food.stories.tsx
  • packages/mobile/app/(tabs)/food.test.tsx
  • packages/mobile/app/(tabs)/food.tsx
  • packages/mobile/app/food/add.tsx
  • packages/mobile/components/MealSection.tsx
  • packages/mobile/lib/invalidate-synced-health-data.test.ts
  • packages/mobile/lib/invalidate-synced-health-data.ts
  • packages/mobile/lib/mobile-query-persistence.test.tsx
  • packages/mobile/lib/mobile-query-persistence.ts
  • packages/mobile/types/api.ts
  • packages/nutrition/README.md
  • packages/nutrition/src/selected-date-summary.test.ts
  • packages/nutrition/src/selected-date-summary.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/repositories/correlation-repository.ts
  • packages/server/src/repositories/food-repository.test.ts
  • packages/server/src/repositories/food-repository.ts
  • packages/server/src/repositories/insights-repository.ts
  • packages/server/src/repositories/nutrition-analytics-repository.test.ts
  • packages/server/src/repositories/nutrition-analytics-repository.ts
  • packages/server/src/repositories/nutrition-canonical.integration.test.ts
  • packages/server/src/repositories/nutrition-repository.test.ts
  • packages/server/src/repositories/nutrition-repository.ts
  • packages/server/src/repositories/predictions-repository.ts
  • packages/server/src/repositories/provider-detail-repository.test.ts
  • packages/server/src/repositories/provider-detail-repository.ts
  • packages/server/src/routers/food.test.ts
  • packages/server/src/routers/food.ts
  • packages/server/src/routers/nutrition-analytics-data.integration.test.ts
  • packages/server/src/routers/nutrition-analytics.test.ts
  • packages/server/src/routers/provider-detail.test.ts
  • packages/server/src/routers/router-data.integration.test.ts
  • packages/server/src/routers/router-sql.integration.test.ts
  • packages/server/src/routers/router.integration.test.ts
  • packages/server/src/slack/bot-unit.test.ts
  • packages/server/src/slack/food-entry-repository.test.ts
  • packages/server/src/slack/food-entry-repository.ts
  • packages/server/src/slack/formatting.test.ts
  • packages/server/src/slack/formatting.ts
  • packages/web/src/pages/NutritionPage.ai.test.tsx
  • packages/web/src/pages/NutritionPage.test.ts
  • packages/web/src/pages/NutritionPage.tsx
  • scripts/seed/nutrition.ts
  • src/db/schema/enums.ts
  • src/db/schema/nutrition.ts
  • src/export.test.ts
  • src/export.ts
  • src/providers/apple-health/db-insertion.ts
  • src/providers/apple-health/import.integration.test.ts
  • src/providers/auto-supplements.ts
  • src/providers/cronometer-csv-sync.integration.test.ts
  • src/providers/cronometer-csv.ts
  • src/providers/fatsecret/provider.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements canonical per-day nutrition source resolution backed by new PostgreSQL views and enums, routes all serving paths (server, web, mobile, Slack, MCP, analytics, export) through these canonical totals with explicit provenance, and surfaces overlapping-source conflicts instead of double-counting while preserving raw provider data.

Sequence diagram for nutrition by date conflict-aware response

sequenceDiagram
  actor User
  participant WebClient
  participant FoodRouter as foodRouter.byDate
  participant FoodRepo as FoodRepository
  participant SettingsRepo as SettingsRepository
  participant CanonicalDaily as fitness.v_nutrition_canonical_daily

  User->>WebClient: Request nutrition for date
  WebClient->>FoodRouter: byDate(date)
  FoodRouter->>SettingsRepo: getCalorieGoal()
  SettingsRepo-->>FoodRouter: calorieGoal
  FoodRouter->>FoodRepo: nutritionByDate(date, calorieGoal)
  FoodRepo->>CanonicalDaily: SELECT ... FROM v_nutrition_canonical_daily
  CanonicalDaily-->>FoodRepo: row(resolution_status,...)
  alt resolution_status == available
    FoodRepo-->>FoodRouter: {summary, resolution}
  else resolution_status == source_conflict
    FoodRepo-->>FoodRouter: {summary: null, resolution}
  end
  FoodRouter-->>WebClient: {entries, summary, resolution}
  alt status == available
    WebClient-->>User: Show totals and macros
  else status == source_conflict
    WebClient-->>User: Show conflict alert, hide totals
  end
Loading

File-Level Changes

Change Details Files
Introduce canonical nutrition contribution set and classification in the database and wire repositories to use it instead of raw per-provider daily totals.
  • Add nutrition_entry_grain enum and nutrition_grain column to fitness.food_entry and classify entries via new v_nutrition_entry_classification view.
  • Create v_nutrition_daily_resolution, v_nutrition_canonical_nutrient, v_nutrition_canonical_daily, and v_nutrition_display_entry views to pick a single contribution set or mark source_conflict with provenance.
  • Add integration test nutrition-canonical.integration.test.ts to validate selection, exclusion, ambiguity handling, and downstream analytics behavior.
drizzle/0059_canonical_nutrition_contribution_set.sql
src/db/schema/enums.ts
src/db/schema/nutrition.ts
packages/server/src/repositories/nutrition-canonical.integration.test.ts
Extend server nutrition repositories and routers to return canonical totals plus source resolution metadata, and update all consumers to handle conflicts and nullable summaries.
  • Change FoodRepository to query v_nutrition_canonical_daily and v_nutrition_canonical_nutrient, introduce nutritionByDate() returning {summary
null, resolution}, and adapt nutritionSummaryByDate(), dailyTotals(), and dailyTotalsRange() to resolution-aware schemas.
  • Update NutritionRepository, Correlation/Insights/Predictions/NutritionAnalytics repositories to read from canonical views and filter on resolution_status = 'available' where needed.
  • Update foodRouter.byDate to return entries plus {summary, resolution}, and adapt MCP tools to expose resolution fields and propagate conflicts rather than forcing a single provider.
  • Propagate nutrition source resolution DTOs through shared nutrition types, web, mobile, Slack, and MCP surfaces, surfacing conflicts in UI and messaging and guarding on nullable summaries.
    • Add nutritionSourceResolutionSchema and NutritionSourceResolution type to @dofek/nutrition/selected-date-summary and use it in web/mobile selectedDateFood/FoodByDate schemas with nullable summary.
    • Update web NutritionPage and mobile FoodScreen/MealSection to render an alert when resolution.status === 'source_conflict', hide calorie goal/macro bars when summary is null, and handle optional mealCalories.
    • Extend Slack DailyCalorieProgress to a discriminated union, fetch canonical daily calories with resolution fields, and adjust formatting to show provenance-only messages on conflicts.
    • Update MCP tests and types to expect resolution_status/message, contributing/excluded providers, and null totals on conflicts.
    packages/nutrition/src/selected-date-summary.ts
    packages/web/src/pages/NutritionPage.tsx
    packages/web/src/pages/NutritionPage.test.ts
    packages/web/src/pages/NutritionPage.ai.test.tsx
    packages/mobile/app/(tabs)/food.tsx
    packages/mobile/app/(tabs)/food.test.tsx
    packages/mobile/app/(tabs)/food.stories.tsx
    packages/mobile/components/MealSection.tsx
    packages/mobile/types/api.ts
    packages/server/src/slack/food-entry-repository.ts
    packages/server/src/slack/formatting.ts
    packages/server/src/slack/formatting.test.ts
    packages/server/src/mcp/route.test.ts
    Ensure all write paths tag itemized entries with nutrition_grain='itemized' and Apple Health / seed paths tag daily aggregates, keeping raw provenance intact while steering serving logic.
    • Set nutrition_grain='itemized' on manual food creation (server repository), Slack-confirmed entries, auto-supplements, Cronometer, FatSecret, Cypress seeds, and seed food entries.
    • Set nutrition_grain='daily_aggregate' on Apple Health upsertNutritionBatch and seedDailyNutrition, and update ON CONFLICT upserts to preserve nutrition_grain.
    • Add tests to verify that unresolved multi-source days do not get a single providerId and that canonical views ignore unconfirmed entries and other users/dates.
    packages/server/src/repositories/food-repository.ts
    packages/server/src/slack/food-entry-repository.ts
    src/providers/auto-supplements.ts
    src/providers/cronometer-csv.ts
    src/providers/fatsecret/provider.ts
    scripts/seed/nutrition.ts
    cypress/e2e/nutrition.cy.ts
    src/providers/apple-health/db-insertion.ts
    packages/server/src/repositories/nutrition-repository.test.ts
    Align analytics, exports, docs, and incident baseline with the new canonical nutrition model and guarantees around not double-counting overlapping sources.
    • Update NutritionAnalyticsRepository queries for micronutrient adequacy, adaptive TDEE, and macro ratios to use canonical views and add tests asserting the view names and resolution filters in generated SQL.
    • Change export pipeline to export raw fitness.food_entry rows rather than v_nutrition_daily and add a test to assert this behavior.
    • Revise docs (schema, nutrition AI input, Apple Health, nutrition package README, AGENTS, root README, production incident baseline) to describe canonical views, nutrition_grain semantics, and conflict behavior.
    packages/server/src/repositories/nutrition-analytics-repository.ts
    packages/server/src/repositories/nutrition-analytics-repository.test.ts
    src/export.ts
    src/export.test.ts
    docs/schema.md
    docs/apple-health.md
    docs/nutrition-ai-input.md
    packages/nutrition/README.md
    AGENTS.md
    README.md
    docs/production-incident-baseline.md

    Assessment against linked issues

    Issue Objective Addressed Explanation
    #2059 Implement source-aware canonical nutrition aggregation that prevents double-counting overlapping provider daily aggregates with itemized meals, selecting a canonical per-day contribution set with provenance.
    #2059 Surface explicit source-conflict/overlap states instead of totals when a canonical contribution set cannot be determined, across user-facing surfaces (web, mobile, Slack, MCP) while still showing itemized entries.
    #2059 Ensure all nutrition-dependent analytics (micronutrients, adaptive TDEE, correlations, insights, etc.) consume the same canonical contribution set, while exports retain raw provider detail.

    Possibly linked issues

    • #unknown: They both address making server the canonical source for daily nutrition totals and client display, eliminating client-side recomputation.

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @qodo-code-review

    Copy link
    Copy Markdown

    PR Summary by Qodo

    Resolve overlapping nutrition sources via canonical daily contribution views

    🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

    Grey Divider

    AI Description

    • Add query-time source resolution to prevent double-counted daily nutrition totals.
    • Preserve raw provider rows while surfacing conflicts with explicit provenance metadata.
    • Route API, analytics, Slack, MCP, web, and mobile through canonical totals and conflict UX.
    
    Diagram

    graph TD
      A[("Raw nutrition tables\nfood_entry + food_entry_nutrient")] --> B[("v_nutrition_entry_classification")]
      B --> C[("v_nutrition_daily_resolution")]
      C --> D[("Canonical views\nv_nutrition_canonical_daily / _nutrient")]
      D --> E(["Server repos & routers"])
      A --> F(["Provider detail & exports (raw)"])
      E --> G{{"Clients & tools\nWeb / Mobile / Slack / MCP / Analytics"}}
    
      subgraph Legend
        direction LR
        _db[(Database/View)] ~~~ _svc([Service/Module]) ~~~ _ext{{External/Client}}
      end
    
    Loading
    High-Level Assessment

    The following are alternative approaches to this PR:

    1. Resolve overlaps at ingestion (dedupe/write suppression)
    • ➕ Serving queries stay simpler (no resolution logic at read time)
    • ➕ Avoids ambiguous days by enforcing a single writer per day
    • ➖ Would discard raw provenance or require complex audit tables
    • ➖ Hard to safely apply retroactively; risks deleting legitimate data
    • ➖ Still needs conflict visibility for debugging and user support
    2. Materialize canonical daily totals into a table
    • ➕ Faster reads for heavy analytics workloads
    • ➕ Allows indexing/partitioning by user/date for common access patterns
    • ➖ Introduces a second source of truth and invalidation complexity
    • ➖ Requires recomputation/backfills when classification rules evolve
    3. Provider-priority configuration (always prefer X over Y)
    • ➕ Deterministic behavior without returning null totals
    • ➕ Can be user-configurable (e.g., prefer Cronometer over Apple Health)
    • ➖ Can silently mask incorrect overlaps and reintroduce hidden double-counting risk
    • ➖ Still ambiguous for multiple itemized sources or legacy shapes
    • ➖ Adds UX/config surface area and policy complexity

    Recommendation: Keep the PR’s query-time resolution via PostgreSQL views. It preserves raw provider rows, makes conflicts explicit (instead of silently double-counting), and centralizes correctness so every consumer (API, analytics, Slack, MCP, web, mobile) shares the same canonical definition. Materialization or ingestion-time suppression can be revisited later strictly for performance, but only if it doesn’t create a second source of truth.

    Files changed (50) +1612 / -214

    Enhancement (7) +320 / -11
    0059_canonical_nutrition_contribution_set.sqlIntroduce nutrition_grain enum and canonical nutrition resolution views +273/-0

    Introduce nutrition_grain enum and canonical nutrition resolution views

    • Adds nutrition_entry_grain type + food_entry.nutrition_grain column, then creates classification, resolution, canonical nutrient/daily totals, and display-entry views to prevent overlapping sources from being summed.
    

    drizzle/0059_canonical_nutrition_contribution_set.sql

    api.tsUpdate FoodByDate schema to include resolution and nullable summary +6/-2

    Update FoodByDate schema to include resolution and nullable summary

    • Re-exports the shared resolution schema and updates FoodByDate DTO to permit summary=null for source_conflict days.
    

    packages/mobile/types/api.ts

    selected-date-summary.tsAdd NutritionSourceResolution schema/type +12/-0

    Add NutritionSourceResolution schema/type

    • Defines a Zod schema and type for conveying availability vs source_conflict plus contributing/excluded provenance.
    

    packages/nutrition/src/selected-date-summary.ts

    food.tsReturn {summary, resolution} from byDate +3/-3

    Return {summary, resolution} from byDate

    • Replaces summary-only call with nutritionByDate and spreads the returned summary/resolution fields into the router response.
    

    packages/server/src/routers/food.ts

    formatting.tsFormat Slack daily progress for conflicts +19/-5

    Format Slack daily progress for conflicts

    • Introduces a discriminated union for daily progress and renders conflict messages + source labels instead of calories/progress bars.
    

    packages/server/src/slack/formatting.ts

    enums.tsAdd nutrition_entry_grain enum to DB schema +5/-0

    Add nutrition_entry_grain enum to DB schema

    • Introduces the nutrition_entry_grain enum for itemized vs daily_aggregate classification.
    

    src/db/schema/enums.ts

    nutrition.tsAdd nutritionGrain column to foodEntry table definition +2/-1

    Add nutritionGrain column to foodEntry table definition

    • Wires nutrition_grain into the Drizzle schema for fitness.food_entry.
    

    src/db/schema/nutrition.ts

    Bug fix (16) +428 / -165
    food.tsxRender source-conflict alert and tolerate null summary +41/-2

    Render source-conflict alert and tolerate null summary

    • Consumes resolution metadata from API, shows an accessibilityRole=alert on conflicts, and makes per-meal calories nullable when totals are unavailable.
    

    packages/mobile/app/(tabs)/food.tsx

    MealSection.tsxAllow null totalCalories for conflict days +2/-2

    Allow null totalCalories for conflict days

    • Changes MealSection prop typing and rendering to avoid showing calories when totals are unavailable.
    

    packages/mobile/components/MealSection.tsx

    tools.tsEmit resolution metadata in MCP nutrition tool output +6/-1

    Emit resolution metadata in MCP nutrition tool output

    • Adds resolution_status/message and contributing/excluded providers to tool output, using contributingProviders for single-provider attribution.
    

    packages/server/src/mcp/tools.ts

    correlation-repository.tsUse canonical daily totals for correlation data +2/-1

    Use canonical daily totals for correlation data

    • Switches nutrition inputs to v_nutrition_canonical_daily and filters to resolution_status='available' to avoid polluted datasets.
    

    packages/server/src/repositories/correlation-repository.ts

    food-repository.tsServe canonical totals with resolution metadata; exclude aggregate rows from display +193/-70

    Serve canonical totals with resolution metadata; exclude aggregate rows from display

    • Routes entry reads through v_nutrition_display_entry, reads canonical totals from v_nutrition_canonical_daily/_nutrient, and returns {summary|null, resolution} for by-date nutrition.
    

    packages/server/src/repositories/food-repository.ts

    insights-repository.tsBase insights nutrition on canonical available days +2/-1

    Base insights nutrition on canonical available days

    • Switches from v_nutrition_daily to v_nutrition_canonical_daily with availability filter to prevent overlap-inflated trends.
    

    packages/server/src/repositories/insights-repository.ts

    nutrition-analytics-repository.tsRead analytics from canonical contribution set +11/-11

    Read analytics from canonical contribution set

    • Switches micronutrient adequacy to v_nutrition_canonical_nutrient and updates adaptive TDEE/macro ratios to use v_nutrition_canonical_daily with conflict filtering.
    

    packages/server/src/repositories/nutrition-analytics-repository.ts

    nutrition-repository.tsReturn canonical daily nutrition with resolution metadata +46/-8

    Return canonical daily nutrition with resolution metadata

    • Switches query to v_nutrition_canonical_daily, sets provider_id only when exactly one contributor exists, and surfaces resolution/provenance fields to callers.
    

    packages/server/src/repositories/nutrition-repository.ts

    predictions-repository.tsUse canonical totals for predictions inputs +2/-1

    Use canonical totals for predictions inputs

    • Reads v_nutrition_canonical_daily and filters to available days to avoid double-counted training data.
    

    packages/server/src/repositories/predictions-repository.ts

    food-entry-repository.tsHandle source conflicts in Slack daily calorie progress +38/-11

    Handle source conflicts in Slack daily calorie progress

    • Switches calorie progress query to v_nutrition_canonical_daily, returns a union type for available vs source_conflict, and writes itemized nutrition_grain on Slack-created entries.
    

    packages/server/src/slack/food-entry-repository.ts

    NutritionPage.tsxRender source-conflict alert and hide summary UI when unavailable +76/-55

    Render source-conflict alert and hide summary UI when unavailable

    • Adds resolution parsing to the DTO, renders a conflict banner with provenance, and conditionally renders daily/macro summary only when summary is present.
    

    packages/web/src/pages/NutritionPage.tsx

    export.tsExport raw food_entry provenance rows +1/-1

    Export raw food_entry provenance rows

    • Changes export table config to export fitness.food_entry ordered by date/created_at instead of exporting v_nutrition_daily.
    

    src/export.ts

    db-insertion.tsMark Apple Health nutrition upserts as daily_aggregate +2/-0

    Mark Apple Health nutrition upserts as daily_aggregate

    • Sets nutritionGrain='daily_aggregate' on insert and conflict-update for Apple Health nutrition entries.
    

    src/providers/apple-health/db-insertion.ts

    auto-supplements.tsSet nutrition_grain to itemized for auto-supplements entries +3/-1

    Set nutrition_grain to itemized for auto-supplements entries

    • Ensures updated/inserted supplement-derived food entries are labeled itemized for correct canonical resolution.
    

    src/providers/auto-supplements.ts

    cronometer-csv.tsSet nutrition_grain to itemized for Cronometer CSV imports +2/-0

    Set nutrition_grain to itemized for Cronometer CSV imports

    • Marks both updated and inserted Cronometer CSV food entries as itemized so aggregates don’t overlap them.
    

    src/providers/cronometer-csv.ts

    provider.tsMark FatSecret entries as itemized +1/-0

    Mark FatSecret entries as itemized

    • Adds nutritionGrain='itemized' to FatSecret upsert payloads to participate correctly in canonical resolution.
    

    src/providers/fatsecret/provider.ts

    Tests (15) +723 / -16
    nutrition.cy.tsSeed itemized nutrition_grain in E2E inserts +8/-4

    Seed itemized nutrition_grain in E2E inserts

    • Updates test data inserts to set nutrition_grain='itemized' to match new classification expectations.
    

    cypress/e2e/nutrition.cy.ts

    food.test.tsxTest mobile source-conflict UI and nullable totals +54/-0

    Test mobile source-conflict UI and nullable totals

    • Updates mocks to include resolution metadata and adds an assertion that conflict renders an accessible alert while leaving totals unavailable.
    

    packages/mobile/app/(tabs)/food.test.tsx

    selected-date-summary.test.tsAdd tests for nutrition source resolution schema +22/-1

    Add tests for nutrition source resolution schema

    • Introduces parsing coverage for the new resolution DTO, including source_conflict provenance fields.
    

    packages/nutrition/src/selected-date-summary.test.ts

    route.test.tsUpdate MCP nutrition totals to include resolution fields +22/-6

    Update MCP nutrition totals to include resolution fields

    • Adjusts MCP tests to expect explicit resolution metadata and null totals for conflicts instead of implying a missing provider.
    

    packages/server/src/mcp/route.test.ts

    food-repository.test.tsAdd tests for canonical queries and conflict DTO mapping +73/-0

    Add tests for canonical queries and conflict DTO mapping

    • Extends test fixtures with resolution fields, asserts display-entry view usage, and verifies summary=null with provenance on source conflicts.
    

    packages/server/src/repositories/food-repository.test.ts

    nutrition-analytics-repository.test.tsAssert analytics queries use canonical views and availability filter +24/-0

    Assert analytics queries use canonical views and availability filter

    • Adds coverage ensuring micronutrients come from canonical nutrient rows and calories/macros use canonical daily totals with resolution_status='available'.
    

    packages/server/src/repositories/nutrition-analytics-repository.test.ts

    nutrition-canonical.integration.test.tsExecutable Postgres tests for canonical source resolution and provenance +314/-0

    Executable Postgres tests for canonical source resolution and provenance

    • Adds integration tests validating selection rules, conflict handling, legacy classification, isolation by user/date/confirmed, and downstream analytics correctness.
    

    packages/server/src/repositories/nutrition-canonical.integration.test.ts

    nutrition-repository.test.tsUpdate NutritionRepository tests for canonical view and providerId nullability +28/-1

    Update NutritionRepository tests for canonical view and providerId nullability

    • Extends fixtures to include resolution fields and asserts queries now target v_nutrition_canonical_daily.
    

    packages/server/src/repositories/nutrition-repository.test.ts

    food.test.tsTest food router returns resolution and null summary on conflicts +73/-0

    Test food router returns resolution and null summary on conflicts

    • Updates router tests to assert the new response shape includes resolution metadata and handles source_conflict by returning summary=null.
    

    packages/server/src/routers/food.test.ts

    nutrition-analytics.test.tsUpdate router SQL expectations for canonical micronutrient source +3/-3

    Update router SQL expectations for canonical micronutrient source

    • Adjusts assertions to match the canonical nutrient view query aliases and FROM clause.
    

    packages/server/src/routers/nutrition-analytics.test.ts

    bot-unit.test.tsUpdate Slack bot test fixture with resolution fields +8/-1

    Update Slack bot test fixture with resolution fields

    • Extends mocked DB row for daily calorie progress to include resolution metadata returned from canonical daily totals.
    

    packages/server/src/slack/bot-unit.test.ts

    formatting.test.tsAdd coverage for Slack conflict messaging +14/-0

    Add coverage for Slack conflict messaging

    • Verifies Slack output shows conflict provenance and avoids rendering calorie totals when sources overlap.
    

    packages/server/src/slack/formatting.test.ts

    NutritionPage.ai.test.tsxAdd web AI confirmation tests for resolution and conflicts +54/-0

    Add web AI confirmation tests for resolution and conflicts

    • Updates mocks to include resolution metadata and adds an accessibility test ensuring conflict days suppress remaining-calorie UI.
    

    packages/web/src/pages/NutritionPage.ai.test.tsx

    NutritionPage.test.tsUpdate schema parsing test for nullable summary + resolution +13/-0

    Update schema parsing test for nullable summary + resolution

    • Extends selectedDateFoodSchema parsing test to include the new resolution DTO and guards against null summary in the assertion.
    

    packages/web/src/pages/NutritionPage.test.ts

    export.test.tsEnsure export uses raw tables instead of aggregate serving view +13/-0

    Ensure export uses raw tables instead of aggregate serving view

    • Adds a regression test asserting exports query fitness.food_entry directly rather than v_nutrition_daily.
    

    src/export.test.ts

    Documentation (9) +117 / -18
    AGENTS.mdDocument canonical nutrition serving views +1/-1

    Document canonical nutrition serving views

    • Clarifies that v_nutrition_daily is a raw per-provider projection and introduces canonical serving views to prevent overlap double-counting.
    

    AGENTS.md

    README.mdUpdate README to reference canonical daily nutrition totals +4/-2

    Update README to reference canonical daily nutrition totals

    • Reframes v_nutrition_daily as provenance-only and points serving paths to v_nutrition_canonical_daily for overlap-safe totals.
    

    README.md

    apple-health.mdSpecify Apple Health imports as daily_aggregate and canonical serving +11/-1

    Specify Apple Health imports as daily_aggregate and canonical serving

    • Documents Apple Health quantity samples as daily_aggregate food_entry rows and notes canonical views exclude overlapping aggregates when itemized data exists.
    

    docs/apple-health.md

    nutrition-ai-input.mdAlign AI nutrition docs with canonical totals views +7/-1

    Align AI nutrition docs with canonical totals views

    • Clarifies raw totals vs serving totals and references canonical daily/nutrient views for overlap-safe metrics.
    

    docs/nutrition-ai-input.md

    production-incident-baseline.mdRecord local Docker disk exhaustion during #2059 validation +30/-0

    Record local Docker disk exhaustion during #2059 validation

    • Adds an incident note explaining why local DB-backed validation was blocked and what CI must run before merge.
    

    docs/production-incident-baseline.md

    schema.dbmlAdd nutrition_entry_grain and food_entry.nutrition_grain +7/-1

    Add nutrition_entry_grain and food_entry.nutrition_grain

    • Extends schema diagrams with nutrition_entry_grain enum and food_entry.nutrition_grain column.
    

    docs/schema.dbml

    schema.mdDocument canonical nutrition views and conflict behavior +43/-11

    Document canonical nutrition views and conflict behavior

    • Expands schema docs to describe grain classification, resolution rules, canonical views, and conflict semantics with explicit provenance.
    

    docs/schema.md

    schema.pumlUpdate PUML schema with nutrition_grain +2/-1

    Update PUML schema with nutrition_grain

    • Adds nutrition_grain to the food_entry entity for diagram parity with DB schema.
    

    docs/schema.puml

    README.mdDocument selected-date nutrition contract and provenance +12/-0

    Document selected-date nutrition contract and provenance

    • Explains that clients render server-owned totals and source-resolution metadata derived from PostgreSQL views.
    

    packages/nutrition/README.md

    Other (3) +24 / -4
    _journal.jsonRegister migration 0059 in Drizzle journal +7/-0

    Register migration 0059 in Drizzle journal

    • Adds migration metadata entry for 0059_canonical_nutrition_contribution_set.
    

    drizzle/meta/_journal.json

    food.stories.tsxAdd resolution DTO to mobile food stories +10/-0

    Add resolution DTO to mobile food stories

    • Extends story seed data with resolution metadata to match the updated FoodByDate contract.
    

    packages/mobile/app/(tabs)/food.stories.tsx

    nutrition.tsSeed nutrition_grain for daily aggregates and itemized entries +7/-4

    Seed nutrition_grain for daily aggregates and itemized entries

    • Marks Apple Health seed totals as daily_aggregate and manual food entries as itemized to match canonical resolution expectations.
    

    scripts/seed/nutrition.ts

    @github-actions

    github-actions Bot commented Jul 27, 2026

    Copy link
    Copy Markdown
    Contributor

    Mobile Preview

    Scan to open on device:

    QR code for dofek://preview/pr-2220

    Channel pr-2220
    Deep Link dofek://preview/pr-2220
    Commit 4fe603e

    To test on device:

    1. Build and install the preview client: PREVIEW_CHANNEL=pr-2220 pnpm expo prebuild --clean -p ios
    2. Or tap deep link on an existing preview build: dofek://preview/pr-2220

    Each PR gets its own channel. Build a preview client with PREVIEW_CHANNEL=pr-{N} to test.

    @github-actions

    github-actions Bot commented Jul 27, 2026

    Copy link
    Copy Markdown
    Contributor

    Storybook previews for 4fe603e5 are ready:

    This comment updates automatically on each PR push.

    Retain v_nutrition_daily as the serving contract and rename the raw
    provider projection to v_nutrition_provider_daily. Format migration 0059
    for the strict SQLFluff gate.
    
    Refs #2059
    @codereviewbot-ai

    codereviewbot-ai Bot commented Jul 27, 2026

    Copy link
    Copy Markdown

    Summary of Review

    This PR introduces a well-structured canonical nutrition contribution set that resolves provider overlap, handles itemized vs. aggregate food entry grains, and cleanly surfaces resolution statuses to consumers.

    Key Findings:

    1. Unconfirmed Entries in Canonical Nutrients (0059_canonical_nutrition_contribution_set.sql): v_nutrition_canonical_nutrient joined v_nutrition_entry_classification without checking classification.confirmed = TRUE. This allows unconfirmed draft entries from a contributing source to be included in nutrient totals and analytics.
    2. Unconfirmed Entries in Meal Count (food-repository.ts): dailyTotalsRange joined v_nutrition_display_entry without display.confirmed = true, including unconfirmed entries in the calculated meal_count.

    Both issues have inline suggestions attached. Overall, the schema, resolution logic, and mobile/web UI additions look clean and well-tested!


    🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

    Comment thread drizzle/0060_canonical_nutrition_contribution_set.sql
    Comment thread packages/server/src/repositories/food-repository.ts
    @qodo-code-review

    qodo-code-review Bot commented Jul 27, 2026

    Copy link
    Copy Markdown

    Code Review by Qodo

    🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

    Context used
    ✅ Compliance rules (platform): 251 rules

    Grey Divider


    Action required

    1. Drafts leak into totals ✓ Resolved 🐞 Bug ≡ Correctness
    Description
    fitness.v_nutrition_canonical_nutrient does not filter food_entry.confirmed, so unconfirmed rows
    that match the selected (user_id,date,source_key,grain) can be included in canonical nutrient rows
    and summed into fitness.v_nutrition_canonical_daily. This breaks the invariant that canonical
    totals are computed from confirmed entries only and can silently change served totals and downstream
    analytics.
    
    Code

    drizzle/0059_canonical_nutrition_contribution_set.sql[R186-202]

    +CREATE VIEW fitness.v_nutrition_canonical_nutrient AS
    +SELECT
    +  classification.user_id,
    +  classification.date,
    +  classification.provider_id,
    +  classification.id AS food_entry_id,
    +  nutrient.nutrient_id,
    +  nutrient.amount
    +FROM fitness.v_nutrition_entry_classification classification
    +JOIN fitness.v_nutrition_daily_resolution resolution
    +  ON resolution.user_id = classification.user_id
    +  AND resolution.date = classification.date
    +JOIN fitness.food_entry_nutrient nutrient
    +  ON nutrient.food_entry_id = classification.id
    +WHERE resolution.resolution_status = 'available'
    +  AND classification.source_key = ANY(resolution.contributing_source_keys)
    +  AND classification.effective_grain = resolution.contribution_grain;
    Relevance

    ⭐⭐⭐ High

    Confirmed-only invariant is explicit; missing predicate can silently corrupt canonical totals.
    

    ⓘ Recommendations generated based on similar findings in past PRs

    Evidence
    Resolution is computed from confirmed-only rows, but canonical nutrients re-join classification
    without a confirmed predicate, unlike the legacy daily totals view which explicitly filters
    confirmed entries.
    

    drizzle/0059_canonical_nutrition_contribution_set.sql[41-46]
    drizzle/0059_canonical_nutrition_contribution_set.sql[186-202]
    drizzle/0006_row_based_nutrients.sql[369-372]
    src/db/schema/nutrition.ts[98-130]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    `fitness.v_nutrition_daily_resolution` decides the contribution set using only `confirmed = true` rows, but `fitness.v_nutrition_canonical_nutrient` re-joins the (unfiltered) classification view on `(user_id, date)` and only constrains `source_key`/`effective_grain`. Any unconfirmed food entry that shares the selected source_key/date/grain can therefore contribute nutrients to canonical totals.
    
    ## Issue Context
    Legacy `fitness.v_nutrition_daily` explicitly filters `fe.confirmed = true`. The new canonical projections should preserve that behavior so drafts/pending rows never affect served totals.
    
    ## Fix Focus Areas
    - drizzle/0059_canonical_nutrition_contribution_set.sql[186-203]
    
    ### Suggested fix
    - Add `AND classification.confirmed = true` to `fitness.v_nutrition_canonical_nutrient` (or rewrite it to source from the confirmed CTE / a confirmed-only classification relation).
    - Add an executable regression in `src/repositories/nutrition-canonical.integration.test.ts` that inserts both a confirmed and an unconfirmed entry with the same provider/source_key on the same date and asserts canonical totals only reflect the confirmed entry.
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



    Remediation recommended

    2. context.db.execute unvalidated rows ✓ Resolved 📘 Rule violation ≡ Correctness
    Description
    The new integration test and the updated seed script both consume raw SQL results returned from
    context.db.execute<...>(sql...) using TypeScript generics and direct destructuring/assertions
    without any Zod parsing/validation. This violates the requirement to validate raw SQL query results
    and can mask unexpected row shapes, leading to silent schema drift or runtime surprises when DB
    projections change.
    
    Code

    packages/server/src/repositories/nutrition-canonical.integration.test.ts[R101-113]

    +    const dailyRows = await context.db.execute<{
    +      calories: number;
    +      protein_g: number;
    +      resolution_status: string;
    +      source_providers: string[];
    +      contributing_providers: string[];
    +      excluded_providers: string[];
    +    }>(sql`
    +      SELECT calories, protein_g, resolution_status, source_providers,
    +             contributing_providers, excluded_providers
    +      FROM fitness.v_nutrition_canonical_daily
    +      WHERE user_id = ${TEST_USER_ID} AND date = ${date}::date
    +    `);
    Relevance

    ⭐⭐⭐ High

    Strong precedent to validate raw SQL results with schema-validated helpers instead of generic
    execute().
    

    PR-#2215

    ⓘ Recommendations generated based on similar findings in past PRs

    Evidence
    PR Compliance ID 722091 requires validating raw SQL query results with Zod. In the integration test,
    SELECT queries are executed via context.db.execute<...> and the returned rows (e.g.,
    dailyRows) are used directly in assertions without a z.object(...).array().parse(...) (or
    similar) validation step, and in the seed script, raw SQL queries that return rows (including
    RETURNING id) are immediately destructured/treated as FoodEntryRow[] via sql<FoodEntryRow[]>
    without any runtime Zod parsing/validation, demonstrating non-compliance in both locations.
    

    Rule 722091: Validate all raw SQL query results with Zod schemas
    packages/server/src/repositories/nutrition-canonical.integration.test.ts[101-113]
    scripts/seed/nutrition.ts[24-40]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    Raw SQL query results in both the new integration test and the seed script are trusted via TypeScript generics and then used/destructured directly without runtime validation, violating the requirement to validate raw SQL results with Zod.
    
    ## Issue Context
    PR Compliance ID 722091 requires that any rows returned from raw SQL (`SELECT`, `RETURNING ...`, etc.) be validated with Zod schemas (e.g., `z.object(...).array().parse(...)`) before being consumed in tests or seed logic, to prevent schema drift and unexpected row shapes from slipping through.
    
    ## Fix Focus Areas
    - packages/server/src/repositories/nutrition-canonical.integration.test.ts[101-277]
    - scripts/seed/nutrition.ts[24-41]
    - scripts/seed/nutrition.ts[91-102]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    3. Drafts inflate meal_count ✓ Resolved 🐞 Bug ≡ Correctness
    Description
    FoodRepository.dailyTotalsRange() counts COUNT(display.id) from
    fitness.v_nutrition_display_entry, but that view does not filter confirmed = true, so
    unconfirmed itemized entries can inflate meal_count even though canonical totals are
    confirmed-only. This causes incorrect meal counts in range totals (e.g., MCP/analytics outputs).
    
    Code

    packages/server/src/repositories/food-repository.ts[R714-726]

    +            COUNT(display.id)::int AS meal_count,
    +            daily.source_providers,
    +            daily.resolution_status,
    +            daily.resolution_message,
    +            daily.contributing_providers,
    +            daily.excluded_providers,
    +            daily.source_labels,
    +            daily.contributing_source_labels,
    +            daily.excluded_source_labels
    +          FROM fitness.v_nutrition_canonical_daily daily
    +          LEFT JOIN fitness.v_nutrition_display_entry display
    +            ON display.user_id = daily.user_id AND display.date = daily.date
    +          WHERE daily.user_id = ${this.#userId}
    Relevance

    ⭐⭐⭐ High

    Deterministic correctness fix: meal_count should not include unconfirmed/draft display rows.
    

    ⓘ Recommendations generated based on similar findings in past PRs

    Evidence
    The display view is defined without a confirmation predicate, and dailyTotalsRange() joins it only
    on (user_id, date) then counts IDs; therefore drafts are counted.
    

    drizzle/0059_canonical_nutrition_contribution_set.sql[269-273]
    packages/server/src/repositories/food-repository.ts[703-736]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    `fitness.v_nutrition_display_entry` includes unconfirmed rows, and `dailyTotalsRange()` counts display rows without filtering confirmation. This makes `meal_count` inconsistent with canonical totals (which are computed from confirmed-only resolution inputs).
    
    ## Issue Context
    Entry list queries (`byDate`, `list`, `search`) explicitly apply `confirmed = true`, but `dailyTotalsRange()` currently does not.
    
    ## Fix Focus Areas
    - drizzle/0059_canonical_nutrition_contribution_set.sql[269-273]
    - packages/server/src/repositories/food-repository.ts[703-736]
    
    ### Suggested fix
    Choose one:
    1) Prefer view-level safety: update `fitness.v_nutrition_display_entry` to include `WHERE entry.confirmed = true AND classification.effective_grain = 'itemized'`.
    2) Or query-level fix: add `AND display.confirmed = true` to the LEFT JOIN condition in `dailyTotalsRange()`.
    
    Add/extend a test to insert an unconfirmed itemized entry on a day and assert `meal_count` does not include it.
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    4. NutritionPage test uses render() ✗ Dismissed 📜 Skill insight ▣ Testability
    Description
    The updated UI tests render full page/screen components (NutritionPage/FoodScreen) instead of
    testing an exported builder/pure function as required by the compliance checklist. This makes the
    tests more brittle, less deterministic, and less focused on exercising pure logic.
    
    Code

    packages/web/src/pages/NutritionPage.ai.test.tsx[R233-272]

    +  it("renders an accessible source conflict and leaves totals unavailable", async () => {
    +    foodByDateQuery = {
    +      data: {
    +        entries: [
    +          {
    +            id: "food-1",
    +            food_name: "Named itemized meal",
    +            meal: "breakfast",
    +            calories: 420,
    +            protein_g: 20,
    +            carbs_g: 50,
    +            fat_g: 15,
    +            food_description: null,
    +          },
    +        ],
    +        summary: null,
    +        resolution: {
    +          status: "source_conflict",
    +          message:
    +            "Totals are unavailable because nutrition sources overlap and no canonical contribution set can be determined.",
    +          sourceProviders: ["apple-health", "cronometer"],
    +          contributingProviders: [],
    +          excludedProviders: ["apple-health", "cronometer"],
    +          sourceLabels: ["Apple Health", "Cronometer"],
    +          contributingSourceLabels: [],
    +          excludedSourceLabels: ["Apple Health", "Cronometer"],
    +        },
    +      },
    +      error: null,
    +      isLoading: false,
    +    };
    +    const { NutritionPage } = await import("./NutritionPage");
    +
    +    render(<NutritionPage />);
    +
    +    expect(screen.getByRole("alert")).toHaveTextContent("Totals are unavailable");
    +    expect(screen.getByRole("alert")).toHaveTextContent("Apple Health, Cronometer");
    +    expect(screen.queryByText(/kcal remaining/)).toBeNull();
    +    expect(screen.getByText("Named itemized meal")).toBeTruthy();
    +  });
    Relevance

    ⭐⭐ Medium

    Policy says avoid rendering full pages in tests, but repo commonly uses RTL render; unclear
    enforcement.
    

    ⓘ Recommendations generated based on similar findings in past PRs

    Evidence
    PR Compliance ID 722152 requires UI tests to call exported builder functions rather than render full
    React components. The newly added test blocks cited in
    packages/web/src/pages/NutritionPage.ai.test.tsx and packages/mobile/app/(tabs)/food.test.tsx
    explicitly use React Testing Library’s render(<NutritionPage />) / render(<FoodScreen />) to
    validate the source-conflict alert behavior, directly demonstrating non-compliance with the policy
    and the resulting component-render-based brittleness.
    

    packages/web/src/pages/NutritionPage.ai.test.tsx[233-272]
    packages/mobile/app/(tabs)/food.test.tsx[303-342]
    Skill: write-tests

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The newly added UI tests are rendering full React components (`render(<NutritionPage />)` / `render(<FoodScreen />)`), but PR Compliance ID 722152 requires UI tests to exercise exported builder (pure) functions rather than rendering components.
    
    ## Issue Context
    These tests are intended to verify the source-conflict alert UX, including the alert copy and accessibility role/labels. To comply with the checklist and make the tests more deterministic, extract the alert content/props/labels into an exported pure function (e.g., `buildNutritionSourceConflictAlert(...)` or similar) and unit test the returned values/structure instead of rendering the full page/screen.
    
    ## Fix Focus Areas
    - packages/web/src/pages/NutritionPage.ai.test.tsx[233-272]
    - packages/mobile/app/(tabs)/food.test.tsx[303-342]
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



    Informational

    5. Aggregate days trigger noisy logs ✓ Resolved 🐞 Bug ◔ Observability
    Description
    FoodRepository.byDate() now reads from fitness.v_nutrition_display_entry (itemized-only), so
    aggregate-only days will legitimately return entries.length === 0. foodRouter.byDate logs this
    as an info-level anomaly, which can become noisy and reduce signal in logs.
    
    Code

    packages/server/src/repositories/food-repository.ts[R569-575]

        const rows = await executeWithSchema(
          this.#db,
          foodEntryRowSchema,
    -      sql`SELECT * FROM fitness.v_food_entry_with_nutrition
    +      sql`SELECT * FROM fitness.v_nutrition_display_entry
              WHERE user_id = ${this.#userId}
                AND confirmed = true
                AND date = ${date}::date
    Relevance

    ⭐⭐⭐ High

    Team has accepted reducing false-positive/noisy logging when empty results are legitimate.
    

    PR-#787

    ⓘ Recommendations generated based on similar findings in past PRs

    Evidence
    The display view filters to itemized entries only, so aggregate-only days naturally return 0
    entries, but the router still logs when the entries array is empty.
    

    drizzle/0059_canonical_nutrition_contribution_set.sql[269-273]
    packages/server/src/repositories/food-repository.ts[567-579]
    packages/server/src/routers/food.ts[128-145]

    Agent prompt
    The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
    
    ## Issue description
    The byDate router currently logs whenever `entries.length === 0`. After switching byDate to the itemized-only display surface, zero entries is expected for aggregate-only days and should not be treated as notable.
    
    ## Issue Context
    Canonical totals can still be available via `fitness.v_nutrition_canonical_daily` even when there are no display entries.
    
    ## Fix Focus Areas
    - packages/server/src/repositories/food-repository.ts[567-579]
    - packages/server/src/routers/food.ts[132-145]
    
    ### Suggested fix
    Update the router logging to only log when both:
    - entries are empty, and
    - the nutrition resolution indicates no sources (e.g., `resolution.status === 'available'` and `sourceProviders.length === 0`),
    or lower the log level to debug and/or include resolution status in the log context.
    

    ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


    Grey Divider

    Qodo Logo

    Comment thread packages/server/src/repositories/nutrition-canonical.integration.test.ts Outdated
    Comment thread packages/web/src/pages/NutritionPage.ai.test.tsx
    Comment thread drizzle/0060_canonical_nutrition_contribution_set.sql
    Comment thread packages/server/src/repositories/food-repository.ts
    Comment thread packages/server/src/repositories/food-repository.ts
    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    Asherlc added 2 commits July 27, 2026 13:48
    …sue-2059
    
    # Conflicts:
    #	docs/production-incident-baseline.md
    #	drizzle/meta/_journal.json
    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @codereviewbot-ai

    Copy link
    Copy Markdown

    🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

    @Asherlc
    Asherlc merged commit 84fc060 into main Jul 27, 2026
    115 checks passed
    @Asherlc
    Asherlc deleted the codex/issue-2059 branch July 27, 2026 21:58
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    [P0-01] Nutrition double-counts provider daily aggregates with itemized meals

    2 participants