Skip to content

fix(nutrition): explain adaptive TDEE evidence - #2342

Merged
Asherlc merged 2 commits into
mainfrom
Asherlc/issue-2137-tdee-evidence
Jul 30, 2026
Merged

Asherlc merged 2 commits into
mainfrom
Asherlc/issue-2137-tdee-evidence

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • seed 90 deterministic canonical ClickHouse body-weight samples for the review user while preserving unrelated metric-stream rows
  • replace uncalibrated adaptive-TDEE confidence with server-owned evidence, explicit unavailable reasons, and the observed accepted-window range
  • render the same evidence contract on web and mobile, with review stories for available and unavailable states

Root cause

The review fixture populated Postgres DEXA rows but no canonical ClickHouse body_weight sensor samples. Adaptive TDEE reads weight only through the deduped ClickHouse body-measurement model, so retries could never produce an estimate. The estimator also treated 28 available nutrition rows as 28 calendar days and exposed a weight-row ratio as confidence.

Validation

  • pnpm test:changed (14 files, 127 tests)
  • pnpm --filter @dofek/server typecheck
  • pnpm --filter @dofek/web typecheck
  • pnpm --filter @dofek/mobile typecheck
  • pnpm lint:sandbox
  • web and mobile Storybook builds
  • real Postgres/ClickHouse review-seed integration test, including idempotent rerun and unrelated-row preservation
  • focused dbt build: +daily_body_measurement
  • live repository read over the seeded review fixture returned TDEE 2420 kcal/day, observed range 2420–2626, 89 calorie days, 90 weight days, and 62 accepted windows

The full local analytics integration suite could not complete because the shared Docker VM repeatedly restarted ClickHouse while many workspace containers were active. Focused real-engine coverage and the production repository path passed; CI will run the isolated suite. Root pnpm lint also reaches unchanged analytics SQL and fails on pre-existing sqlfluff CP04/LT08 behavior; all changed-code lint gates pass via pnpm lint:sandbox.

Fixes #2137

Summary by Sourcery

Explain adaptive TDEE availability using canonical ClickHouse body-weight samples, dense calendar evaluation, and server-owned evidence, and render the new evidence contract consistently on web and mobile.

New Features:

  • Expose adaptive TDEE status, evidence, estimate range, and explicit unavailability reasons from the nutrition analytics API.
  • Render adaptive TDEE evidence details, including fit window requirements, coverage, exclusions, and observed estimate range, in web and mobile clients.
  • Add web and mobile Storybook scenarios and tests for available and unavailable adaptive TDEE states.

Bug Fixes:

  • Ensure the review fixture seeds canonical ClickHouse body-weight samples without truncating unrelated metric-stream data, enabling adaptive TDEE in the review user.
  • Evaluate adaptive TDEE over true calendar days, counting missing and conflicting nutrition dates against the fit-window eligibility instead of skipping them.
  • Replace the uncalibrated confidence metric with concrete evidence counts and range information that describe why an estimate is available or unavailable.

Enhancements:

  • Refine adaptive TDEE repository queries to read nutrition resolution status and exclusion context, and propagate them through smoothing and estimation.
  • Extend server-side integration tests to assert evidence semantics, conflict exclusions, and daily calendar coverage for adaptive TDEE.

Documentation:

  • Document the ClickHouse review seed behavior, canonical body-weight fixture, and adaptive TDEE evidence availability in the review scenario plan and fixture docs.

Tests:

  • Add unit and integration tests for adaptive TDEE calendar building, evidence reporting, unavailability reasons, and observed rolling estimate ranges.
  • Expand web and mobile tests and Storybook fixtures to validate rendering of the new adaptive TDEE evidence and unavailable states.
  • Strengthen ClickHouse review seed integration tests to verify deterministic body-weight sample insertion and preservation of existing sensor rows.

Summary by cubic

Replaced adaptive TDEE “confidence” with server-owned fit evidence and an observed rolling range, and seeded deterministic canonical body-weight data so the review fixture produces estimates on web and mobile. Uses one timezone-local end date across stores and bounds “All time” evaluation to 365 days. Fixes #2137.

  • New Features

    • Server returns status, unavailableReason, evidence (fit window, coverage, exclusions), and estimateRange; builds dense calendar days, intersects access windows, and uses a single user-timezone end date.
    • “All time” selection is bounded to 365 days for adaptive TDEE.
    • Review seed inserts 90 deterministic body_weight rows into canonical ingest.metric_stream, tombstoning only prior review-seed-body-weight-* rows; all metric-stream columns are named and unrelated sensor rows are preserved.
    • Web and mobile render the same evidence and unavailable reasons; show “Observed rolling range”; added available/unavailable stories and tests.
  • Migration

    • API: stop reading confidence and dataPoints. Use status, estimateRange, unavailableReason, and evidence. dailyData.caloriesIn may be null, and dailyData includes nutritionStatus and lowerPrioritySourcesExcluded.
    • Review data: run pnpm review:seed-clickhouse to add canonical weight samples.

Written for commit b81f29e. Summary will update on new commits.

Review in cubic

Replace uncalibrated confidence with server-owned fit evidence and an observed rolling range. Seed canonical review body-weight samples without replacing unrelated sensor data.\n\nRefs #2137
Copilot AI review requested due to automatic review settings July 30, 2026 06:39
@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 30, 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.

@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.

@coderabbitai

coderabbitai Bot commented Jul 30, 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: 18 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: 5e20e112-517b-421b-b392-1da7241e3553

📥 Commits

Reviewing files that changed from the base of the PR and between 509986e and b81f29e.

📒 Files selected for processing (24)
  • docs/review-fixture-scenarios.md
  • docs/superpowers/plans/2026-07-29-adaptive-tdee-evidence.md
  • packages/mobile/.storybook/nutrition-analytics-story-link.test.ts
  • packages/mobile/.storybook/nutrition-analytics-story-link.ts
  • packages/mobile/app/nutrition-analytics.stories.tsx
  • packages/mobile/app/nutrition-analytics.test.tsx
  • packages/mobile/app/nutrition-analytics.tsx
  • 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/routers/nutrition-analytics-data.integration.test.ts
  • packages/server/src/routers/nutrition-analytics-pmc-power.test.ts
  • packages/server/src/routers/nutrition-analytics.test.ts
  • packages/server/src/routers/nutrition-analytics.ts
  • packages/server/src/routers/router.integration.test.ts
  • packages/web/src/components/AdaptiveTdeeChart.stories.tsx
  • packages/web/src/components/AdaptiveTdeeChart.test.tsx
  • packages/web/src/components/AdaptiveTdeeChart.tsx
  • packages/web/src/pages/NutritionAnalyticsPage.stories.tsx
  • packages/web/src/pages/NutritionAnalyticsPage.test.tsx
  • scripts/README.md
  • scripts/seed-review-clickhouse.integration.test.ts
  • scripts/seed-review-clickhouse.test.ts
  • scripts/seed-review-clickhouse.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 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors adaptive TDEE to operate on dense calendar-day windows with explicit evidence and unavailability reasons, wires that new contract through the server, web, mobile, and review ClickHouse seed, and adds deterministic body-weight samples plus tests and docs for the review fixture.

Sequence diagram for adaptive TDEE estimation with evidence

sequenceDiagram
  actor Client
  participant Router as nutritionAnalyticsRouter
  participant Repo as NutritionAnalyticsRepository
  participant CH as ClickHouse
  participant Estimator as adaptiveTdeeEngine

  Client->>Router: adaptiveTdee({ days })
  Router->>Repo: getAdaptiveTdee(days)
  Repo->>Repo: getAdaptiveTdeeData(days)
  Repo->>CH: query fitness.v_nutrition_daily
  Repo->>CH: query analytics.daily_body_measurement
  CH-->>Repo: AdaptiveTdeeDataPoint[]
  Repo->>Repo: buildAdaptiveTdeeCalendar(sourceData, days, today, accessWindow)
  Repo->>Repo: smoothWeightData(calendar)
  Repo->>Estimator: estimateTdee(smoothedData, days)
  Estimator-->>Repo: AdaptiveTdeeResultData
  Repo->>Router: new AdaptiveTdeeEstimate(result)
  Router-->>Client: AdaptiveTdeeResult (status, estimateRange, evidence, dailyData)
Loading

File-Level Changes

Change Details Files
Replace confidence/data-point based adaptive TDEE result with a status, estimate range, server-authored unavailability reasons, and a structured evidence object, and adjust the estimator to work on dense calendar-day windows with nutrition conflict/missing-day handling.
  • Extend AdaptiveTdee result types to include status, estimateRange, unavailableReason, evidence, and nullable calories plus nutrition status fields.
  • Introduce calendar-building helpers (date parsing/shifting, dense window construction) and use them to create one row per calendar day based on access window and selected range.
  • Update TDEE estimation to track rolling window estimates, compute coverage/exclusion counts, enforce a minimum usable calorie-day threshold, and derive explicit unavailability reasons instead of confidence.
  • Adapt smoothing and estimation tests to the new data shape and add focused tests for calendar densification, insufficiency explanations, and observed estimate ranges.
packages/server/src/repositories/nutrition-analytics-repository.ts
packages/server/src/repositories/nutrition-analytics-repository.test.ts
packages/server/src/routers/nutrition-analytics.ts
packages/server/src/routers/router.integration.test.ts
packages/server/src/routers/nutrition-analytics-data.integration.test.ts
packages/server/src/routers/nutrition-analytics-pmc-power.test.ts
Change adaptive TDEE data fetching to read canonical nutrition resolution and exclusion context from ClickHouse, join weight by date without filtering to available-only calories, and feed that into the new calendar-based estimator.
  • Update ClickHouse nutrition query to return calories_in as nullable plus resolution_status and excluded_source_labels instead of pre-filtered available calories.
  • Build AdaptiveTdeeDataPoint maps keyed by date, attach weight rows to existing nutrition rows, and default missing dates to a "missing" nutrition status so conflicts and gaps are modeled.
  • Use the new calendar builder and estimator with selectedWindowDays and accessWindow when computing adaptive TDEE in the repository.
  • Tighten integration tests to assert source_conflict days are preserved, excluded in evidence, and visible in dailyData.
packages/server/src/repositories/nutrition-analytics-repository.ts
packages/server/src/repositories/nutrition-analytics-repository.test.ts
packages/server/src/routers/nutrition-analytics-data.integration.test.ts
Seed deterministic canonical body-weight samples for the review user in ClickHouse via ingest.metric_stream, while preserving unrelated metric-stream rows and supporting idempotent reruns.
  • Introduce helper constants for the review user UUID and a review body-weight external-id prefix in the seed script.
  • Add a tombstone INSERT that marks prior review-seed body_weight rows as deleted using FINAL reads and is_deleted flags rather than truncating metric_stream.
  • Add an INSERT that generates 90 daily body_weight rows via numbers(90), with stable external IDs, a gentle weight trend, and review metadata.
  • Extend the ClickHouse seed integration test to run the script twice, assert sentinel rows are preserved, and verify exactly 90 non-deleted review-seed body_weight rows exist.
  • Add a unit test ensuring the seed script never truncates ingest.metric_stream and that it targets body_weight with the expected prefix and count.
scripts/seed-review-clickhouse.ts
scripts/seed-review-clickhouse.integration.test.ts
scripts/seed-review-clickhouse.test.ts
scripts/README.md
docs/review-fixture-scenarios.md
Update web adaptive TDEE chart to consume the new evidence-based contract, render estimate range and evidence details, and show explicit server-authored unavailable reasons with matching stories and tests.
  • Change AdaptiveTdeeChart to treat caloriesIn as nullable, remove confidence/dataPoints display, add estimateRange and evidence rendering, and show unavailableReason when status is "unavailable".
  • Extract an AdaptiveTdeeEvidence subcomponent that formats fit window, coverage, exclusions, and accepted windows from the evidence object.
  • Adjust chart visibility so an empty dailyData array still shows textual evidence/unavailability instead of an empty chart.
  • Update Storybook stories to include available, loading, and unavailable evidence scenarios using the new contract.
  • Revise component and page tests to assert on observed rolling range, evidence counts, exclusion text, and unavailableReason formatting instead of confidence/dataPoints.
packages/web/src/components/AdaptiveTdeeChart.tsx
packages/web/src/components/AdaptiveTdeeChart.test.tsx
packages/web/src/components/AdaptiveTdeeChart.stories.tsx
packages/web/src/pages/NutritionAnalyticsPage.stories.tsx
packages/web/src/pages/NutritionAnalyticsPage.test.tsx
Update mobile nutrition analytics to render the new adaptive TDEE evidence-based contract, including estimate range and explicit unavailability reasons, and add Storybook parity for available/unavailable scenarios.
  • Change AdaptiveTdeeSection to use status, estimateRange, unavailableReason, and evidence instead of confidence/dataPoints, and to show server-authored unavailability messaging when estimatedTdee is null.
  • Add an AdaptiveTdeeEvidence subcomponent mirroring the web evidence formatting (fit window, coverage, exclusions, accepted windows).
  • Update the mobile Storybook link and stories to support an "unavailable" scenario and to route available vs unavailable data through the story link.
  • Extend mobile tests to cover unavailable evidence rendering, available estimate range rendering, exclusion counts, and the new contract shape.
  • Add a new test for the story link unavailable scenario to assert the evidence calorieDays/weightDays match expectations.
packages/mobile/app/nutrition-analytics.tsx
packages/mobile/app/nutrition-analytics.test.tsx
packages/mobile/app/nutrition-analytics.stories.tsx
packages/mobile/.storybook/nutrition-analytics-story-link.ts
packages/mobile/.storybook/nutrition-analytics-story-link.test.ts
Document and plan the adaptive TDEE evidence work, including a TDD plan and updated review fixture documentation referencing canonical body weight and unavailable-story coverage.
  • Add a superpowers plan document that captures current evidence, test strategy, file structure, and task breakdown for adaptive TDEE evidence and review fixture behavior.
  • Update the review fixture scenario docs to mention the ClickHouse review seed’s canonical body-weight samples and link to web/mobile unavailable stories.
  • Ensure documentation aligns the seed script behavior with the metric-stream retirement record and new adaptive TDEE evidence contract.
docs/superpowers/plans/2026-07-29-adaptive-tdee-evidence.md
docs/review-fixture-scenarios.md
scripts/README.md

Assessment against linked issues

Issue Objective Addressed Explanation
#2137 Ensure the adaptive TDEE estimator produces an estimate in the review fixture by providing appropriate canonical body-weight data instead of remaining empty after retries. ✅
#2137 When adaptive TDEE is unavailable, have the server explain data sufficiency, fit window requirements, exclusions, and a clear reason for why the model is unavailable. ✅
#2137 When adaptive TDEE is available, expose and render on both web and mobile the estimate’s evidence: fit window, minimum calorie days, observed coverage, exclusions, and the observed estimate range (uncertainty) rather than a bare point estimate. ✅

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

Explain adaptive TDEE availability with server-owned evidence and seeded weights

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Seed deterministic ClickHouse body-weight samples for the review user without truncating
 metric-stream data.
• Replace adaptive-TDEE “confidence” with server-owned evidence, explicit unavailability reasons,
 and observed rolling range.
• Render the same evidence contract on web and mobile, with tests and Storybook scenarios.
Diagram

graph TD
  Seed["Review CH seed"] --> CH[("ClickHouse metric_stream")]
  PG[("Postgres fitness")]
  CH --> Repo["NutritionAnalyticsRepository"]
  PG --> Repo
  Repo --> API["adaptiveTdee API"]
  API --> Web["Web AdaptiveTdeeChart"]
  API --> Mobile["Mobile NutritionAnalytics"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Client-derived evidence (compute from dailyData on web/mobile)
  • ➕ Avoids expanding the API response surface
  • ➕ Lets clients iterate on presentation without server changes
  • ➖ Duplicates eligibility/window logic across clients (risk of drift)
  • ➖ Harder to keep wording/reason codes consistent
  • ➖ Makes fixture/debugging harder because “truth” is split across tiers
2. Push evidence computation into analytics/dbt views
  • ➕ Evidence becomes queryable/debuggable directly in ClickHouse views
  • ➕ Potentially reduces per-request server compute
  • ➖ More complex SQL and slower iteration on eligibility semantics
  • ➖ Harder to express nuanced unavailability reason selection logic
  • ➖ Still requires API/clients to interpret multiple derived fields

Recommendation: Keep the PR’s server-owned evidence and reason strings. It centralizes eligibility semantics (dense calendar-day windows, exclusions, accepted-window range) and guarantees web/mobile parity while preventing clients from mislabeling a coverage ratio as uncertainty.

Files changed (22) +1106 / -152

Enhancement (3) +98 / -24
nutrition-analytics.tsxRender adaptive TDEE evidence, rolling range, and unavailable reason on mobile +40/-8

Render adaptive TDEE evidence, rolling range, and unavailable reason on mobile

• Updates the adaptive TDEE section to no longer rely on confidence/dataPoints. Adds an evidence subcomponent that renders fit-window requirements, coverage, accepted windows, and exclusions, and shows the server-authored unavailable reason when no estimate exists.

packages/mobile/app/nutrition-analytics.tsx

nutrition-analytics.tsUpdate adaptive TDEE router types to include evidence and nutrition statuses +8/-3

Update adaptive TDEE router types to include evidence and nutrition statuses

• Updates exported AdaptiveTdeeResult/Row typings to match the new server contract (status, estimateRange, unavailableReason, evidence, and per-day nutritionStatus/lowerPrioritySourcesExcluded). Removes confidence/dataPoints from the API surface.

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

AdaptiveTdeeChart.tsxRender server-owned adaptive TDEE evidence and unavailable reason on web +50/-13

Render server-owned adaptive TDEE evidence and unavailable reason on web

• Removes the confidence/dataPoints summary and instead renders observed rolling range plus evidence (fit window, minimum calorie days, coverage, exclusions). Shows the unavailableReason when status is unavailable and avoids rendering a chart when no daily data exists.

packages/web/src/components/AdaptiveTdeeChart.tsx

Bug fix (2) +298 / -39
nutrition-analytics-repository.tsMake adaptive TDEE evaluate true calendar days and return evidence/range +223/-39

Make adaptive TDEE evaluate true calendar days and return evidence/range

• Expands adaptive TDEE inputs to carry nutrition resolution status and excluded-source context, then densifies sparse rows into a full calendar window (respecting access windows). Replaces confidence/dataPoints with explicit status, unavailableReason, evidence counters, and observed rolling estimate range computed from accepted windows.

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

seed-review-clickhouse.tsSeed deterministic review-user body-weight samples in canonical ClickHouse metric_stream +75/-0

Seed deterministic review-user body-weight samples in canonical ClickHouse metric_stream

• Adds tombstoning of prior seeded body_weight rows (scoped by provider/channel/prefix) and inserts 90 deterministic daily body_weight samples via numbers(90). Ensures unrelated metric_stream rows remain untouched while making adaptive TDEE available in the review fixture.

scripts/seed-review-clickhouse.ts

Tests (10) +481 / -72
nutrition-analytics-story-link.test.tsTest Storybook link for adaptive TDEE unavailable scenario +12/-0

Test Storybook link for adaptive TDEE unavailable scenario

• Adds a unit test asserting the Storybook tRPC link can return an explicit adaptive TDEE unavailable response. Validates the new fields (status, unavailableReason, evidence counts).

packages/mobile/.storybook/nutrition-analytics-story-link.test.ts

nutrition-analytics.test.tsxUpdate mobile screen tests to assert evidence/range rendering +36/-6

Update mobile screen tests to assert evidence/range rendering

• Adds shared evidence fixtures and updates expectations for available vs unavailable responses. Verifies the screen renders the server-authored unavailable reason, fit-window requirements, rolling range, and excluded-day breakdown.

packages/mobile/app/nutrition-analytics.test.tsx

nutrition-analytics-repository.test.tsAdd server unit tests for calendar densification, evidence, and unavailability reasons +271/-34

Add server unit tests for calendar densification, evidence, and unavailability reasons

• Introduces tests for dense calendar-day construction, weight insufficiency reasons, exclusion counters, accepted-window range reporting, and updated model serialization. Updates existing smoothing/TDEE tests to account for nutrition statuses and the new evidence contract.

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

nutrition-analytics-data.integration.test.tsIntegration test: adaptive TDEE returns dense calendar data and reports conflicts +28/-7

Integration test: adaptive TDEE returns dense calendar data and reports conflicts

• Adds a nutrition conflict entry to force a source_conflict day. Updates assertions to expect 90 calendar-day rows, available status, non-null estimateRange, evidence accepted-windows, and explicit inclusion of a source_conflict day with null calories.

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

nutrition-analytics-pmc-power.test.tsUpdate router tests for new adaptive TDEE contract and dense window length +18/-5

Update router tests for new adaptive TDEE contract and dense window length

• Adjusts mocked rows to include nutrition resolution fields and updates expectations to use status/evidence instead of confidence. Ensures adaptiveTdee returns 90 rows for a 90-day query and that accepted windows are reported when eligible.

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

router.integration.test.tsUpdate router integration coverage for adaptive TDEE status/evidence fields +15/-8

Update router integration coverage for adaptive TDEE status/evidence fields

• Adjusts the integration test shape assertions to validate the new contract fields (status, evidence requirements, and per-day nutritionStatus). Removes confidence/dataPoints checks and ensures evidence fields are present.

packages/server/src/routers/router.integration.test.ts

AdaptiveTdeeChart.test.tsxTest web chart rendering of rolling range, evidence, and unavailable reason +63/-2

Test web chart rendering of rolling range, evidence, and unavailable reason

• Expands tests to assert evidence text, exclusions, and observed rolling range for available responses. Adds a dedicated test verifying unavailable responses display the server-authored reason and evidence requirements.

packages/web/src/components/AdaptiveTdeeChart.test.tsx

NutritionAnalyticsPage.test.tsxRemove confidence/dataPoints expectations from web page tests +0/-4

Remove confidence/dataPoints expectations from web page tests

• Updates tests to stop supplying/expecting the removed confidence/dataPoints fields in adaptive TDEE results. Keeps focus on estimate presence and refresh/error behavior.

packages/web/src/pages/NutritionAnalyticsPage.test.tsx

seed-review-clickhouse.integration.test.tsIntegration test: verify idempotent seeding and 90 canonical body-weight rows +22/-6

Integration test: verify idempotent seeding and 90 canonical body-weight rows

• Runs the seed twice to validate idempotency and preservation behavior. Adds a ClickHouse query assertion that exactly 90 non-deleted, seeded body_weight metric_stream rows exist for the review user.

scripts/seed-review-clickhouse.integration.test.ts

seed-review-clickhouse.test.tsUnit test: ensure seed script inserts body_weight via metric_stream without truncation +16/-0

Unit test: ensure seed script inserts body_weight via metric_stream without truncation

• Adds a test asserting the generated statements include an INSERT using numbers(90) for body_weight with the expected external_id prefix. Verifies the statement set never truncates ingest.metric_stream.

scripts/seed-review-clickhouse.test.ts

Documentation (3) +86 / -2
review-fixture-scenarios.mdDocument ClickHouse review seed prerequisites for adaptive TDEE +11/-0

Document ClickHouse review seed prerequisites for adaptive TDEE

• Adds guidance that review now requires running the ClickHouse seed after Postgres seeding. Links to the seed implementation and its preservation integration test, and points to the explicit unavailable-state stories on web and mobile.

docs/review-fixture-scenarios.md

2026-07-29-adaptive-tdee-evidence.mdAdd TDD plan for adaptive TDEE evidence and fixture availability +70/-0

Add TDD plan for adaptive TDEE evidence and fixture availability

• Introduces a detailed plan capturing the root cause (missing canonical weights), desired evidence contract, and staged testing/implementation tasks. Serves as design/intent documentation for the contract and fixture changes.

docs/superpowers/plans/2026-07-29-adaptive-tdee-evidence.md

README.mdDocument review ClickHouse seed inserting deterministic body-weight samples safely +5/-2

Document review ClickHouse seed inserting deterministic body-weight samples safely

• Updates script documentation to clarify that the ClickHouse review seed inserts deterministic body_weight rows into canonical metric_stream. Emphasizes it tombstones only prior seed rows and preserves unrelated sensor data.

scripts/README.md

Other (4) +143 / -15
nutrition-analytics-story-link.tsExtend mobile Storybook tRPC fixtures to include adaptive TDEE evidence/unavailable +43/-4

Extend mobile Storybook tRPC fixtures to include adaptive TDEE evidence/unavailable

• Replaces confidence/dataPoints with status, estimateRange, unavailableReason, and evidence. Adds an "unavailable" scenario and routes adaptiveTdee queries to the correct fixture payload.

packages/mobile/.storybook/nutrition-analytics-story-link.ts

nutrition-analytics.stories.tsxAdd mobile Storybook scenario for TDEE unavailable state +8/-2

Add mobile Storybook scenario for TDEE unavailable state

• Expands scenario typing and wiring so Storybook can request the new "unavailable" adaptive TDEE response. Adds a dedicated TdeeUnavailable story entry.

packages/mobile/app/nutrition-analytics.stories.tsx

AdaptiveTdeeChart.stories.tsxUpdate web adaptive TDEE stories to include evidence and unavailable state +36/-4

Update web adaptive TDEE stories to include evidence and unavailable state

• Replaces confidence/dataPoints with the evidence contract and observed rolling range. Renames the empty story to an explicit Unavailable story that exercises server-authored reason and evidence counts.

packages/web/src/components/AdaptiveTdeeChart.stories.tsx

NutritionAnalyticsPage.stories.tsxAdd web page Storybook scenario for adaptive TDEE unavailable state +56/-5

Add web page Storybook scenario for adaptive TDEE unavailable state

• Extends mock TRPC observable to support an "unavailable" scenario without forcing an error. Adds a TdeeUnavailable page story and updates adaptive TDEE fixture data to the new evidence contract.

packages/web/src/pages/NutritionAnalyticsPage.stories.tsx

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

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

Channel pr-2342
Deep Link dofek://preview/pr-2342
Commit 0fd1b05

To test on device:

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

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

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Storybook previews for 0fd1b056 are ready:

This comment updates automatically on each PR push.

@qodo-code-review

qodo-code-review Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 208 rules

Grey Divider


Action required

1. Timezone window mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
getAdaptiveTdee() builds a dense calendar ending at the user’s timezone-local “today”, but the
underlying Postgres and ClickHouse queries are still anchored to server/DB CURRENT_DATE and
ClickHouse today() (via endDate="now"). Around local midnight (especially for timezones ahead of the
server), the newest local-day rows can be omitted and treated as “missing”, skewing evidence and
potentially flipping status to "unavailable".
Code

packages/server/src/repositories/nutrition-analytics-repository.ts[R1008-1013]

+    const calendar = buildAdaptiveTdeeCalendar(
+      sourceData,
+      days,
+      formatDateYmdInTimeZone(new Date(), this.timezone),
+      this.accessWindow,
+    );
Relevance

●●● Strong

Timezone-boundary/off-by-one issues are repeatedly fixed; likely they’ll align query window anchors
with user-local today.

PR-#1527
PR-#2038

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR introduces a timezone-local calendar anchor, but the repo still queries nutrition rows using
CURRENT_DATE and weight rows using ClickHouse today() via endDate="now", so the data fetch window
and the calendar window can disagree near timezone boundaries.

packages/server/src/repositories/nutrition-analytics-repository.ts[956-975]
packages/server/src/repositories/nutrition-analytics-repository.ts[1007-1015]
packages/server/src/lib/date-window.ts[342-351]
packages/server/src/repositories/body-clickhouse.ts[75-76]
packages/server/src/repositories/body-clickhouse.ts[121-133]
PR-#2314

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Adaptive TDEE uses a timezone-local end date for calendar densification, but still fetches nutrition rows via Postgres CURRENT_DATE and weight rows via ClickHouse today() (endDate="now"), which can drop the user’s current local day around midnight.

## Issue Context
- Calendar uses `formatDateYmdInTimeZone(new Date(), this.timezone)`.
- Postgres uses `currentDateRangePredicate(...)` which is CURRENT_DATE-based.
- ClickHouse uses `endDate="now"` which maps to `today()`.

## Fix Focus Areas
- packages/server/src/repositories/nutrition-analytics-repository.ts[956-1016]
- packages/server/src/lib/date-window.ts[342-351]
- packages/server/src/repositories/body-clickhouse.ts[75-76,121-133]

## Implementation notes
- Compute `const endDate = formatDateYmdInTimeZone(new Date(), this.timezone)` once in `getAdaptiveTdee()`.
- Use `endDate` consistently:
 - Pass `endDate` (not "now") into `fetchBodyWeightRows(...)`.
 - Replace `currentDateRangePredicate(sql`date`, days)` with an endDate-based predicate (e.g. `dateWindowStartPredicate(sql`date`, endDate, days)` and add an explicit upper bound `date <= endDate::date`).
 - Pass the same `endDate` into `buildAdaptiveTdeeCalendar(..., endDate, ...)`.
- Add a regression unit/integration test that sets a timezone ahead of UTC and mocks time near local midnight to ensure the newest local day is included in both fetched rows and the dense calendar.

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



Remediation recommended

2. Hardcoded kcal/day in TDEE ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The mobile UI hardcodes kcal/day in a user-facing string instead of using a shared formatter/i18n
unit label. This can cause inconsistent unit formatting and makes future unit-system or localization
changes harder.
Code

packages/mobile/app/nutrition-analytics.tsx[R195-198]

+                <Text style={styles.cardSubtext}>
+                  Observed rolling range: {formatNutritionNumber(data.estimateRange.minimum)}–
+                  {formatNutritionNumber(data.estimateRange.maximum)} kcal/day
+                </Text>
Relevance

●●● Strong

Team often removes hardcoded user-facing/unit strings in favor of shared constants/formatters to
prevent drift.

PR-#2339
PR-#2275

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 722048 requires user-facing unit strings to come from a shared formatter/i18n layer
rather than being embedded in UI literals. The added mobile string renders kcal/day directly in
the UI (Observed rolling range: ... kcal/day).

Rule 722048: Centralize user-facing unit strings in a shared formatter or i18n layer
packages/mobile/app/nutrition-analytics.tsx[193-199]

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 mobile nutrition analytics UI hardcodes the unit string `kcal/day` in user-facing text, instead of sourcing the unit label from a shared formatter or i18n layer.

## Issue Context
A shared formatter (`@dofek/format/format`) already exists (e.g., `formatCalories(...)` returns a properly formatted value with `kcal`). The new “Observed rolling range” string currently formats numbers via `formatNutritionNumber(...)` and then appends a hardcoded unit.

## Fix Focus Areas
- packages/mobile/app/nutrition-analytics.tsx[193-199]

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


3. Tombstone column-order risk ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new review seed tombstone uses INSERT INTO ingest.metric_stream SELECT ... without specifying
target columns, so it depends on ClickHouse’s physical column order matching the SELECT list. If the
table schema evolves (added/reordered columns), the seed can fail or tombstone with incorrect
positional mapping.
Code

scripts/seed-review-clickhouse.ts[R221-246]

+function buildReviewBodyWeightTombstoneStatement(): string {
+  return `INSERT INTO ingest.metric_stream
+SELECT
+  id,
+  activity_id,
+  user_id,
+  recorded_at,
+  channel,
+  provider_id,
+  external_id,
+  device_id,
+  source_type,
+  scalar,
+  vector,
+  point,
+  metadata,
+  now64(9),
+  1,
+  toInt64(toUnixTimestamp64Nano(now64(9))),
+  generation
+FROM ingest.metric_stream FINAL
+WHERE user_id = toUUID(${reviewUserId})
+  AND provider_id = 'manual_review'
+  AND channel = 'body_weight'
+  AND startsWith(ifNull(external_id, ''), '${reviewBodyWeightPrefix}')
+  AND is_deleted = 0`;
Relevance

●●● Strong

Omitting INSERT column list is a clear reliability footgun; nearby code already uses explicit
columns as safer pattern.

PR-#1110

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tombstone statement omits a column list while the insert statement provides one, proving the
tombstone depends on implicit column ordering and is inconsistent with the safer pattern used right
next to it.

scripts/seed-review-clickhouse.ts[221-247]
scripts/seed-review-clickhouse.ts[249-289]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`buildReviewBodyWeightTombstoneStatement()` performs an INSERT without an explicit column list, making it fragile to ClickHouse schema/order changes.

## Issue Context
The adjacent insert statement already uses an explicit column list; the tombstone should do the same to ensure positional safety and future schema compatibility.

## Fix Focus Areas
- scripts/seed-review-clickhouse.ts[221-290]

## Implementation notes
- Change the tombstone statement to `INSERT INTO ingest.metric_stream (id, activity_id, ... , generation) SELECT ...` using the same ordered column list as `buildReviewBodyWeightInsertStatement()`.
- Consider extracting the shared column list into a constant so both statements cannot drift.
- Keep the WHERE clause unchanged so only prior `review-seed-body-weight-*` rows are tombstoned.

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


4. All-time calendar expands ✓ Resolved 🐞 Bug ➹ Performance
Description
When days is null, buildAdaptiveTdeeCalendar() densifies every calendar day between the earliest
and latest observed date, even if the source data is sparse. For long histories this can produce a
very large dailyData payload and increase CPU in smoothing and rolling-window evaluation.
Code

packages/server/src/repositories/nutrition-analytics-repository.ts[R396-441]

+export function buildAdaptiveTdeeCalendar(
+  sourceDays: AdaptiveTdeeDataPoint[],
+  selectedWindowDays: RangeDays,
+  today: string,
+  accessWindow: AccessWindow,
+): AdaptiveTdeeDataPoint[] {
+  const sourceByDate = new Map(sourceDays.map((day) => [day.date, day]));
+  let startDate: string | undefined;
+  let endDate: string | undefined;
+
+  if (selectedWindowDays == null) {
+    const observedDates = [...sourceByDate.keys()].sort();
+    startDate = observedDates[0];
+    endDate = observedDates.at(-1);
+  } else {
+    startDate = shiftDateYmd(today, -(selectedWindowDays - 1));
+    endDate = today;
+  }
+
+  if (accessWindow.kind === "limited") {
+    startDate =
+      startDate == null ? accessWindow.startDate : laterDate(startDate, accessWindow.startDate);
+    const accessEnd = shiftDateYmd(accessWindow.endDateExclusive, -1);
+    endDate = endDate == null ? accessEnd : earlierDate(endDate, accessEnd);
+  }
+
+  if (startDate == null || endDate == null || startDate > endDate) return [];
+
+  const numberOfDays =
+    Math.floor(
+      (parseDateYmd(endDate).getTime() - parseDateYmd(startDate).getTime()) / MILLISECONDS_PER_DAY,
+    ) + 1;
+
+  return Array.from({ length: numberOfDays }, (_, index) => {
+    const date = shiftDateYmd(startDate, index);
+    return (
+      sourceByDate.get(date) ?? {
+        date,
+        caloriesIn: null,
+        nutritionStatus: "missing",
+        lowerPrioritySourcesExcluded: false,
+        weightKg: null,
+      }
+    );
+  });
+}
Relevance

●● Moderate

Potentially large all-time expansion; team has flagged unbounded payload/memory risks, but precedent
is adjacent, not exact.

PR-#1558

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The all-time branch explicitly expands the calendar from earliest to latest observed date and
allocates one row per day, making runtime and payload size proportional to history span rather than
observed rows.

packages/server/src/repositories/nutrition-analytics-repository.ts[396-441]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
For `days=null` (all-time), the adaptive TDEE calendar densifies the entire observed span, potentially generating thousands of synthetic missing days and returning them to clients.

## Issue Context
`buildAdaptiveTdeeCalendar()` chooses `startDate = earliestObserved` and `endDate = latestObserved` when `selectedWindowDays == null`, then allocates `Array.from({ length: numberOfDays })`.

## Fix Focus Areas
- packages/server/src/repositories/nutrition-analytics-repository.ts[396-441]

## Implementation notes
Pick one mitigation:
- Disallow `days=null` for adaptiveTdee (enforce finite days in router schema), OR
- Cap densification to a maximum span (e.g., last 365 days), OR
- For `days=null`, densify only a bounded tail needed for estimation/evidence (e.g., last N days where N >= fitWindowDays + warmup).
Also consider adding a unit test that passes `selectedWindowDays=null` with multi-year observed dates and asserts the result is capped.

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


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread packages/mobile/app/nutrition-analytics.tsx
Comment thread packages/server/src/repositories/nutrition-analytics-repository.ts
Comment thread scripts/seed-review-clickhouse.ts
Comment thread packages/server/src/repositories/nutrition-analytics-repository.ts
Use one timezone-local date boundary, bound all-time evaluation, name metric-stream seed columns, and cover estimator thresholds for mutation testing.

Refs #2137
@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 ff0e1eb into main Jul 30, 2026
105 checks passed
@Asherlc
Asherlc deleted the Asherlc/issue-2137-tdee-evidence branch July 30, 2026 07:26
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.

[NUTA-03] TDEE empty after retries despite calorie/weight fixtures; no uncertainty

2 participants