Skip to content

feat(recovery): expose baseline-relative metrics - #2287

Merged
Asherlc merged 9 commits into
mainfrom
issue-2248-baseline-relative-metrics
Jul 29, 2026
Merged

Asherlc merged 9 commits into
mainfrom
issue-2248-baseline-relative-metrics

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • add baseline-relative HRV, resting heart rate, respiratory rate, and sleep-efficiency context to the existing incremental ClickHouse recovery read models
  • expose preceding-30-day mean/SD/z-score, sample count/coverage, and latest-7-day versus preceding-28-day deltas through the server contract and MCP health trends
  • use the canonical ClickHouse z-scores for readiness, stress, and health status
  • render the same baseline context on web and mobile recovery surfaces
  • make the canonical web unit-test setup resolve within isolated worktrees

Semantics

  • baseline: preceding 30 calendar days, excluding the current day
  • comparison: latest 7 calendar days versus the preceding 28 calendar days
  • mean requires one sample; SD/z-score require two samples and nonzero SD
  • no hidden coverage threshold; sample count and calendar coverage are exposed
  • scope is the recovery slice only; body weight and training load remain unchanged

Validation

  • executable ClickHouse recovery read-model integration: 2 passed
  • pnpm test:changed: 39 files, 732 tests passed
  • focused server recovery suite: 344 tests passed
  • focused web suite: 28 tests passed
  • focused mobile recovery suite: 16 tests passed
  • pnpm typecheck
  • pnpm lint:sandbox
  • git diff --check origin/main...HEAD

Local Docker Desktop's daemon API stopped responding after the focused ClickHouse integration passed, so the final analytics SQL/dbt rerun is delegated to the required CI check rather than bypassed.

Fixes #2248

Summary by Sourcery

Add canonical baseline-relative recovery metrics to analytics and server contracts and surface them consistently across tRPC, MCP, web, and mobile recovery experiences.

New Features:

  • Expose baseline-relative HRV, resting heart rate, respiratory rate, and sleep efficiency metrics with 30-day baselines, 7-versus-28-day comparisons, and z-scores via a shared server contract.
  • Provide recovery baseline context in MCP health trends and daily metrics/recovery APIs for use by clients.
  • Render baseline-relative recovery context in web health status bars and mobile recovery cards, including respiratory rate and sleep efficiency.

Enhancements:

  • Refactor recovery readiness and stress scoring to consume canonical ClickHouse z-scores instead of recomputing baselines in application code.
  • Introduce a RecoveryBaselineRepository and baseline-relative metric schema to centralize recovery baseline queries and mapping.
  • Update ClickHouse read models and migrations to persist baseline sample counts, coverage, and comparison stats in daily recovery serving tables.
  • Adjust tests and stories across server, web, and mobile to validate the new baseline context and z-score driven behavior.

Build:

  • Update Vitest unit test setup for web to use a shared test-setup file enabling DOM matchers across isolated worktrees.

Deployment:

  • Register a new ClickHouse migration to add baseline context columns to daily recovery serving tables and wire it into the migration registry.

Documentation:

  • Document baseline-relative recovery semantics and the updated MCP get_health_trends behavior, including window definitions and z-score availability rules.

Tests:

  • Extend ClickHouse integration, analytics SQL policy, and server/router tests to cover baseline-relative recovery metrics, z-scores, and access-window constraints.
  • Add UI tests for web health status bars and mobile recovery screens to ensure baseline context is rendered and formatted correctly.

Summary by cubic

Adds baseline‑relative recovery metrics (HRV, resting HR, respiratory rate, sleep efficiency) with 30‑day baselines, z‑scores, and 7‑vs‑28‑day comparisons across API, MCP, web, and mobile. Unifies readiness and stress scoring on canonical z‑scores and adds @dofek/format/baseline-context for consistent display. Implements #2248.

  • New Features

    • ClickHouse: baseline stats/z‑scores for HRV, resting HR, respiratory rate, sleep efficiency; include sample count, coverage, 7‑day and prior‑28‑day means.
    • Server: new baseline-relative-metrics contract and RecoveryBaselineRepository; daily-metrics, recovery, and mobile dashboard return a baselineRelative array; export contract/types; latest baseline query bounded to the selected/access window.
    • MCP: get_health_trends now includes baseline‑relative recovery context.
    • Web/Mobile: HealthStatusBar and mobile recovery tab render baseline context; add respiratory_rate and sleep_efficiency; use @dofek/format/baseline-context for formatting.
    • Tests/Docs: updated MCP docs and fixtures; added format tests covering baseline context states; web Vitest setup resolves via ./packages/web/test-setup.ts.
  • Migration

    • Run ClickHouse migration 0062_daily_recovery_baseline_context (additive columns; incremental models remain compatible).
    • No breaking API changes; clients receive a new baselineRelative array.

Written for commit 81acbe7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added baseline-relative recovery insights, including 30-day averages, variability, sample coverage, and recent-versus-prior-period comparisons.
    • Recovery views now include respiratory rate and sleep efficiency alongside HRV and resting heart rate.
    • Web and mobile recovery cards display clearer baseline context, trends, and comparison directions.
    • Health trend results now include baseline-relative recovery details for supported metrics.
  • Bug Fixes

    • Improved recovery and stress scoring consistency by using standardized baseline-derived scores across supported experiences.
  • Documentation

    • Expanded health trends documentation to describe baseline-relative recovery information and availability.

Asherlc added 4 commits July 29, 2026 07:55
Additive ClickHouse columns keep the incremental recovery models compatible while exposing prior-calendar-day statistics.

Refs #2248
Serve canonical recovery baselines through API, MCP, web, and mobile. Reuse the same z-scores for readiness, stress, and health status so every consumer compares against one baseline definition.
Load the existing web test setup through a repository-relative path. Bare setup resolution escaped Conductor worktrees into the parent workspace and prevented canonical Vitest runs.
Copilot AI review requested due to automatic review settings July 29, 2026 14:59
@cursor

cursor Bot commented Jul 29, 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.

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

@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, your pull request is larger than the review limit of 150000 diff characters

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 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Recovery baseline statistics are computed in ClickHouse and exposed through server contracts, tRPC, MCP, web, and mobile surfaces. The change adds baseline means, standard deviations, z-scores, coverage, 7-day versus prior-28-day comparisons, and related rendering and test coverage.

Changes

Baseline computation and storage

Layer / File(s) Summary
Calendar-window recovery statistics
analytics/models/read_models/daily_recovery*.sql, analytics/models/read_models/read_model_microbatch.sql.test.ts, packages/server/src/services/daily-recovery-read-model.integration.test.ts
Recovery models now calculate preceding 30-day calendar baselines, sample counts, coverage, 7-day means, prior-28-day means, and z-scores for four recovery metrics.
ClickHouse serving schema
src/db/clickhouse-migrations/*, packages/server/src/routers/clickhouse-integration-test-models.ts
Migration 0062 and test schemas add the new baseline and z-score columns to both recovery serving tables.

Server contract and consumers

Layer / File(s) Summary
Baseline metric contract and repository
packages/server/src/contracts/*, packages/server/src/repositories/recovery-baseline-repository.*, packages/server/package.json
A validated baseline-relative metric contract, builder, and repository map ClickHouse recovery rows into ordered metric payloads.
API, MCP, readiness, and stress integration
packages/server/src/routers/*, packages/server/src/mcp/*, packages/server/src/services/*, packages/server/src/repositories/stress-repository.*
Server consumers use canonical baseline rows and precomputed z-scores for trends, health status, readiness, stress, and MCP responses.

Client rendering

Layer / File(s) Summary
Web and mobile baseline context
packages/web/src/components/*, packages/web/src/pages/Dashboard.tsx, packages/mobile/app/(tabs)/recovery.*, packages/mobile/components/HealthStatusCards.tsx
Web and mobile render baseline mean/standard deviation, z-score direction, comparison delta, coverage, and respiratory-rate and sleep-efficiency metrics.

Documentation and validation

Layer / File(s) Summary
Contracts, fixtures, and documentation
docs/mcp.md, docs/superpowers/plans/*, packages/server/src/contracts/*, packages/web/src/components/*, packages/mobile/app/(tabs)/*
Documentation, stories, fixtures, and tests describe and validate the baseline-relative response shape and rendering behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TrendsAPI
  participant RecoveryBaselineRepository
  participant ClickHouse
  participant HealthStatus
  Client->>TrendsAPI: request recovery trends
  TrendsAPI->>RecoveryBaselineRepository: load baseline rows
  RecoveryBaselineRepository->>ClickHouse: query daily_recovery
  ClickHouse-->>RecoveryBaselineRepository: baseline statistics and z-scores
  RecoveryBaselineRepository-->>TrendsAPI: baselineRelative metrics
  TrendsAPI->>HealthStatus: build health statuses
  TrendsAPI-->>Client: trends, baselineRelative, healthStatus
Loading

Assessment against linked issues

Objective Addressed Explanation
Expose rolling baseline mean, standard deviation, z-score, comparison delta, and coverage for recovery metrics [#2248]
Use one canonical ClickHouse/server computation instead of client-side or Postgres derivation [#2248]
Expose baseline-relative values through tRPC and MCP [#2248]
Render deviation alongside standalone web and mobile metrics [#2248]

Out-of-scope changes

Code Change Explanation
Unit Vitest setup changed to packages/web/test-setup.ts (vitest.config.ts:70) No linked objective describes changing global test setup; this is unrelated configuration behavior.

Possibly related PRs

Suggested labels: area/server, area/web, area/mobile, area/db, type/feature

Suggested reviewers: copilot, cubic-dev-ai

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning Relevant title, but it misses the required area prefix format (for example, [server]) from the title rules. Rename to an imperative title with the proper area prefix, e.g. [server] expose baseline-relative metrics.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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 29, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR standardizes recovery metric baselines by pushing canonical baseline-relative calculations into ClickHouse read models, exposing them via new server contracts and repositories, and wiring them through tRPC/MCP/web/mobile so HRV, resting HR, respiratory rate, and sleep efficiency share a consistent 30‑day baseline + 7‑vs‑28‑day comparison context and z-scores for readiness/stress/health surfaces.

File-Level Changes

Change Details Files
Move recovery readiness and stress scoring to canonical ClickHouse z-scores and remove ad-hoc TypeScript baseline math.
  • Replace mobile recovery readiness and stress computation to use DailyRecoveryBaseline rows with precomputed z-scores and values instead of manual mean/SD calculations.
  • Update recoveryRouter.readinessScore and StressRepository to read hrv_z_score/resting_hr_z_score/respiratory_rate_z_score from analytics.daily_recovery and derive deviations by rounding z-scores.
  • Adjust recovery and stress tests/mocks to supply z-score fields, remove direct mean/SD-dependent expectations, and assert new query shapes and fallbacks when z-scores are missing.
packages/server/src/services/mobile-recovery-tab.ts
packages/server/src/routers/recovery.ts
packages/server/src/repositories/stress-repository.ts
packages/server/src/services/mobile-recovery-tab.test.ts
packages/server/src/routers/recovery.test.ts
packages/server/src/repositories/stress-repository.test.ts
packages/server/src/routers/stress.test.ts
packages/server/src/routers/strength-stress.test.ts
Extend ClickHouse daily recovery read models and schema with baseline-relative statistics and z-scores for the four recovery metrics.
  • Change daily_recovery_inputs.sql to use RANGE windows over toUInt32(date) for prior-30-day baselines and 7-/28-day means, add sample count and coverage columns, and normalize SD visibility to require at least two baseline samples.
  • Extend daily_recovery.sql to carry baseline context through, compute z-scores for efficiency, and feed canonical z-scores into sigmoid-based readiness scoring.
  • Add ClickHouse migration 0062_daily_recovery_baseline_context and update migration registry and ClickHouse integration test schemas to include new baseline and z-score columns.
  • Update dbt read-model microbatch SQL tests and server ClickHouse integration tests to assert the new window frame semantics, columns, and baseline context behavior.
analytics/models/read_models/daily_recovery_inputs.sql
analytics/models/read_models/daily_recovery.sql
analytics/models/read_models/read_model_microbatch.sql.test.ts
packages/server/src/services/daily-recovery-read-model.integration.test.ts
packages/server/src/routers/clickhouse-integration-test-models.ts
src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.ts
src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.test.ts
src/db/clickhouse-migrations/registry.ts
src/db/clickhouse-migrations/registry.test.ts
Introduce a shared baseline-relative metric contract and a RecoveryBaselineRepository to serve baseline context across tRPC and MCP.
  • Define baseline-relative metric schemas and builder utilities capturing metric key, value, 30-day baseline stats, 7-vs-28 comparison, and direction, and export them from the server package.
  • Implement RecoveryBaselineRepository to query analytics.daily_recovery FINAL for a date range, map rows into BaselineRelativeMetric arrays per day, and expose helper latestRecoveryBaselineMetrics to pick the latest non-null metric by type.
  • Add repository tests to validate mapping, query shape, and access-window constraints, and update various server tests to canonicalize recovery rows with z-scores when mocking sensorStore/query behavior.
  • Wire baseline-relative metrics into daily-metrics router (trends), health-status service, mobile-dashboard recovery output, MCP get_health_trends tool, and update associated tests to expect baselineRelative arrays and health status derived from recovery baselines rather than selected-range aggregates.
packages/server/src/contracts/baseline-relative-metrics.ts
packages/server/src/contracts/baseline-relative-metrics.test.ts
packages/server/src/repositories/recovery-baseline-repository.ts
packages/server/src/repositories/recovery-baseline-repository.test.ts
packages/server/src/routers/daily-metrics.ts
packages/server/src/services/health-status.ts
packages/server/src/services/health-status.test.ts
packages/server/src/mcp/tools.ts
packages/server/src/mcp/route.test.ts
packages/server/src/routers/daily-metrics.test.ts
packages/server/src/routers/mobile-dashboard.test.ts
packages/server/src/contracts/mobile-dashboard-contracts.ts
packages/server/src/contracts/mobile-dashboard-contracts.test.ts
packages/server/src/types.ts
docs/mcp.md
docs/superpowers/plans/2026-07-29-baseline-relative-recovery-metrics.md
Render baseline-relative recovery context on mobile recovery screens and web health-status bars, including new respiratory-rate and sleep-efficiency cards.
  • Update mobile RecoveryScreen to consume baselineRelative from the server, derive HRV, resting HR, respiratory rate, and sleep efficiency context, and format baseline mean/SD, z-score, 7-vs-28 deltas, and coverage in metric subtitles; add respiratory rate and sleep efficiency cards.
  • Adjust mobile recovery stories and tests to include baselineRelative fixtures for the four metrics and assert correct text rendering and coverage display.
  • Extend web HealthStatusBar to accept baselineRelative, format baseline mean/SD, z-score, comparison delta, and coverage per metric, and show the new context below status labels; update Dashboard to pass baselineRelative and units for respiratory rate/resting HR/sleep efficiency.
  • Update web tests and stories for HealthStatusBar, Dashboard, and TimeRangeSelector consumers to include baselineRelative arrays and verify the new context rendering while preserving existing status/units behavior.
packages/mobile/app/(tabs)/recovery.tsx
packages/mobile/app/(tabs)/recovery.test.tsx
packages/mobile/app/(tabs)/recovery.stories.tsx
packages/mobile/components/HealthStatusCards.tsx
packages/web/src/components/HealthStatusBar.tsx
packages/web/src/components/HealthStatusBar.test.tsx
packages/web/src/components/HealthStatusBar.stories.tsx
packages/web/src/pages/Dashboard.tsx
packages/web/src/pages/Dashboard.test.tsx
packages/web/src/components/TimeRangeSelector.consumers.test.tsx
packages/web/src/lib/healthStatus.ts
packages/web/test-setup.ts
Adjust tests, mocks, and utility schemas across routers and services to align with new baseline-relative fields and ensure isolated unit-test setup for web.
  • Update various router tests (recovery-settings-sleep-need-sport-settings, dailyMetricsRouter, mobileDashboard recovery, stressRouter, strength-stressRouter) to inject baselineRelative metrics and/or canonical z-scores into mock recovery rows and assert updated query SQL and behavior.
  • Modify health metric key enums in contracts and mobile components to include respiratory_rate and sleep_efficiency, and adapt MCP healthMetricSchema/columns/recoveryMetricKeys to handle recovery and non-recovery metrics consistently.
  • Change server vitest unit config to use a web-local setup file instead of @testing-library/jest-dom directly, improving isolation for worktrees.
  • Ensure resting-heart-rate-only sensorStore mocks now account for additional recovery-baseline queries and adjust expectations on query call counts and parameters accordingly.
packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
packages/server/src/routers/daily-metrics.test.ts
packages/server/src/routers/mobile-dashboard.test.ts
packages/server/src/routers/stress.test.ts
packages/server/src/routers/strength-stress.test.ts
packages/server/src/mcp/tools.ts
packages/server/src/mcp/route.test.ts
packages/server/src/contracts/mobile-dashboard-contracts.ts
packages/mobile/components/HealthStatusCards.tsx
packages/server/src/types.ts
packages/web/src/lib/healthStatus.ts
packages/web/src/components/TimeRangeSelector.consumers.test.tsx
vitest.config.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#2248 Implement a canonical rolling-baseline computation for recovery metrics (HRV, resting heart rate, respiratory rate, sleep efficiency) in the ClickHouse/dbt read models, including preceding-30-day mean and standard deviation, z-scores, sample count and coverage, and 7-day vs preceding-28-day comparison, with explicit gap handling, and reuse these values for readiness and stress calculations.
#2248 Expose the baseline-relative recovery metrics through the server APIs (tRPC routes for daily metrics and recovery, mobile dashboard contracts) and the MCP get_health_trends tool, including schema/contract updates and documentation, so clients receive baseline-relative context alongside raw metric aggregates.
#2248 Update web and mobile clients to render server-computed baseline-relative deviation and trend context (z-scores, 7d vs 28d deltas, coverage) alongside raw recovery metrics, removing client-side baseline calculations, and add tests for statistics, sparse/boundary cases, and end-to-end query paths.

Possibly linked issues


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

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

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

Channel pr-2287
Deep Link dofek://preview/pr-2287
Commit a97cf71

To test on device:

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

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Expose baseline-relative recovery metrics via ClickHouse read models and shared contract

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

Grey Divider

AI Description

• Extend ClickHouse daily recovery read models with 30d baselines and 7v28 comparisons.
• Add a shared baseline-relative metric contract and serve it via tRPC and MCP.
• Render baseline context on web and mobile recovery surfaces using server-owned z-scores.
Diagram

graph TD
  A["ClickHouse read models"] --> B[("analytics.daily_recovery")]
  B --> C["RecoveryBaselineRepo"] --> D["Baseline metric contract"]
  D --> E["tRPC routers"] --> F["Web Dashboard"]
  D --> G["Mobile recovery tab"]
  D --> H["MCP get_health_trends"]
  subgraph Legend
    direction LR
    _db[("Database")] ~~~ _svc(["Service/Repo"]) ~~~ _api["API/Contract"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Compute baselines at request-time (server SQL/TS)
  • ➕ Avoids ClickHouse schema and dbt model changes
  • ➕ Faster iteration on semantics without backfilling/migrations
  • ➖ Reintroduces known slow-path rolling-window work
  • ➖ Risk of inconsistent semantics across API/MCP/clients
  • ➖ Harder to expose sample count/coverage consistently
2. Expose only z-scores (omit mean/SD/count/coverage)
  • ➕ Smaller contract surface
  • ➕ Less UI formatting and less schema churn
  • ➖ Less transparency for sparse data
  • ➖ Cannot explain directionality/coverage to users
  • ➖ Limits future UI/insight features requiring means and deltas
3. Single ‘recovery baseline context’ blob per day
  • ➕ Fewer contract objects and matching logic
  • ➕ Easier to version as one structure
  • ➖ Harder to consume per-metric UI components
  • ➖ More fragile to partial metric availability
  • ➖ Less reusable for mixed metric lists (e.g., MCP metrics param)

Recommendation: The PR’s approach (precompute in ClickHouse + map through one server-owned contract) is the best fit given prior performance evidence and the need for one canonical baseline definition across readiness/stress/health-status and all clients. The main review focus should be validating the baseline window semantics (exclude current day; calendar-day ranges), nullability rules (mean vs SD/z-score), and ensuring z-score sign conventions remain consistent across consumers.

Files changed (47) +2205 / -476

Enhancement (13) +823 / -65
daily_recovery.sqlPersist baseline context and canonical z-scores in daily_recovery +52/-4

Persist baseline context and canonical z-scores in daily_recovery

• Adds baseline sample-count/coverage and 7d-vs-28d mean fields for HRV/RHR/RR plus sleep efficiency baseline stats. Introduces efficiency z-score and switches RHR/RR z-score polarity to a canonical definition, adjusting sigmoid inputs accordingly.

analytics/models/read_models/daily_recovery.sql

daily_recovery_inputs.sqlCompute prior-calendar-day 30d baselines and 7v28 comparisons +179/-10

Compute prior-calendar-day 30d baselines and 7v28 comparisons

• Reworks window frames to use calendar-day RANGE windows that exclude the current day for 30d baselines. Adds baseline sample counts, derived coverage, 7-day mean, and previous-28-day mean for recovery metrics, with SD gated on >=2 samples.

analytics/models/read_models/daily_recovery_inputs.sql

recovery.tsxRender server-provided baseline context on recovery cards +78/-10

Render server-provided baseline context on recovery cards

• Switches HRV/RHR cards to use baselineRelative values and subtitles (mean/SD/z-score/delta/coverage). Adds respiratory rate and sleep efficiency metric cards driven by baselineRelative metrics.

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

HealthStatusCards.tsxExpand mobile health metric key union for recovery metrics +2/-0

Expand mobile health metric key union for recovery metrics

• Adds respiratory rate and sleep efficiency keys to the local HealthMetricKey type to align with new recovery health status items.

packages/mobile/components/HealthStatusCards.tsx

baseline-relative-metrics.tsIntroduce baseline-relative metric schema and builder +92/-0

Introduce baseline-relative metric schema and builder

• Defines Zod schemas for supported recovery metrics and comparison direction, plus a builder that computes 7v28 delta and direction while preserving server-owned baseline fields.

packages/server/src/contracts/baseline-relative-metrics.ts

mobile-dashboard-contracts.tsAdd baselineRelative to mobile recovery tab contract +4/-0

Add baselineRelative to mobile recovery tab contract

• Extends health metric keys to include respiratory rate and sleep efficiency and adds baselineRelative array to the mobile recovery tab output schema.

packages/server/src/contracts/mobile-dashboard-contracts.ts

tools.tsAttach baseline-relative recovery context to MCP health trends +97/-31

Attach baseline-relative recovery context to MCP health trends

• Fetches recovery baseline rows alongside daily metrics and enriches aggregates with baseline_relative for HRV/RHR/RR/sleep efficiency. Updates metric registry to support sleep_efficiency which is sourced from recovery rows rather than daily metrics columns.

packages/server/src/mcp/tools.ts

recovery-baseline-repository.tsAdd repository to query and map baseline-relative recovery metrics +219/-0

Add repository to query and map baseline-relative recovery metrics

• Introduces a typed SQL query over analytics.daily_recovery and maps each row into BaselineRelativeMetric objects (HRV, RHR, RR, sleep efficiency). Provides helper to select latest non-null metrics across a range.

packages/server/src/repositories/recovery-baseline-repository.ts

daily-metrics.tsExpose baselineRelative in daily metrics trends response +40/-9

Expose baselineRelative in daily metrics trends response

• Fetches recovery baseline rows in parallel with trends, computes latest baselineRelative metrics, and returns them alongside healthStatus built from canonical baselines.

packages/server/src/routers/daily-metrics.ts

types.tsRe-export BaselineRelativeMetric type for clients +1/-0

Re-export BaselineRelativeMetric type for clients

• Adds BaselineRelativeMetric to the server types barrel so web/mobile can type baselineRelative props.

packages/server/src/types.ts

HealthStatusBar.tsxRender baseline-relative context under health status metrics +49/-0

Render baseline-relative context under health status metrics

• Adds optional baselineRelative prop and formats baseline mean/SD, z-score position, 7v28 delta, and sample coverage into a compact context line per metric.

packages/web/src/components/HealthStatusBar.tsx

healthStatus.tsExtend health status metric schema with recovery keys +2/-0

Extend health status metric schema with recovery keys

• Adds respiratory_rate and sleep_efficiency to the healthStatusMetricSchema enum.

packages/web/src/lib/healthStatus.ts

Dashboard.tsxPass baselineRelative and units to HealthStatusBar +8/-1

Pass baselineRelative and units to HealthStatusBar

• Extends trends Zod schema to include baselineRelative and wires trendData.baselineRelative into HealthStatusBar, adding units for RR and sleep efficiency.

packages/web/src/pages/Dashboard.tsx

Refactor (4) +100 / -282
stress-repository.tsUse ClickHouse z-scores for stress deviations +6/-36

Use ClickHouse z-scores for stress deviations

• Simplifies stress deviation computation to use precomputed hrv_z_score and resting_hr_z_score from analytics.daily_recovery rather than recomputing from 60d baselines.

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

recovery.tsConsume canonical ClickHouse z-scores for readiness components +16/-57

Consume canonical ClickHouse z-scores for readiness components

• Changes readinessScore query to select z-score columns and updates component scoring to use those z-scores (with inversion where lower-is-better). Removes application-side z-score recomputation from baseline mean/sd inputs.

packages/server/src/routers/recovery.ts

health-status.tsBuild recovery health status from baselineRelative metrics +25/-17

Build recovery health status from baselineRelative metrics

• Adds helpers to convert BaselineRelativeMetric into HealthStatusMetric and updates daily-metrics health status generation to prefer canonical baselineRelative recovery metrics.

packages/server/src/services/health-status.ts

mobile-recovery-tab.tsCentralize mobile recovery tab baselines via RecoveryBaselineRepository +53/-172

Centralize mobile recovery tab baselines via RecoveryBaselineRepository

• Replaces request-time baseline calculations with RecoveryBaselineRepository queries, uses baselineRelative z-scores for readiness/stress computations, and returns baselineRelative in the mobile recovery tab result.

packages/server/src/services/mobile-recovery-tab.ts

Tests (24) +1132 / -127
read_model_microbatch.sql.test.tsAssert SQL policy for baseline windows and new columns +28/-0

Assert SQL policy for baseline windows and new columns

• Extends read-model tests to verify calendar-day RANGE frames (30d excluding current day; 7d; previous 28d) and presence of new baseline context + z-score columns.

analytics/models/read_models/read_model_microbatch.sql.test.ts

recovery.stories.tsxAdd baselineRelative fixtures for recovery stories +86/-0

Add baselineRelative fixtures for recovery stories

• Updates mobile recovery stories to include baselineRelative metric objects for HRV, resting heart rate, respiratory rate, and sleep efficiency.

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

recovery.test.tsxTest rendering of baseline-relative context and new recovery cards +65/-2

Test rendering of baseline-relative context and new recovery cards

• Adds fixtures and assertions for canonical baseline context strings, and verifies respiratory rate and sleep efficiency cards render when baselineRelative data is present.

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

baseline-relative-metrics.test.tsAdd contract/builder unit tests +92/-0

Add contract/builder unit tests

• Covers delta/direction behavior, sparse baseline behavior (mean available; SD/z-score null), and schema validity for built metrics.

packages/server/src/contracts/baseline-relative-metrics.test.ts

mobile-dashboard-contracts.test.tsUpdate mobile dashboard contract fixtures for baselineRelative +23/-0

Update mobile dashboard contract fixtures for baselineRelative

• Extends the recovery fixture to include a baselineRelative array to satisfy the updated schema.

packages/server/src/contracts/mobile-dashboard-contracts.test.ts

route.test.tsValidate MCP health trends include baseline_relative context +95/-0

Validate MCP health trends include baseline_relative context

• Adds coverage ensuring get_health_trends returns baseline_relative for recovery metrics (including sleep efficiency) with server-computed baseline fields.

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

recovery-baseline-repository.test.tsAdd RecoveryBaselineRepository tests +119/-0

Add RecoveryBaselineRepository tests

• Verifies mapping from a compact daily_recovery row to four baselineRelative metrics and ensures access-window constraints and inclusive date range filtering in queries.

packages/server/src/repositories/recovery-baseline-repository.test.ts

stress-repository.test.tsUpdate stress repository tests for canonical z-scores +28/-1

Update stress repository tests for canonical z-scores

• Adjusts mocks to synthesize z-scores and asserts queries select hrv_z_score/resting_hr_z_score instead of 60d mean/sd inputs.

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

clickhouse-integration-test-models.tsExtend ClickHouse test schemas with baseline context columns +40/-0

Extend ClickHouse test schemas with baseline context columns

• Adds the new baseline context and z-score columns to integration-test table definitions for daily_recovery_inputs and daily_recovery.

packages/server/src/routers/clickhouse-integration-test-models.ts

daily-metrics.test.tsTest baselineRelative returned from dailyMetrics.trends +83/-13

Test baselineRelative returned from dailyMetrics.trends

• Updates expectations to include baselineRelative and adds a test that validates baselineRelative is built from canonical recovery rows.

packages/server/src/routers/daily-metrics.test.ts

mobile-dashboard.test.tsUpdate mobile dashboard recovery tests for new recovery columns +24/-4

Update mobile dashboard recovery tests for new recovery columns

• Expands mocked recovery rows with baseline context fields and adjusts expected recovery payload to include baselineRelative.

packages/server/src/routers/mobile-dashboard.test.ts

recovery-settings-sleep-need-sport-settings.test.tsAlign recovery settings tests with new z-score fields +1/-0

Align recovery settings tests with new z-score fields

• Updates fixture rows to include respiratory_rate_z_score to match new serving model shape.

packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts

recovery.test.tsAdjust recovery router tests for z-score based readiness inputs +51/-2

Adjust recovery router tests for z-score based readiness inputs

• Updates sensor-store mocking to canonicalize z-score fields for daily_recovery queries and asserts readinessScore queries now select z-score columns rather than baseline mean/sd fields.

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

strength-stress.test.tsUpdate strength stress tests to provide z-score fields +21/-1

Update strength stress tests to provide z-score fields

• Adjusts mocked recovery rows to include computed z-score fields so router logic can consume canonical z-scores.

packages/server/src/routers/strength-stress.test.ts

stress.test.tsUpdate stress router tests to expect z-score selection +23/-1

Update stress router tests to expect z-score selection

• Synthesizes z-score fields in test rows and asserts SQL includes hrv_z_score/resting_hr_z_score.

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

daily-recovery-read-model.integration.test.tsAdd ClickHouse integration coverage for baseline context semantics +155/-4

Add ClickHouse integration coverage for baseline context semantics

• Adds an integration test verifying prior-day exclusion, sparse sample count/coverage visibility, 7v28 means, and z-score/SD behavior for HRV and sleep efficiency. Updates materialization helpers and table schemas to include new columns.

packages/server/src/services/daily-recovery-read-model.integration.test.ts

health-status.test.tsTest health status uses canonical baselines +45/-0

Test health status uses canonical baselines

• Adds a test ensuring buildDailyMetricHealthStatuses uses baselineRelative inputs rather than selected-range aggregates for recovery metrics.

packages/server/src/services/health-status.test.ts

mobile-recovery-tab.test.tsUpdate mobile recovery tab tests to use RecoveryBaselineRepository output +62/-97

Update mobile recovery tab tests to use RecoveryBaselineRepository output

• Migrates tests away from ad-hoc recovery row computations, asserts baselineRelative is used to build health status, and updates stress/readiness expectations to use canonical z-scores.

packages/server/src/services/mobile-recovery-tab.test.ts

HealthStatusBar.stories.tsxAdd story example with baselineRelative context +23/-0

Add story example with baselineRelative context

• Extends the HealthStatusBar story args to include a baselineRelative metric example for HRV.

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

HealthStatusBar.test.tsxTest baseline context rendering in HealthStatusBar +37/-0

Test baseline context rendering in HealthStatusBar

• Adds a test asserting the baseline context line renders mean/SD, z-score deviation, 7v28 delta, and coverage text from server data.

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

TimeRangeSelector.consumers.test.tsxUpdate TRPC mocks for baselineRelative field +2/-0

Update TRPC mocks for baselineRelative field

• Updates mocked trends payloads to include baselineRelative to satisfy new schema expectations.

packages/web/src/components/TimeRangeSelector.consumers.test.tsx

Dashboard.test.tsxUpdate dashboard tests for baselineRelative in trends data +3/-0

Update dashboard tests for baselineRelative in trends data

• Extends dashboard test fixtures to include baselineRelative arrays so parsing and rendering align with the new trends schema.

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

0062_daily_recovery_baseline_context.test.tsAdd migration unit test for baseline context columns +23/-0

Add migration unit test for baseline context columns

• Verifies the new migration includes baseline sample/coverage, 7/28 means, efficiency stats, and z-score columns for daily_recovery.

src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.test.ts

registry.test.tsUpdate migration registry ordering expectations +3/-2

Update migration registry ordering expectations

• Adjusts registry test assertions to account for the new 0062 migration being appended.

src/db/clickhouse-migrations/registry.test.ts

Documentation (2) +91 / -1
mcp.mdDocument baseline-relative recovery context in get_health_trends +10/-1

Document baseline-relative recovery context in get_health_trends

• Updates tool description and documents semantics for baseline_relative fields (30d baseline, 7v28 comparison, sample count/coverage, availability rules).

docs/mcp.md

2026-07-29-baseline-relative-recovery-metrics.mdAdd TDD plan for baseline-relative recovery metrics +81/-0

Add TDD plan for baseline-relative recovery metrics

• Introduces a detailed plan covering semantics, performance rationale, test strategy, and implementation tasks for exposing baseline-relative recovery context across surfaces.

docs/superpowers/plans/2026-07-29-baseline-relative-recovery-metrics.md

Other (4) +59 / -1
package.jsonExport baseline-relative metrics contract module +1/-0

Export baseline-relative metrics contract module

• Adds an exports entry for ./baseline-relative-metrics so web/clients can import the canonical schema.

packages/server/package.json

0062_daily_recovery_baseline_context.tsAdd ClickHouse migration for recovery baseline context columns +55/-0

Add ClickHouse migration for recovery baseline context columns

• Adds additive columns to analytics.daily_recovery_inputs and analytics.daily_recovery for baseline counts/coverage, 7/28 means, efficiency stats, and z-score columns.

src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.ts

registry.tsRegister new 0062 ClickHouse migration +2/-0

Register new 0062 ClickHouse migration

• Imports and appends the daily recovery baseline context migration to the ClickHouse migration registry.

src/db/clickhouse-migrations/registry.ts

vitest.config.tsFix web test setup resolution in isolated worktrees +1/-1

Fix web test setup resolution in isolated worktrees

• Changes Vitest setupFiles to use a repository-relative web test setup path to avoid escaping worktrees during test runs.

vitest.config.ts

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for a97cf71f are ready:

This comment updates automatically on each PR push.

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

@qodo-code-review

qodo-code-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 218 rules

Grey Divider


Remediation recommended

1. Unbounded baseline query ✓ Resolved 🐞 Bug ➹ Performance
Description
dailyMetrics.trends queries RecoveryBaselineRepository.listRange() from 1970-01-01 when
range.days is null, potentially scanning the user’s entire analytics.daily_recovery history
just to compute latest baseline-relative metrics. This can significantly increase dashboard latency
and ClickHouse load for “all time” ranges.
Code

packages/server/src/routers/daily-metrics.ts[R80-94]

+      const baselineStartDate =
+        range.days === null
+          ? "1970-01-01"
+          : dateWindowStartString(input.endDate, Math.max(0, range.days - 1));
+      const [restingHeartRateCte, baselineRows] = await Promise.all([
+        fetchRestingHeartRateValuesCte({
+          sensorStore,
+          userId: ctx.userId,
+          timezone: ctx.timezone,
+          endDate: input.endDate,
+          days: range.days,
+        }),
+        baselineRepository.listRange(baselineStartDate, input.endDate, {
+          priority: "dashboard",
+        }),
Relevance

●●● Strong

Team has accepted bounding expensive dashboard queries to reduce ClickHouse load/latency (PR1132).

PR-#1132

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The router explicitly sets the start date to 1970 for days === null, and the baseline repository
query uses that start date as a lower bound against analytics.daily_recovery FINAL, causing
potentially unbounded reads.

packages/server/src/routers/daily-metrics.ts[69-95]
packages/server/src/repositories/recovery-baseline-repository.ts[145-210]

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

### Issue description
When `range.days === null`, the router requests baseline rows starting at 1970, but it only uses them to derive *latest* baselineRelative metrics.

### Issue Context
`RecoveryBaselineRepository.listRange()` runs a range query over `analytics.daily_recovery FINAL` constrained by `startDate`/`endDate`, so an early startDate can pull a large result set.

### Fix
Avoid full-history scans in the `days === null` case by either:
- querying only the last N days (e.g., 365) and falling back to a wider lookback only if no metrics are found, or
- adding a dedicated repository method to fetch the latest non-null metric rows (per metric key) directly in ClickHouse (e.g., ORDER BY date DESC LIMIT … / argMax patterns).

### Fix Focus Areas
- packages/server/src/routers/daily-metrics.ts[69-104]
- packages/server/src/repositories/recovery-baseline-repository.ts[145-210]

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


2. Respiratory rate intent wrong ✓ Resolved 🐞 Bug ≡ Correctness
Description
recoveryMetricIntent() returns neutral for respiratory_rate, so health-status
classification/labels won’t reflect that lower respiratory rate is better. This contradicts
readiness scoring which inverts the respiratory-rate z-score (treating higher RR as worse).
Code

packages/server/src/services/health-status.ts[R201-205]

+function recoveryMetricIntent(metric: BaselineRelativeMetric): HealthMetricIntent {
+  if (metric.metric === "hrv" || metric.metric === "sleep_efficiency") return "higher";
+  if (metric.metric === "resting_heart_rate") return "lower";
+  return "neutral";
+}
Relevance

●●● Strong

Team fixes health-status metric intent/classification correctness (steps status fix accepted in
PR1960).

PR-#1960

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Health status intent currently classifies respiratory rate as neutral, while readiness scoring
explicitly treats lower respiratory rate as better by negating its z-score.

packages/server/src/services/health-status.ts[201-205]
packages/server/src/services/mobile-recovery-tab.ts[65-82]

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

### Issue description
`recoveryMetricIntent()` falls back to `"neutral"` for `respiratory_rate`, which makes the newly-exposed respiratory-rate health status inconsistent with the rest of recovery scoring.

### Issue Context
Readiness scoring treats higher respiratory rate as worse by negating the z-score before mapping to a score.

### Fix
Update `recoveryMetricIntent()` to return `"lower"` for `respiratory_rate`.

### Fix Focus Areas
- packages/server/src/services/health-status.ts[201-205]
- packages/server/src/services/health-status.test.ts[11-80] (add a regression test covering respiratory_rate intent/status behavior)

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



Informational

3. Stress baseline window mismatch 🐞 Bug ≡ Correctness
Description
StressRepository now consumes hrv_z_score/resting_hr_z_score from analytics.daily_recovery,
but those z-scores are computed from 30-day baselines while the stress model is documented as using
60-day baseline deviations. This silently changes stress scores/trends versus prior behavior and
risks miscalibration against existing thresholds.
Code

packages/server/src/repositories/stress-repository.ts[R45-50]

const rawRowSchema = z.object({
  date: dateStringSchema,
-  hrv: z.coerce.number().nullable(),
-  resting_hr: z.coerce.number().nullable(),
-  hrv_mean_60d: z.coerce.number().nullable(),
-  hrv_sd_60d: z.coerce.number().nullable(),
-  rhr_mean_60d: z.coerce.number().nullable(),
-  rhr_sd_60d: z.coerce.number().nullable(),
+  hrv_z_score: z.coerce.number().nullable(),
+  resting_hr_z_score: z.coerce.number().nullable(),
  efficiency_pct: z.coerce.number().nullable(),
});
Relevance

● Weak

Team standardized recovery baselines to canonical ClickHouse 30‑day z-scores; stress repo already
migrated similarly (PR1244).

PR-#1244

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The stress model documentation calls out 60-day baseline deviations, but the ClickHouse z-score
fields now being consumed are defined using 30-day baseline windows in the read model.

packages/recovery/src/stress.ts[1-24]
analytics/models/read_models/daily_recovery.sql[68-92]
packages/server/src/repositories/stress-repository.ts[45-113]

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

### Issue description
Stress scoring is documented/tuned for 60-day baseline deviations, but the repository now reads z-scores computed from 30-day baselines in the ClickHouse read model.

### Issue Context
- `@dofek/recovery/stress` explicitly describes 60-day baselines.
- `analytics.daily_recovery` computes `*_z_score` from `*_mean_30d/*_sd_30d`.

### Fix
Choose one and make it consistent end-to-end:
1) Keep stress as 60-day based: query the existing 60d fields and compute deviations in `StressRepository` (or add dedicated `*_z_score_60d` columns in ClickHouse and consume those).
2) If stress is intended to move to 30-day baselines: update the stress module docs and re-tune/validate `stressThresholds` accordingly, plus add regression tests that lock expected behavior.

### Fix Focus Areas
- packages/server/src/repositories/stress-repository.ts[45-113]
- analytics/models/read_models/daily_recovery.sql[68-92]
- packages/recovery/src/stress.ts[1-24]

ⓘ 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/server/src/services/health-status.ts Outdated
Comment thread packages/server/src/routers/daily-metrics.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
analytics/models/read_models/daily_recovery_inputs.sql (1)

122-223: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Nullify empty-window means to preserve explicit-gap semantics.

Following the analytics-rule requirement to preserve bounded/dirty-key behavior, empty windows here currently emit NaN via avg(...) OVER (...); the SD guards handle baseline_sample_count and IS NOT NULL checks, but mean-only gaps are not caught and incrementally compared NaNs make the dirty-key comparison fragile. Convert empty-window means to NULL consistently, including hrv_mean_7d/hrv_mean_previous_28d and all RHR/RR/efficiency counterparts, using avgOrNull(...) or an equivalent sample-count guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@analytics/models/read_models/daily_recovery_inputs.sql` around lines 122 -
223, Update the windowed mean expressions in the window_statistics CTE to use
avgOrNull (or an equivalent count-based NULL guard) so empty windows return NULL
instead of NaN. Apply this consistently to the 30-day, 7-day, previous-28-day,
and 60-day means for HRV, resting heart rate, respiratory rate, and
efficiency_pct, while preserving existing window ranges and standard-deviation
logic.
analytics/models/read_models/daily_recovery.sql (1)

71-98: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat empty rolling windows as missing baselines before scoring.

hrv_mean_30d/rhr_mean_30d/rr_mean_30d/efficiency_mean_30d are produced by plain avg(...) OVER (...) in daily_recovery_inputs.sql; when the 30-day preceding window is empty, ClickHouse returns NaN, and NaN IS NOT NULL makes the z-score guards pass. The result is NaN persisted for hrv_z_score/resting_hr_z_score/respiratory_rate_z_score, and the downstream IS NULL fallbacks in this file lose their intended 62 sentinel values.

Fix this at the source in daily_recovery_inputs.sql’s window_statistics CTE by returning NULL when the relevant baseline sample count is zero, then keep the existing z-score/null checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@analytics/models/read_models/daily_recovery.sql` around lines 71 - 98, The
window_statistics CTE in daily_recovery_inputs.sql must convert empty 30-day
baseline averages to NULL by checking each relevant baseline sample count and
returning NULL when it is zero. Update the HRV, resting heart rate, respiratory
rate, and efficiency baseline expressions at the source, while leaving the
existing z-score guards and sleep_score logic in daily_recovery.sql unchanged.
packages/server/src/contracts/mobile-dashboard-contracts.ts (1)

105-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate metric-key literals — derive from baselineRelativeMetricKeySchema instead.

healthMetricKeySchema's first four values (hrv, resting_heart_rate, respiratory_rate, sleep_efficiency) duplicate baselineRelativeMetricKeySchema in baseline-relative-metrics.ts. Adding a new baseline metric later requires remembering to update both enums in sync.

♻️ Proposed fix to derive from the canonical schema
+import { baselineRelativeMetricKeySchema } from "./baseline-relative-metrics.ts";
+
 export const healthMetricKeySchema = z.enum([
-  "hrv",
-  "resting_heart_rate",
-  "respiratory_rate",
-  "sleep_efficiency",
+  ...baselineRelativeMetricKeySchema.options,
   "spo2",
   "steps",
   "skin_temperature",
   "trend_weight",
   "body_fat_percentage",
 ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/contracts/mobile-dashboard-contracts.ts` around lines 105
- 115, Update healthMetricKeySchema to derive the shared baseline metric keys
from baselineRelativeMetricKeySchema instead of duplicating its first four
literals, while retaining the remaining health-specific keys and preserving the
resulting schema behavior.
packages/server/src/services/mobile-recovery-tab.test.ts (1)

62-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

One 30+ column analytics.daily_recovery fixture, copy-pasted per test file. Identical bodies also exist in packages/server/src/mcp/route.test.ts and packages/server/src/routers/daily-metrics.test.ts, so every new baseline column costs four synchronized edits and any missed copy fails as a Zod parse error rather than an obvious diff.

  • packages/server/src/services/mobile-recovery-tab.test.ts#L62-L99: move recoveryRow into a colocated test-helpers.ts under packages/server/src/services/ (or a shared server-test helper module) and export it.
  • packages/server/src/routers/mobile-dashboard.test.ts#L822-L844: replace the inline row literal with a call to that shared helper plus the overrides this test actually cares about.

As per coding guidelines: "Shared test helpers and fixtures used by multiple tests belong in a local test-helpers.ts file, not in production source files or another test file."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/services/mobile-recovery-tab.test.ts` around lines 62 -
99, Move the duplicated recoveryRow fixture from
packages/server/src/services/mobile-recovery-tab.test.ts:62-99 into an exported
local test-helpers.ts, then update that test and the inline fixture at
packages/server/src/routers/mobile-dashboard.test.ts:822-844 to import and call
the shared recoveryRow helper with only the needed overrides; preserve all
existing fixture defaults and test-specific values.

Source: Coding guidelines

packages/server/src/routers/recovery.ts (1)

433-433: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The 30-day warmup fetch is now dead weight. Baselines and z-scores are precomputed in analytics.daily_recovery, so nothing in this handler needs rows before the output window — they are fetched, parsed, and then dropped by the metrics.date <= cutoffDate guard on Line 488. Query the requested window directly.

♻️ Proposed change
-      const queryRange = range.withWarmupDays(30);
+      const queryRange = range;

Also applies to: 455-469

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/routers/recovery.ts` at line 433, Remove the
withWarmupDays(30) expansion from the recovery handler’s query range
construction, including the analogous range used around the rows referenced at
lines 455-469. Query only the requested output window while preserving the
existing date cutoff and downstream metric processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-07-29-baseline-relative-recovery-metrics.md`:
- Line 28: Update the story requirements in the plan to explicitly include
loading and empty/no-data states alongside default/complete, sparse,
unavailable, and directional variants. Apply the same requirement to the
corresponding story requirement at the other referenced location, preserving the
existing meaningful variant coverage.

In `@packages/mobile/app/`(tabs)/recovery.tsx:
- Around line 71-96: Extract the shared baseline-relative metric formatting into
`@dofek/format`, preserving the complete behavior of formatBaselineContext and
supporting the optional formatter/unit parameters needed by the web caller. In
packages/mobile/app/(tabs)/recovery.tsx lines 71-96, remove the local
formatBaselineContext implementation and import the shared helper. In
packages/web/src/components/HealthStatusBar.tsx lines 27-55, remove the
duplicate formatting logic, including formatContextValue, and call the shared
helper with the existing formatter and unit values.
- Around line 397-400: Move the shared baseline-context formatting logic used by
formatBaselineContext in recovery.tsx and HealthStatusBar.tsx into
`@dofek/format`, preserving the required baseline, z-score, and comparison
semantics for each platform. Export the shared formatter/helper from
`@dofek/format`, then update both platform components to import and use it instead
of maintaining separate client-side implementations.

In `@packages/server/src/mcp/tools.ts`:
- Around line 323-339: Update the get_health_trends handler to fail explicitly
when context.sensorStore is absent, using the same precondition-check pattern
and error message style as get_body_metrics. After validating the required
ClickHouse analytics store, remove the baselineRows fallback to [] and preserve
the existing repository query path.

In `@packages/server/src/routers/recovery.test.ts`:
- Around line 47-68: Remove the TypeScript z-score synthesis from
packages/server/src/routers/recovery.test.ts#L47-L68 by deleting baselineZScore
and canonicalizeRecoveryRows, then provide all three *_z_score values directly
in makeSensorStore fixtures. In
packages/server/src/repositories/stress-repository.test.ts#L27-L52, restore the
query mock to return rows directly, add both z-score fields to makeRow, and
remove mean/sd fields. In packages/server/src/routers/stress.test.ts#L53-L83,
remove zScore and define z-score values in makeRow defaults or overrides. In
packages/server/src/routers/strength-stress.test.ts#L184-L203, remove zScore and
recoveryRows mapping so the analytics.daily_recovery branch returns
caller-supplied rows; leave statistical semantics to the ClickHouse integration
test.

In `@packages/server/src/routers/recovery.ts`:
- Around line 491-506: Create a shared readiness-components helper in
`@dofek/scoring` beside zScoreToRecoveryScore, with a named 62 fallback constant,
centralized sign inversions, and sleep-efficiency clamping. In
packages/server/src/routers/recovery.ts#L491-L506, replace the inline
calculations with the helper using the parsed row’s four *_z_score fields and
efficiency_pct; in packages/server/src/services/mobile-recovery-tab.ts#L60-L98,
have computeReadinessRows resolve the four baseline metrics and delegate to the
same helper so both surfaces use one canonical computation.

In `@packages/server/src/services/daily-recovery-read-model.integration.test.ts`:
- Around line 52-75: Add a regression test alongside the existing daily recovery
integration fixture that represents a new user or metric with zero antecedent
baseline samples. Seed only the required current-day data, run
materializeRecoveryInputs and materializeRecovery, then assert
hrv_baseline_sample_count is 0 and the resulting mean/z-score are NULL while the
recovery score uses the documented default of 62; ensure the assertions reject
NaN.

In `@packages/server/src/services/health-status.test.ts`:
- Around line 38-80: Expand the test around buildDailyMetricHealthStatuses to
table-drive all four recoveryMetricIntent metrics: hrv, resting_heart_rate,
respiratory_rate, and sleep_efficiency. Provide each case with its corresponding
baseline-relative metric and assert the expected intent-derived status, ensuring
respiratory_rate validates the intended non-neutral branch rather than the
current fallthrough.

In `@packages/server/src/services/health-status.ts`:
- Around line 220-223: Make baselineRelative a required parameter of
buildDailyMetricHealthStatuses by removing its = [] default, preserving the
existing production caller in daily-metrics.ts. Update the health-status test
that omits this argument to explicitly pass an empty array.
- Around line 201-205: Update recoveryMetricIntent so the respiratory_rate
metric returns "lower", aligning its intent with the inverted z-score behavior
in computeReadinessRows and the readinessScore handler. Keep the existing hrv,
sleep_efficiency, resting_heart_rate, and neutral fallbacks unchanged.

In `@packages/server/src/services/mobile-recovery-tab.ts`:
- Around line 56-58: Update findRecoveryMetric so its metric parameter uses the
BaselineRelativeMetric["metric"] literal union instead of string, importing
BaselineRelativeMetric as a type from the baseline-relative-metrics contract.
Keep the existing lookup behavior unchanged while ensuring invalid metric keys,
including mismatched spellings, fail at compile time.

---

Outside diff comments:
In `@analytics/models/read_models/daily_recovery_inputs.sql`:
- Around line 122-223: Update the windowed mean expressions in the
window_statistics CTE to use avgOrNull (or an equivalent count-based NULL guard)
so empty windows return NULL instead of NaN. Apply this consistently to the
30-day, 7-day, previous-28-day, and 60-day means for HRV, resting heart rate,
respiratory rate, and efficiency_pct, while preserving existing window ranges
and standard-deviation logic.

In `@analytics/models/read_models/daily_recovery.sql`:
- Around line 71-98: The window_statistics CTE in daily_recovery_inputs.sql must
convert empty 30-day baseline averages to NULL by checking each relevant
baseline sample count and returning NULL when it is zero. Update the HRV,
resting heart rate, respiratory rate, and efficiency baseline expressions at the
source, while leaving the existing z-score guards and sleep_score logic in
daily_recovery.sql unchanged.

In `@packages/server/src/contracts/mobile-dashboard-contracts.ts`:
- Around line 105-115: Update healthMetricKeySchema to derive the shared
baseline metric keys from baselineRelativeMetricKeySchema instead of duplicating
its first four literals, while retaining the remaining health-specific keys and
preserving the resulting schema behavior.

In `@packages/server/src/routers/recovery.ts`:
- Line 433: Remove the withWarmupDays(30) expansion from the recovery handler’s
query range construction, including the analogous range used around the rows
referenced at lines 455-469. Query only the requested output window while
preserving the existing date cutoff and downstream metric processing.

In `@packages/server/src/services/mobile-recovery-tab.test.ts`:
- Around line 62-99: Move the duplicated recoveryRow fixture from
packages/server/src/services/mobile-recovery-tab.test.ts:62-99 into an exported
local test-helpers.ts, then update that test and the inline fixture at
packages/server/src/routers/mobile-dashboard.test.ts:822-844 to import and call
the shared recoveryRow helper with only the needed overrides; preserve all
existing fixture defaults and test-specific values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ed9aebc-eb1d-4b94-aca6-0750a78a42d6

📥 Commits

Reviewing files that changed from the base of the PR and between d08ce9c and c3effda.

📒 Files selected for processing (47)
  • analytics/models/read_models/daily_recovery.sql
  • analytics/models/read_models/daily_recovery_inputs.sql
  • analytics/models/read_models/read_model_microbatch.sql.test.ts
  • docs/mcp.md
  • docs/superpowers/plans/2026-07-29-baseline-relative-recovery-metrics.md
  • packages/mobile/app/(tabs)/recovery.stories.tsx
  • packages/mobile/app/(tabs)/recovery.test.tsx
  • packages/mobile/app/(tabs)/recovery.tsx
  • packages/mobile/components/HealthStatusCards.tsx
  • packages/server/package.json
  • packages/server/src/contracts/baseline-relative-metrics.test.ts
  • packages/server/src/contracts/baseline-relative-metrics.ts
  • packages/server/src/contracts/mobile-dashboard-contracts.test.ts
  • packages/server/src/contracts/mobile-dashboard-contracts.ts
  • packages/server/src/mcp/route.test.ts
  • packages/server/src/mcp/tools.ts
  • packages/server/src/repositories/recovery-baseline-repository.test.ts
  • packages/server/src/repositories/recovery-baseline-repository.ts
  • packages/server/src/repositories/stress-repository.test.ts
  • packages/server/src/repositories/stress-repository.ts
  • packages/server/src/routers/clickhouse-integration-test-models.ts
  • packages/server/src/routers/daily-metrics.test.ts
  • packages/server/src/routers/daily-metrics.ts
  • packages/server/src/routers/mobile-dashboard.test.ts
  • packages/server/src/routers/recovery-settings-sleep-need-sport-settings.test.ts
  • packages/server/src/routers/recovery.test.ts
  • packages/server/src/routers/recovery.ts
  • packages/server/src/routers/strength-stress.test.ts
  • packages/server/src/routers/stress.test.ts
  • packages/server/src/services/daily-recovery-read-model.integration.test.ts
  • packages/server/src/services/health-status.test.ts
  • packages/server/src/services/health-status.ts
  • packages/server/src/services/mobile-recovery-tab.test.ts
  • packages/server/src/services/mobile-recovery-tab.ts
  • packages/server/src/types.ts
  • packages/web/src/components/HealthStatusBar.stories.tsx
  • packages/web/src/components/HealthStatusBar.test.tsx
  • packages/web/src/components/HealthStatusBar.tsx
  • packages/web/src/components/TimeRangeSelector.consumers.test.tsx
  • packages/web/src/lib/healthStatus.ts
  • packages/web/src/pages/Dashboard.test.tsx
  • packages/web/src/pages/Dashboard.tsx
  • src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.test.ts
  • src/db/clickhouse-migrations/0062_daily_recovery_baseline_context.ts
  • src/db/clickhouse-migrations/registry.test.ts
  • src/db/clickhouse-migrations/registry.ts
  • vitest.config.ts

Comment thread docs/superpowers/plans/2026-07-29-baseline-relative-recovery-metrics.md Outdated
Comment thread packages/mobile/app/(tabs)/recovery.tsx Outdated
Comment thread packages/mobile/app/(tabs)/recovery.tsx Outdated
Comment thread packages/server/src/mcp/tools.ts
Comment thread packages/server/src/routers/recovery.test.ts Outdated
Comment thread packages/server/src/services/health-status.test.ts
Comment thread packages/server/src/services/health-status.ts Outdated
Comment thread packages/server/src/services/health-status.ts
Comment thread packages/server/src/services/mobile-recovery-tab.ts Outdated
@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

codereviewbot-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Review failed: an error occurred during analysis.

@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 2efa929 into main Jul 29, 2026
113 checks passed
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.

Expose baseline-relative metrics (rolling mean, SD, z-score, 7d vs 28d delta), not just raw values

2 participants