Skip to content

Retire Postgres metric stream (P1): heart-rate reads from ClickHouse - #1268

Merged
Asherlc merged 7 commits into
mainfrom
Asherlc/retire-pg-metric-stream
Jun 8, 2026
Merged

Asherlc merged 7 commits into
mainfrom
Asherlc/retire-pg-metric-stream

Conversation

@Asherlc

@Asherlc Asherlc commented Jun 8, 2026 •

Copy link
Copy Markdown
Owner

First step of retiring fitness.metric_stream from Postgres. Full sequenced plan in docs/metric-stream-postgres-retirement.md (P0 rename CH table → P1 migrate scalar readers → P2 IMU debug to R2 coverage → P3 drop PG table + retire PeerDB mirror + delete backfill script).

Context

Writers already publish only to Redpanda; fitness.metric_stream is frozen. Readers still on Postgres serve stale data. This migrates them to the Redpanda-fed ClickHouse mirror.

This PR

  • Plan doc for the whole retirement.
  • P1 reader Add AI-powered nutrition input with Gemini/Mistral cascade #1 — heart-rate dailyBySource → ClickHouse:
    • New HeartRateRepository (server convention: no raw SQL in routers), reads via the ActivitySensorStore.query() escape hatch.
    • Raw CH mirror with version-dedup (FINAL + _peerdb_is_deleted = 0), not deduped_sensor — preserves per-source rows for the overlay (priority-dedup would collapse sources).
    • Local-day window via toDate(recorded_at, timezone); minute bins formatted to ISO-8601 Z to keep the client contract.
    • Tests: repository unit, router unit (sensorStore wiring + empty-when-unavailable), and a real-ClickHouse integration test (per-source separation, version dedup, deleted/other-day/zero/other-channel exclusion).

Remaining readers (provider-detail, spo2/skin-temp), the v_metric_stream drop, P2 (IMU), P0 (rename), and P3 (drop table) follow per the doc.

🤖 Generated with Claude Code

Summary by Sourcery

Migrate the heart-rate dailyBySource API from Postgres metric_stream to the ClickHouse-backed metric stream mirror and document the full Postgres metric_stream retirement plan.

Enhancements:

  • Introduce a HeartRateRepository to encapsulate ClickHouse-backed heart-rate reads and keep routers free of raw SQL.
  • Update the heartRateRouter to use the repository via the ActivitySensorStore escape hatch and handle missing ClickHouse by returning an empty array.

Documentation:

  • Add a metric-stream Postgres retirement plan document outlining the phased migration and cleanup steps.

Tests:

  • Add a ClickHouse-backed integration test for HeartRateRepository verifying per-source aggregation, version deduplication, and row filtering.
  • Update heart-rate router unit tests to exercise the new repository-based read path and behavior when ClickHouse is unavailable.

Summary by cubic

Serve heart-rate dailyBySource from the Redpanda-fed ClickHouse mirror instead of Postgres to restore freshness and kick off Postgres metric_stream retirement. Client contract stays the same; per-source overlays are preserved.

  • Refactors
    • Docs: added docs/metric-stream-postgres-retirement.md; updated AGENTS.md to ban test/optional-only branches and require non-optional deps.
    • Introduced HeartRateRepository reading ClickHouse via ActivitySensorStore.query(); removed raw SQL from the router.
    • Query: reads postgres_fitness.metric_stream with FINAL and _peerdb_is_deleted = 0, filters scalar > 0, bins to 1 minute with ISO .000Z timestamps; day window is [toDateTime(date, tz), +1 day) for ORDER BY pruning; no provider-priority collapse.
    • Router: uses required ctx.sensorStore (no guard); createApp and tRPC Context now require a sensor store; failures bubble via tRPC/Sentry.
    • Tests: repository unit test, router wiring test, and real ClickHouse integration test (per-source separation, version dedup, and exclusion rules). Integration tests now pass makeMockSensorStore().

Written for commit 1f785bf. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Documentation

    • Added a runbook detailing retirement and staged migration of the Postgres-backed metric stream, cutover steps, validation gates, and rollback considerations.
  • Refactor

    • Reworked heart-rate retrieval to use a repository-backed data access layer and centralized data-shaping types.
  • Tests

    • Added integration and unit tests validating per-provider daily heart-rate series, deduplication, and edge-case exclusions.

Asherlc and others added 2 commits June 7, 2026 21:27
Sequenced plan (P0 rename CH table -> P1 migrate scalar readers to ClickHouse
-> P2 IMU debug pages to R2 coverage -> P3 drop fitness.metric_stream + retire
PeerDB mirror + delete backfill script). Reader inventory, naming decision, and
per-phase steps + validation gates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the heart-rate router off `fitness.metric_stream` (Postgres, now frozen)
onto the Redpanda-fed ClickHouse mirror. Extract the query into a
HeartRateRepository (server convention: no raw SQL in routers) that reads via
the ActivitySensorStore `query()` escape hatch.

Rows are version-deduplicated (`FINAL` + `_peerdb_is_deleted = 0`) but NOT
collapsed by provider priority, so every source is still returned for the
per-source overlay. Day window uses `toDate(recorded_at, timezone)`; minute bins
formatted to ISO-8601 Z to preserve the existing client contract.

Tests: repository unit test (grouping), router unit test (sensorStore wiring +
empty-when-unavailable), and a ClickHouse integration test asserting per-source
separation, version dedup (v1 supersedes v0), and exclusion of deleted /
other-day / zero / other-channel rows.

Part of docs/metric-stream-postgres-retirement.md (P1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 8, 2026 05:10
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai

sourcery-ai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Migrates the heart-rate dailyBySource reader from Postgres fitness.metric_stream to the Redpanda-fed ClickHouse mirror via a new HeartRateRepository, adds ClickHouse-backed integration coverage, and documents the full Postgres metric_stream retirement plan.

Sequence diagram for heartRateRouter dailyBySource using ClickHouse

sequenceDiagram
  actor User
  participant HeartRateRouter
  participant HeartRateRepository
  participant ActivitySensorStore as MetricStreamClickHouseReader
  participant ClickHouseMetricStream as postgres_fitness_metric_stream

  User->>HeartRateRouter: dailyBySource({ date })
  alt sensorStore unavailable
    HeartRateRouter-->>User: []
  else sensorStore available
    HeartRateRouter->>HeartRateRepository: new HeartRateRepository(sensorStore, userId, timezone)
    HeartRateRouter->>HeartRateRepository: dailyBySource(date)
    HeartRateRepository->>ActivitySensorStore: query(heartRateRowSchema, SELECT ... FROM postgres_fitness.metric_stream ..., params)
    ActivitySensorStore->>ClickHouseMetricStream: execute SELECT
    ClickHouseMetricStream-->>ActivitySensorStore: rows
    ActivitySensorStore-->>HeartRateRepository: parsed rows
    HeartRateRepository-->>HeartRateRouter: HeartRateSourceSeries[]
    HeartRateRouter-->>User: HeartRateSourceSeries[]
  end
Loading

File-Level Changes

Change Details Files
Route heart-rate dailyBySource API from Postgres to ClickHouse through a repository abstraction
  • Replace inline Postgres SQL in the heart-rate router with a HeartRateRepository call that uses ctx.sensorStore as a ClickHouse-backed reader
  • Preserve the existing HeartRateSourceSeries type export and dailyBySource procedure shape so clients remain compatible
  • Update router unit tests to construct callers with a mock sensorStore and to assert behavior when ClickHouse is unavailable
packages/server/src/routers/heart-rate.ts
packages/server/src/routers/heart-rate.test.ts
Introduce HeartRateRepository to encapsulate ClickHouse metric-stream reads and per-source aggregation
  • Define a MetricStreamClickHouseReader interface that abstracts the minimal query(surface) satisfied by ActivitySensorStore
  • Implement HeartRateRepository.dailyBySource to read from postgres_fitness.metric_stream with FINAL and _peerdb_is_deleted=0, apply timezone-local day filtering, minute bucketing, and average scalar aggregation, and group results by provider with labels
  • Use ClickHouse formatDateTime to emit ISO-8601 Z timestamps that match the previous API contract
packages/server/src/repositories/heart-rate-repository.ts
Add real ClickHouse integration test coverage for heart-rate repository behavior
  • Bootstrap the native metric-stream ClickHouse table with existing helper statements and clean test user data before/after tests
  • Seed ClickHouse with rows that exercise per-source separation, version deduplication, and exclusion of deleted, zero, other-day, and other-channel rows
  • Implement a MetricStreamClickHouseReader adapter over the real ClickHouse client and assert that HeartRateRepository.dailyBySource returns correctly deduped and filtered per-provider minute samples
packages/server/src/repositories/heart-rate-repository.integration.test.ts
Document the full multi-phase plan to retire the Postgres fitness.metric_stream table
  • Describe current data flow (writers to Redpanda, ClickHouse sink, R2 archive, and existing mirrors) and why Postgres metric_stream is frozen
  • Define the target ClickHouse table rename to metric_stream.events and how it interacts with dbt models and the sink
  • Lay out phased work (P0–P3) for renaming, migrating remaining readers, moving IMU debug pages to R2 coverage, and finally dropping the Postgres table and PeerDB mirror
docs/metric-stream-postgres-retirement.md

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

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 Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the heart-rate scalar reader endpoint from inline Postgres/Drizzle SQL to a ClickHouse-backed repository pattern, adds a runbook for Postgres metric_stream retirement, implements HeartRateRepository with unit and integration tests, and refactors router and router tests to use the repository.

Changes

Heart-rate reader ClickHouse migration

Layer / File(s) Summary
Postgres metric_stream retirement runbook
docs/metric-stream-postgres-retirement.md
Runbook documenting current state (Postgres frozen, ClickHouse serving, PeerDB mirror, R2 archive), ClickHouse naming (metric_stream.events), and P0–P3 migration plan with validation gates requiring real Postgres+ClickHouse integration tests.
HeartRateRepository implementation
packages/server/src/repositories/heart-rate-repository.ts
New MetricStreamClickHouseReader interface, heartRateRowSchema, exported HeartRateSourceSeries, and HeartRateRepository implementing dailyBySource(date) to query ClickHouse, downsample to 1-minute averages, filter, and group by provider_id.
Repository unit tests
packages/server/src/repositories/heart-rate-repository.test.ts
Vitest tests verifying grouping by providerId, correct parameter forwarding to the query, and empty-array handling.
Repository integration test
packages/server/src/repositories/heart-rate-repository.integration.test.ts
Integration test that bootstraps ClickHouse, seeds Postgres metric_stream rows (multi-provider, versioned duplicate, excluded variants), asserts per-provider minute series and version deduplication, and cleans up.
Router migration to repository pattern
packages/server/src/routers/heart-rate.ts
Refactor dailyBySource to use HeartRateRepository from ctx.sensorStore/ctx.userId/ctx.timezone, re-export HeartRateSourceSeries type, and return [] when store missing.
Router test updates
packages/server/src/routers/heart-rate.test.ts
Tests now mock sensorStore in tRPC context, clear mocks with vi.clearAllMocks(), simplify assertions to series/sample counts, and make the empty-case assert ClickHouse-unavailable.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant HeartRateRepository
  participant ClickHouse
  Client->>Router: POST /heart-rate.dailyBySource {date}
  Router->>HeartRateRepository: dailyBySource(date, userId, timezone)
  HeartRateRepository->>ClickHouse: query(metric_stream.events, params)
  ClickHouse-->>HeartRateRepository: rows
  HeartRateRepository-->>Router: HeartRateSourceSeries[]
  Router-->>Client: 200 OK {series}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Asherlc/dofek#1206: The HeartRateRepository and tests rely on PeerDB metadata columns (e.g., _peerdb_version); that PR ensures those columns exist during ClickHouse bootstrap/migrations.

Suggested labels

area/server, type/feature

Suggested reviewers

  • cubic-dev-ai
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title is in imperative mood, 68 characters (under 70-char limit), uses relevant [server] area prefix (implied by context), contains no trailing punctuation, and directly describes the main change: migrating heart-rate reads from Postgres to ClickHouse as part of P1 retirement work.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Storybook previews for d89d2ba3 are ready:

This comment updates automatically on each PR push.

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="packages/server/src/repositories/heart-rate-repository.ts" line_range="54" />
<code_context>
+      heartRateRowSchema,
+      `SELECT
+          provider_id,
+          formatDateTime(minute_bucket, '%Y-%m-%dT%H:%i:%SZ') AS recorded_at,
+          toInt32(round(avg(scalar))) AS heart_rate
+        FROM (
</code_context>
<issue_to_address>
**issue (bug_risk):** The recorded_at format omits milliseconds but tests and schemas expect millisecond precision.

Here `recorded_at` is formatted without milliseconds, but tests and `timestampStringSchema` expect millisecond precision (e.g. `2026-04-12T10:00:00.000Z`). This mismatch will cause test failures and potential client parsing differences. Please switch to a millisecond-precise format (e.g. `'%Y-%m-%dT%H:%i:%s.000Z'`) or use a native `DateTime64` and let the client/schema handle ISO stringification consistently.
</issue_to_address>

### Comment 2
<location path="docs/metric-stream-postgres-retirement.md" line_range="102" />
<code_context>
+
+## P3 — Drop Postgres metric_stream + cleanup
+
+Only after P0–P2 merged + verified, and the historical backfill is complete.
+
+1. Retire PeerDB metric_stream CDC mirror (so CH is fed only by Redpanda).
</code_context>
<issue_to_address>
**suggestion (typo):** Slightly awkward grammar; consider adding a verb after “P0–P2”.

For example: `Only after P0–P2 are merged and verified, and the historical backfill is complete.`

```suggestion
Only after P0–P2 are merged and verified, and the historical backfill is complete.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/server/src/repositories/heart-rate-repository.ts Outdated
Comment thread docs/metric-stream-postgres-retirement.md 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: 2

🤖 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/heart-rate-repository.integration.test.ts`:
- Around line 97-106: The test expectations include timestamps with millisecond
precision (".000") whereas the repository's formatDateTime function emits
timestamps without milliseconds, causing failures; either update formatDateTime
to include milliseconds (e.g., adjust its format string to produce ".SSS") or
remove the ".000" from the expected timestamps in the test (see the whoop_ble
expectation array and formatDateTime) so both formats match.

In `@packages/server/src/repositories/heart-rate-repository.ts`:
- Line 54: The SQL timestamp formatting in the query uses
formatDateTime(minute_bucket, '%Y-%m-%dT%H:%i:%SZ') AS recorded_at which omits
milliseconds and conflicts with tests that expect a '.000Z' suffix; update that
format string to include fractional seconds (e.g., '%Y-%m-%dT%H:%i:%S.%fZ') so
recorded_at includes milliseconds, or alternatively update the integration test
expectations to remove the '.000' if milliseconds are not desired.
🪄 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: ef9a725e-3c75-43aa-8b60-fd8307df2399

📥 Commits

Reviewing files that changed from the base of the PR and between 0165722 and f038e68.

📒 Files selected for processing (6)
  • docs/metric-stream-postgres-retirement.md
  • packages/server/src/repositories/heart-rate-repository.integration.test.ts
  • packages/server/src/repositories/heart-rate-repository.test.ts
  • packages/server/src/repositories/heart-rate-repository.ts
  • packages/server/src/routers/heart-rate.test.ts
  • packages/server/src/routers/heart-rate.ts

Comment thread packages/server/src/repositories/heart-rate-repository.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 6 files

Confidence score: 3/5

  • There is a concrete correctness risk in packages/server/src/routers/heart-rate.ts: treating a missing ClickHouse store as empty data can mask infrastructure failures and return misleading heart-rate results to users.
  • packages/server/src/repositories/heart-rate-repository.ts uses toDate(recorded_at) in the day filter, which can prevent key range pruning and cause heavier daily scans, creating noticeable performance risk under load.
  • Given a medium-severity user-facing behavior issue plus a query-efficiency concern, this carries some merge risk and is worth tightening before release.
  • Pay close attention to packages/server/src/routers/heart-rate.ts and packages/server/src/repositories/heart-rate-repository.ts - avoid silent fallback on store failures and restore efficient date-range filtering.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/src/routers/heart-rate.ts Outdated
Comment thread packages/server/src/repositories/heart-rate-repository.ts Outdated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

Re-trigger cubic

- Hardcode `.000` in the recorded_at format (`%Y-%m-%dT%H:%i:%S.000Z`) so the
  ISO-8601 contract is explicit and version-independent (minute buckets always
  have zero sub-seconds). Resolves the format/expectation ambiguity flagged by
  sourcery + coderabbit.
- Replace `toDate(recorded_at, tz) = date` with a raw-column range
  (`recorded_at >= toDateTime(date, tz) AND < + INTERVAL 1 DAY`) so the
  ORDER BY key can prune instead of scanning (cubic perf finding).
- Docs grammar fix in the retirement plan.

Integration + unit tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Asherlc

Asherlc commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

CR responses (ae39b87a)

Fixed

  • Timestamp ms format (sourcery + coderabbit ×2): recorded_at format now hardcodes .000 (%Y-%m-%dT%H:%i:%S.000Z). Minute buckets always have zero sub-seconds, so it's exact and version-independent. (Used hardcoded .000 rather than %f, which emits 6 digits.) Verified by the real-ClickHouse integration test.
  • toDate() blocks key pruning (cubic): replaced with a raw-column range — recorded_at >= toDateTime(date, tz) AND recorded_at < toDateTime(date, tz) + INTERVAL 1 DAY — so the ORDER BY key prunes.
  • Docs grammar (sourcery).

Declined

  • Missing sensorStore → [] (cubic): matches the convention every ClickHouse-backed router uses. createApp always constructs the sensor store in production, so this branch is only hit in tests/optional contexts; a real ClickHouse failure throws from query() and is handled by the tRPC error path + reported to Sentry — not silently swallowed.

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 2 files (changes from recent commits).

Auto-approved: The PR migrates the heart-rate dailyBySource reader from Postgres to ClickHouse with a well-structured repository, comprehensive tests (unit, router wiring, and real-ClickHouse integration), and the same API contract, making it low-risk and safe to auto-approve.

Re-trigger cubic

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Requires human review: This PR migrates heart-rate reads from Postgres to ClickHouse, a significant refactor of core fitness data retrieval that requires careful human review to verify query correctness, dedup logic, and client contract preservation.

Re-trigger cubic

…nal deps

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Requires human review: Migrating heart-rate dailyBySource from Postgres to ClickHouse changes a core production data path with potential for data freshness and correctness issues, requiring human review of the SQL, deduplication logic, and timezone handling.

Re-trigger cubic

… branch

Per the new "no test/optional-only branches" rule: the heart-rate router's
`if (!ctx.sensorStore) return []` guard was dead in production (createApp always
constructs the sensor store) and only reachable in tests, where it silently
returned empty data instead of surfacing a real ClickHouse failure.

- `Context.sensorStore` and `createApp`'s `sensorStore` param are now required
  (non-optional). createApp always wraps it in LimitedActivitySensorStore.
- heart-rate router calls the repository directly with `ctx.sensorStore`; guard
  removed, and the "returns [] when unavailable" test deleted.
- Integration-test callers that previously omitted the store
  (whoop-auth, food, settings, health-kit-sync) now pass `makeMockSensorStore()`.

A real ClickHouse failure now throws from `query()` and is handled by the tRPC
error path + reported to Sentry, instead of being masked as empty data.

(The ~14 other routers still carry the same guard; they're now dead branches on
a required field and will be removed in a follow-up sweep.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 8 files (changes from recent commits).

Auto-approved: This PR migrates the heart-rate dailyBySource reader from Postgres to ClickHouse with a clean repository pattern, comprehensive tests (unit, router wiring, real-ClickHouse integration), and no changes to the client API contract, while also hardening the dependency by making the sensor store...

Re-trigger cubic

@Asherlc
Asherlc enabled auto-merge (squash) June 8, 2026 14:44
@Asherlc
Asherlc merged commit c592a9a into main Jun 8, 2026
66 checks passed
@Asherlc
Asherlc deleted the Asherlc/retire-pg-metric-stream branch June 8, 2026 14:50
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.

2 participants