feat(body): add measurement decision context - #2417
Conversation
|
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. |
|
LGTM! The PR introduces 🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does. |
Reviewer's GuideAdds a server-authored body measurement decision context (latest measurement provenance, Trend Weight contract, and personalized variation band) shared between web Body page and mobile Recovery tab, backed by ClickHouse queries and a dedicated service, plus Infisical secret masking improvements and incident documentation. Sequence diagram for body decision context computation and mobile recovery renderingsequenceDiagram
actor MobileUser
participant RecoveryScreen
participant loadMobileRecoveryTab
participant BodyAnalyticsRepository
participant BodyClickHouseStore
participant BodyDecisionContextService
participant BodyDecisionContext
MobileUser->>RecoveryScreen: Open Recovery tab
RecoveryScreen->>loadMobileRecoveryTab: loadMobileRecoveryTab(endDate)
loadMobileRecoveryTab->>BodyAnalyticsRepository: getBodyDecisionContext(endDate)
BodyAnalyticsRepository->>BodyClickHouseStore: fetchBodyDecisionMeasurements(userId, timezone, endDate)
BodyAnalyticsRepository->>BodyAnalyticsRepository: getSmoothedWeight(days, endDate)
BodyAnalyticsRepository->>BodyDecisionContextService: buildBodyDecisionContext(measurements, trendPoints)
BodyDecisionContextService-->>BodyAnalyticsRepository: BodyDecisionContext
BodyAnalyticsRepository-->>loadMobileRecoveryTab: decisionContext
loadMobileRecoveryTab-->>RecoveryScreen: recoveryData with decisionContext
RecoveryScreen->>BodyDecisionContext: BodyDecisionContext(context)
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughThe PR adds server-authored body measurement decision context with provenance, Trend Weight processing rules, and residual variation. It exposes the context through analytics and recovery responses, renders it on web and mobile, adds documentation and tests, and masks Infisical secrets in GitHub Actions. ChangesBody decision context
Infisical secret masking
Mobile dashboard test maintenance
Estimated code review effort: 4 (Complex) | ~45 minutes Assessment against linked issues
Out-of-scope changes
Sequence Diagram(s)sequenceDiagram
participant WebOrMobile
participant AnalyticsOrRecovery
participant BodyAnalyticsRepository
participant ClickHouse
participant BodyDecisionContext
WebOrMobile->>AnalyticsOrRecovery: request body overview or recovery data
AnalyticsOrRecovery->>BodyAnalyticsRepository: getBodyDecisionContext(endDate)
BodyAnalyticsRepository->>ClickHouse: fetch measurements and trend points
ClickHouse-->>BodyAnalyticsRepository: return provenance and trend data
BodyAnalyticsRepository->>BodyDecisionContext: build decision context
BodyDecisionContext-->>AnalyticsOrRecovery: return nullable context
AnalyticsOrRecovery-->>WebOrMobile: return body data and decision context
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (1 passed)
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. Comment |
PR Summary by QodoBody: add server-authored measurement decision context (web + mobile)
AI Description
Diagram
High-Level Assessment
Files changed (34)
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
211 rules✅ Skills:
fix-provider, write-tests, cloudflare 1.
|
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 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 @.github/actions/load-infisical-secrets/action.yml:
- Around line 107-116: Update mask_value to escape secret values for GitHub
workflow commands by replacing percent signs before escaping carriage returns
and line feeds, then emit each add-mask command with printf '%s\n' rather than
echo. Apply the same escaping and safe command emission to the success path that
masks retrieved values before output.
In `@docs/production-incident-baseline.md`:
- Around line 26-27: Update the wrapped Markdown line in the incident baseline
text so the literal PR reference beginning with `#2303` cannot be interpreted as a
malformed heading, while preserving the existing wording and meaning.
In `@packages/format/src/body-decision-context.ts`:
- Around line 10-41: Update BodyDecisionContextView to reference the named
BodyDecisionLatestMeasurement and BodyDecisionVariation interfaces for
latestMeasurement and variation instead of duplicating their inline shapes. Move
those interface declarations above BodyDecisionContextView for clarity,
preserving the existing nullable latestMeasurement contract.
In `@packages/mobile/components/BodyDecisionContext.stories.tsx`:
- Around line 56-91: Add an Unavailable story alongside Available and
InsufficientData in the story module, configuring its args with context set to
null so the component’s unavailable-context path is covered.
In `@packages/server/src/repositories/body-clickhouse.ts`:
- Around line 58-65: Update the ClickHouse query fields producing recorded_at,
including the additional occurrence around the body decision measurement query,
to format timestamps explicitly in UTC as an ISO-style string with a trailing Z
before schema parsing. Keep the recorded_at field mapped to
timestampStringSchema and leave unrelated fields unchanged.
In `@packages/server/src/services/mobile-recovery-tab.ts`:
- Around line 227-244: Wrap the getBodyDecisionContext call in
loadMobileRecoveryTab’s Promise.all with a per-call catch that logs the
rejection according to the mandatory catch-block rule and returns null. Preserve
resolution of the remaining recovery-tab data, and add a regression test in
mobile-recovery-tab.test.ts asserting a rejected decision-context fetch yields
result.decisionContext as null while the other payload fields resolve.
In `@packages/web/src/components/BodyDecisionContext.stories.tsx`:
- Around line 44-58: Add an Unavailable story alongside InsufficientData in the
BodyDecisionContext story definitions, passing context: null through its args to
cover the component’s unavailable state while preserving the existing meaningful
and insufficient-data stories.
In `@packages/web/src/components/BodyDecisionContext.test.tsx`:
- Around line 7-33: Update the context fixture declaration in
BodyDecisionContext.test.tsx to use satisfies against the BodyDecisionContext
prop contract, removing the individual as const assertions from trendWeight and
variation literals. Preserve the fixture values while allowing TypeScript to
validate them against the component’s expected context type.
In `@scripts/load-infisical-secrets-policy.test.ts`:
- Around line 8-21: Remove the static action.yml inspection test, including the
test case that reads actionPath and asserts mask-loop, masking-command, and
ordering strings. Use approved schema, build, lint, or review validation
instead, and update the incident document’s validation section to describe the
checks that remain; do not replace it with another test for declarative
configuration.
🪄 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: d976a7e8-c56c-4af3-9eb5-c9ac3d9887e8
📒 Files selected for processing (34)
.github/actions/load-infisical-secrets/action.ymldocs/README.mddocs/body-metrics-decision-context.mddocs/production-incident-baseline.mdpackages/format/package.jsonpackages/format/src/body-decision-context.test.tspackages/format/src/body-decision-context.tspackages/mobile/app/(tabs)/recovery.stories.tsxpackages/mobile/app/(tabs)/recovery.test.tsxpackages/mobile/app/(tabs)/recovery.tsxpackages/mobile/components/BodyDecisionContext.stories.tsxpackages/mobile/components/BodyDecisionContext.test.tsxpackages/mobile/components/BodyDecisionContext.tsxpackages/server/src/contracts/body-decision-context.tspackages/server/src/contracts/mobile-dashboard-contracts.test.tspackages/server/src/contracts/mobile-dashboard-contracts.tspackages/server/src/repositories/body-analytics-repository.test.tspackages/server/src/repositories/body-analytics-repository.tspackages/server/src/repositories/body-clickhouse.test.tspackages/server/src/repositories/body-clickhouse.tspackages/server/src/repositories/body-repository.integration.test.tspackages/server/src/routers/body-analytics.test.tspackages/server/src/routers/body-analytics.tspackages/server/src/routers/mobile-dashboard.test.tspackages/server/src/services/body-decision-context.test.tspackages/server/src/services/body-decision-context.tspackages/server/src/services/mobile-recovery-tab.test.tspackages/server/src/services/mobile-recovery-tab.tspackages/web/src/components/BodyDecisionContext.stories.tsxpackages/web/src/components/BodyDecisionContext.test.tsxpackages/web/src/components/BodyDecisionContext.tsxpackages/web/src/pages/BodyPage.test.tsxpackages/web/src/pages/BodyPage.tsxscripts/load-infisical-secrets-policy.test.ts
|
🤖 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. |
1 similar comment
|
🤖 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. |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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 `@packages/server/src/repositories/body-clickhouse.ts`:
- Line 193: Update fetchBodyDecisionMeasurements and its
accessWindowDateClause/endDateExpression handling so endDate === "now" resolves
to the requested user-local date via the existing timezone expression, matching
recorded_at comparisons instead of using session-dependent today(). Add boundary
coverage for measurements on the current user-local day when the ClickHouse
session timezone differs.
In `@packages/server/src/routers/mobile-dashboard.recovery-training.test.ts`:
- Around line 149-153: Consolidate the recovery and training scenarios from this
file into mobile-dashboard.test.ts so mobileDashboardRouter has a single unit
test file. Remove the separate mobile-dashboard.recovery-training.test.ts file
and preserve its existing coverage in the consolidated test suite.
- Around line 78-110: Parameterize makeSensorStore with an optional custom query
function while preserving its current default query behavior. Replace the
duplicated inline ActivitySensorStore objects in the consolidated recovery and
training tab tests with makeSensorStore calls that supply their custom query
functions, reusing the shared mock methods and allowing the existing
test-specific query behavior to remain unchanged.
In `@packages/server/src/services/mobile-recovery-tab.context.test.ts`:
- Around line 22-134: Move the two `loadMobileRecoveryTab` tests from
`mobile-recovery-tab.context.test.ts` into the existing
`mobile-recovery-tab.test.ts` suite, preserving their assertions and mocks.
Remove the separate scenario test file or its duplicated coverage, and keep
production responsibilities unchanged.
In `@packages/server/src/services/mobile-recovery-tab.test-helpers.ts`:
- Around line 1-8: Rename the shared helper module from
mobile-recovery-tab.test-helpers.ts to test-helpers.ts, then update every import
and reference to use the new colocated module name. Preserve all existing
fixtures, mocks, and exported symbols without other changes.
🪄 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: ea572b81-f5ab-4302-8404-45aef63d0056
📒 Files selected for processing (17)
.github/actions/load-infisical-secrets/action.ymldocs/production-incident-baseline.mdpackages/format/src/body-decision-context.tspackages/mobile/components/BodyDecisionContext.stories.tsxpackages/server/src/repositories/body-clickhouse.test.tspackages/server/src/repositories/body-clickhouse.tspackages/server/src/routers/body-analytics.test.tspackages/server/src/routers/body-analytics.tspackages/server/src/routers/mobile-dashboard.recovery-training.test.tspackages/server/src/routers/mobile-dashboard.test.tspackages/server/src/services/health-status.tspackages/server/src/services/mobile-recovery-tab.context.test.tspackages/server/src/services/mobile-recovery-tab.test-helpers.tspackages/server/src/services/mobile-recovery-tab.test.tspackages/server/src/services/mobile-recovery-tab.tspackages/web/src/components/BodyDecisionContext.stories.tsxpackages/web/src/components/BodyDecisionContext.test.tsx
💤 Files with no reviewable changes (1)
- packages/server/src/routers/mobile-dashboard.test.ts
fdab846 to
14c6eb2
Compare
|
🤖 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. |
Mobile PreviewScan to open on device:
To test on device:
|
|
Storybook previews for This comment updates automatically on each PR push. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/server/src/contracts/mobile-dashboard-contracts.ts (1)
495-507: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winInclude
decisionContext.latestMeasurement.datein the fixture date-window check.
validateDatesInWindowchecks the dates ofhrvVariability,hrvBaseline,readinessScore,stress.daily,dailyMetrics, andweightagainstinput's window. The newdecisionContext.latestMeasurement.datefield is the same kind of date-bearing field but is not included. A story or test fixture could set an out-of-windowdecisionContext.latestMeasurement.datewithout this schema catching it.Add it to the validated list, handling the nullable
decisionContext/latestMeasurement.🧩 Proposed fix
validateDatesInWindow( input, [ ...data.hrvVariability.map((row) => row.date), ...data.hrvBaseline.map((row) => row.date), ...data.readinessScore.map((row) => row.date), ...data.stress.daily.map((row) => row.date), ...data.dailyMetrics.map((row) => row.date), ...data.weight.map((row) => row.date), + ...(data.decisionContext?.latestMeasurement + ? [data.decisionContext.latestMeasurement.date] + : []), ], context, );🤖 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 495 - 507, Update the date list passed to validateDatesInWindow in the surrounding superRefine callback to include decisionContext.latestMeasurement.date when both decisionContext and latestMeasurement are present, while safely omitting it when either is null or absent. Preserve the existing validation for all other date-bearing collections.
🤖 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/body-metrics-decision-context.md`:
- Around line 37-38: Update the threshold description in the decision-context
documentation to refer to fewer than eight residual observations, or valid
measurement/Trend Weight pairs, rather than actual readings; preserve the
existing insufficient-data behavior.
In `@docs/production-incident-baseline.md`:
- Line 162: Insert one blank line immediately before the incident heading
“2026-08-02: Mobile Preview OTA workflow exposed an unmasked Infisical
credential” in docs/production-incident-baseline.md, preserving the heading text
and GitHub-flavored Markdown structure.
---
Outside diff comments:
In `@packages/server/src/contracts/mobile-dashboard-contracts.ts`:
- Around line 495-507: Update the date list passed to validateDatesInWindow in
the surrounding superRefine callback to include
decisionContext.latestMeasurement.date when both decisionContext and
latestMeasurement are present, while safely omitting it when either is null or
absent. Preserve the existing validation for all other date-bearing collections.
🪄 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: bb13d318-1689-461d-9fc8-63fae30175fb
📒 Files selected for processing (21)
.github/actions/load-infisical-secrets/action.ymldocs/README.mddocs/body-metrics-decision-context.mddocs/production-incident-baseline.mdpackages/format/package.jsonpackages/format/src/body-decision-context.test.tspackages/format/src/body-decision-context.tspackages/mobile/app/(tabs)/recovery.stories.tsxpackages/mobile/app/(tabs)/recovery.test.tsxpackages/mobile/app/(tabs)/recovery.tsxpackages/mobile/components/BodyDecisionContext.stories.tsxpackages/mobile/components/BodyDecisionContext.test.tsxpackages/mobile/components/BodyDecisionContext.tsxpackages/server/src/contracts/body-decision-context.tspackages/server/src/contracts/mobile-dashboard-contracts.test.tspackages/server/src/contracts/mobile-dashboard-contracts.tspackages/server/src/repositories/body-analytics-repository.test.tspackages/server/src/repositories/body-analytics-repository.tspackages/server/src/repositories/body-clickhouse.test.tspackages/server/src/repositories/body-clickhouse.tspackages/server/src/repositories/body-repository.integration.test.ts
|
🤖 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. |
|
🤖 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. |
|
Also fixed the outside-diff review finding: mobile recovery fixture validation now includes nullable |
|
🤖 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. |
1 similar comment
|
🤖 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. |
|
🤖 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. |
Summary
GITHUB_ENV; document the exposedEXPO_TOKENfollow-up.Validation
pnpm test:changed— 15,385 passed, 21 skipped across 1,016 files.pnpm typecheck— passed.pnpm lint:sandbox— passed.pnpm test:integration -- packages/server/src/repositories/body-repository.integration.test.ts— blocked before tests because Docker cannot create the Compose network:all predefined address pools have been fully subnetted.pnpm lintreachedlint:analytics-sqlbut was blocked by ClickHouse unavailable at127.0.0.1:8123.Security follow-up
The historical
EXPO_TOKENvalue must be revoked in Expo and replaced in Infisical for dev/prod/staging. Expo documents revocation through the Access Tokens dashboard: https://docs.expo.dev/accounts/programmatic-access/#revoke-access-tokens.Fixes #2145
Summary by Sourcery
Introduce a server-authored body measurement decision context, including latest positive measurement provenance, trend-weight contract, and personalized variation band, and surface it across web and mobile experiences while tightening Infisical secret masking in CI.
New Features:
Bug Fixes:
Enhancements:
Build:
CI:
Documentation:
Tests:
Summary by cubic
Adds a server-authored body measurement decision context across web and mobile with clear provenance, Trend Weight method, and a personalized variation band. Fixes #2145; hardens CI secret masking, validates decision-context dates, improves timezone handling, and allows a nullable decision context in mobile contracts.
New Features
decisionContextfrom body analytics and mobile recovery APIs via one service and a new ClickHouse provenance query; include latest positive measurement (provider/source, user‑local time), Trend Weight contract (10% EWMA, linear interpolation, exclude non‑positive, retain outliers), and Tukey inner‑fence variation from up to 30 residuals; return null on errors or insufficient data.@dofek/format/body-decision-contextand cross‑platform BodyDecisionContext components; standardize UTC timestamps, pick one reading per local day, and document methodology and residual thresholds.Bug Fixes
decisionContextnullable in mobile recovery output and test it; validate decision‑context date fields.GITHUB_ENVwrite and on fetch errors; escape and mask multi‑line values line‑by‑line.Written for commit ebd6582. Summary will update on new commits.
Summary by CodeRabbit