Skip to content

Remove ClickHouse full refresh read models - #1174

Merged
Asherlc merged 42 commits into
mainfrom
Asherlc/strong-csv-no-records
May 25, 2026
Merged

Asherlc merged 42 commits into
mainfrom
Asherlc/strong-csv-no-records

Conversation

@Asherlc

@Asherlc Asherlc commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • Removes full-refresh ClickHouse read models by converting non-sensor read models to normal views and replacing analytics.deduped_sensor with an incremental dirty-key pipeline.
  • Updates post-sync processing to drain sensor dirty keys, adds the sensor-priority PeerDB mirror, and rewires activity sensor queries to derive activity membership from ClickHouse activity windows.
  • Updates integration helpers, tests, and docs/runbooks to enforce that active ClickHouse analytics no longer use REFRESH EVERY or manual refresh commands.

Testing

  • Skipped per user request.

Summary by Sourcery

Replace ClickHouse full-refresh analytics read models with incremental pipelines and standard views, and rewire activity analytics to consume activity-windowed sensor data without relying on REFRESH EVERY semantics.

New Features:

  • Introduce incremental dirty-key–driven pipeline for analytics.deduped_sensor backed by sensor_scalar_sample and sensor_dirty_key tables.
  • Add PeerDB mirrors for sensor provider and device priority tables to drive sensor deduplication in ClickHouse.

Enhancements:

  • Convert non-sensor ClickHouse analytics read models (activity, sleep, body, daily metrics, provider stats, activity summary, trends, resting HR windows, deduped_location) from refreshable materialized views into normal views.
  • Update ClickHouse migrations, bootstrap SQL, and integration test helpers to build analytics views without REFRESH EVERY or SYSTEM REFRESH VIEW commands and to support incremental sensor recomputation.
  • Adjust activity sensor, training, efficiency, cycling, VO2, PMC, intervals, and healthspan queries to join deduped sensor samples to activities by user/time windows and respect is_deleted flags.
  • Extend post-sync processing to drain ClickHouse sensor dirty keys before body measurement refresh and personalization refits, with robust error reporting and bounds on backlog draining.
  • Tighten ClickHouse integration tests and CDC setup to account for new mirrors, tables, and view semantics while ensuring analytics tables are rebuilt deterministically in tests.

Documentation:

  • Revise ClickHouse metric stream, schema, and body-measurement staleness runbooks plus production-incident baseline docs to describe the new incremental deduped sensor pipeline, standard views, and removal of full-refresh read models.

Tests:

  • Update ClickHouse migration, bootstrap, CDC, integration helper, worker, post-sync, and activity sensor store tests to validate the new incremental pipeline, non-refreshable views, and analytics query shapes.

Summary by CodeRabbit

  • New Features

    • Incremental deduped-sensor pipeline with background backlog draining for more responsive analytics.
  • Bug Fixes

    • Activity sensor associations now use time-window matching and ignore deleted samples, improving analytics accuracy.
    • Removed disruptive full-refresh commands to reduce stale UI and host-saturating refreshes.
    • Charts now show empty message (not a loading skeleton) when data is empty after load.
  • Refactor

    • Read-models migrated from refreshable materialized flows to simpler incremental/views for safer backfills.
  • Documentation

    • Updated runbook, architecture docs, incident baseline, and schema descriptions.
  • Tests

    • Expanded coverage for dedupe/backlog processing, migrations, and analytics endpoints.

Review Change Stack

Copilot AI review requested due to automatic review settings May 23, 2026 05:19
@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 May 23, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Replaces ClickHouse full-refresh materialized read models with a combination of standard views, incremental tables, and a dirty-key pipeline for sensor data, rewires all analytics queries to be activity-window based rather than activity_id-based, and updates migrations, tests, CDC setup, jobs, and docs to match the new design and forbid REFRESH-based workflows.

Entity relationship diagram for incremental deduped sensor tables

erDiagram
  sensor_scalar_sample {
    UUID id
    UUID user_id
    DateTime64 recorded_at
    String channel
    Float scalar
    UInt16 provider_priority
    Int8 _peerdb_is_deleted
  }

  sensor_dirty_key {
    UUID user_id
    String channel
    DateTime64 recorded_at
    DateTime64 min_peerdb_synced_at
    DateTime64 max_peerdb_synced_at
    DateTime64 processed_at
    UInt64 dirty_version
  }

  deduped_sensor {
    UUID user_id
    DateTime64 recorded_at
    String channel
    Float scalar
    String provider_id
    UUID source_metric_stream_id
    UInt16 provider_priority
    UInt8 is_deleted
    UInt64 refresh_version
  }

  sensor_scalar_sample ||--o{ sensor_dirty_key : queues_changes
  sensor_scalar_sample ||--o{ deduped_sensor : recomputes_best_sample
  sensor_dirty_key ||--o{ deduped_sensor : drives_incremental_refresh
Loading

File-Level Changes

Change Details Files
Introduce incremental dirty-key based pipeline for deduped sensor data and supporting tables/views.
  • Add analytics.sensor_scalar_sample ReplacingMergeTree projection with ingest and backfill SQL derived from postgres_fitness.metric_stream and sensor priority tables.
  • Add analytics.sensor_dirty_key ReplacingMergeTree table and ingest materialized view to track changed (user_id, channel, recorded_at) keys and their sync windows.
  • Redefine analytics.deduped_sensor as an activity-agnostic ReplacingMergeTree table keyed by (user_id, channel, recorded_date, recorded_at) with provider priority metadata and is_deleted flag.
  • Implement processDedupedSensorDirtyKeys helper that recomputes deduped_sensor rows for pending dirty keys in bounded batches and marks sensor_dirty_key rows as processed.
  • Wire processDedupedSensorDirtyKeys into migrations (bootstrap and migration 0020) and expose buildIncrementalDedupedSensorStatements/migration helpers.
src/db/clickhouse-deduped-sensor.ts
src/db/clickhouse-metric-stream-bootstrap.ts
src/db/clickhouse-migrations.ts
src/db/clickhouse-migrations.test.ts
src/db/clickhouse.test.ts
docs/clickhouse-metric-stream.md
docs/clickhouse-body-measurement-staleness-runbook.md
docs/schema.md
Convert non-sensor ClickHouse read models from refreshable materialized views to standard views and remove REFRESH EVERY / SYSTEM REFRESH usage.
  • Replace refreshableMergeTreeViewHeader with standardViewHeader and update all read model builders to emit CREATE VIEW AS instead of CREATE MATERIALIZED VIEW ... REFRESH EVERY.
  • Remove SYSTEM REFRESH VIEW, SYSTEM WAIT VIEW, and MODIFY REFRESH statements from bootstrap, migrations, and runtime refresh helpers, including post-sync and manual refresh code paths.
  • Add migration 0019 to drop legacy materialized tables/views and recreate them as views using updated builders; adjust tests to assert absence of REFRESH-related SQL and presence of CREATE VIEW statements.
  • Update ClickHouse read model refresh helper to be a no-op for v_body_measurement and revise tests accordingly.
src/db/clickhouse-sql-helpers.ts
src/db/clickhouse-read-models.ts
src/db/clickhouse-metric-stream-bootstrap.ts
src/db/clickhouse-migrations.ts
src/db/clickhouse-migrations.test.ts
src/db/clickhouse-read-model-refresh.ts
src/db/clickhouse-read-model-refresh.test.ts
src/db/clickhouse-resting-heart-rate-materialized-view.ts
src/db/clickhouse.test.ts
Rewire all analytics query code to treat deduped_sensor as activity-agnostic and join by activity time windows, enforcing is_deleted filtering.
  • Update repositories and query helpers (activity sensor analytics, cycling advanced, efficiency, training, PMC, intervals, VO2 max, healthspan, current-strain, personalization refit) to join deduped_sensor to analytics.v_activity or activity_summary via user_id and time bounds instead of activity_id, and to filter samples by is_deleted = 0.
  • Adjust ClickHouseActivitySensorStore queries and helpers to pass activity time windows, drop activity_id predicates, and add is_deleted checks and improved downsampling logic.
  • Modify resting_heart_rate_sleep_window view to exclude samples that overlap any activity window and to read from the activity-agnostic deduped_sensor.
  • Update tests to reflect new join patterns and field usage (e.g., countIf(samples.channel=...), uniqExact(activity.id), and absence of activity_id in deduped_sensor schema).
packages/server/src/repositories/clickhouse-activity-sensor-analytics.ts
packages/server/src/repositories/clickhouse-activity-sensor-store.ts
packages/server/src/repositories/clickhouse-activity-sensor-store.test.ts
packages/server/src/repositories/cycling-advanced-repository.ts
packages/server/src/repositories/efficiency-repository.ts
packages/server/src/repositories/training-repository.ts
packages/server/src/repositories/pmc-repository.ts
packages/server/src/repositories/intervals-repository.ts
packages/server/src/routers/healthspan-query.ts
packages/server/src/lib/current-strain.ts
src/personalization/refit.ts
src/db/clickhouse-resting-heart-rate-materialized-view.ts
src/db/clickhouse.test.ts
Update post-sync job pipeline and worker wiring to drain sensor dirty keys as part of post-sync processing and adjust body measurement behavior.
  • Extend processPostSyncJob to accept a processSensorDirtyKeys callback, drain dirty keys in up to 1000 batches, log totals, and fail fast with Sentry reporting if the backlog cannot be drained or processing throws.
  • Ensure sensor dirty-key processing runs before body measurement refresh and personalization refit, and add tests for ordering, retry limits, and error handling.
  • Make ClickHouseActivitySensorStore.refreshBodyMeasurements a no-op at the SQL level, with corresponding test updates.
  • Wire processDedupedSensorDirtyKeys into worker post-sync processing by adding a processPostSyncSensorDirtyKeys helper that uses the shared ClickHouse client.
src/jobs/process-post-sync-job.ts
src/jobs/process-post-sync-job.test.ts
src/jobs/worker.ts
src/jobs/worker.test.ts
packages/server/src/repositories/clickhouse-activity-sensor-store.test.ts
Extend PeerDB CDC setup to mirror sensor priority tables into ClickHouse and adjust mirror reconciliation and tests.
  • Add dofek_sensor_priority_raw_analytics mirror for fitness.sensor_provider_priority and fitness.sensor_device_priority in metric-stream-cdc.sql with its own initial copy flag.
  • Include sensor priority tables in analyticsSourceTables and rawAnalyticsMirrorTableMappings, plus corresponding RawAnalyticsInitialCopyValues defaults.
  • Update PeerDB setup tests to expect the new mirror, additional queries, and reconciliation behavior, and verify that raw analytics mirror configs include sensor priority tables.
src/db/peerdb/metric-stream-cdc.sql
src/db/clickhouse-cdc.ts
src/db/clickhouse-cdc.test.ts
Adapt ClickHouse integration test harness to the new view-based analytics and incremental deduped sensor pipeline.
  • Change test SQL rewriter to capture CREATE VIEW definitions instead of refreshable materialized views, and introduce REBUILD TEST ANALYTICS TABLE commands that materialize views into physical tables for tests.
  • Replace SYSTEM REFRESH/WAIT VIEW usage in test helpers with the dirty-key processing loop plus REBUILD TEST ANALYTICS TABLE for each analytics view.
  • Update synthetic analytics table schemas in tests to match the new deduped_sensor layout and sensor priority fields, and adjust expectations around command counts and SELECT 1 placeholders.
packages/server/src/routers/clickhouse-integration-test-helpers.ts
packages/server/src/routers/clickhouse-integration-test-helpers.test.ts
Update documentation and runbooks to reflect the new incremental analytics architecture and remove references to full-refresh read models.
  • Document the new incremental sensor pipeline (sensor_scalar_sample, sensor_dirty_key, deduped_sensor) and the fact that analytics views are now normal views without REFRESH EVERY semantics.
  • Adjust staleness runbooks and incident entries to stop recommending SYSTEM REFRESH/STOP VIEW, instead instructing operators to inspect incremental pipeline health and dirty-key backlog.
  • Add new incident documentation describing Strong CSV import visibility issues and the removal of full-refresh read models as a mitigation, including remaining risks until deployment.
docs/clickhouse-metric-stream.md
docs/clickhouse-body-measurement-staleness-runbook.md
docs/schema.md
docs/production-incident-baseline.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

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Converts analytics from refreshable materialized views to an incremental deduped-sensor pipeline: new scalar ingestion + dirty-key ReplacingMergeTree, a batched recompute (processDedupedSensorDirtyKeys), convert read-models to CREATE VIEW, migrate consumers to user_id + recorded_at windows with is_deleted filtering, and wire post-sync draining and tests.

Changes

Deduped Sensor Infrastructure & Analytics Architecture

Layer / File(s) Summary
Deduped sensor SQL builders and dirty-key processing
src/db/clickhouse-deduped-sensor.ts, src/db/clickhouse-deduped-sensor.test.ts
New scalar-channel allowlist, Zod schemas, SQL builders for analytics.sensor_scalar_sample, analytics.deduped_sensor, analytics.sensor_dirty_key; exports processDedupedSensorDirtyKeys() that snapshots pending keys, runs recompute INSERTs, marks processed keys, and returns processed count.
SQL helpers / view header
src/db/clickhouse-sql-helpers.ts, src/db/clickhouse-read-models.ts
Replaces refreshable/materialized view header with standardViewHeader() and emits CREATE VIEW IF NOT EXISTS ... for analytics read models; removes REFRESH scheduling and associated refresh/wait statements.
Bootstrap & migrations
src/db/clickhouse-metric-stream-bootstrap.ts, src/db/clickhouse-migrations.ts
Injects incremental deduped-sensor statements into bootstrap, removes inlined deduped_sensor MV/refresh wiring, adds migrations 0019_non_sensor_read_models_as_views & 0020_incremental_deduped_sensor, and updates migration dependency/wait logic.
Read-model conversion (materialized → standard views)
src/db/clickhouse-read-models.ts, src/db/clickhouse-resting-heart-rate-materialized-view.ts
Converts activity/trend/body/sleep/provider read-models to standard views, removes SYSTEM REFRESH VIEW / SYSTEM WAIT VIEW sequences, and changes resting HR selection to use LEFT JOIN activity-window exclusion.
PeerDB CDC: sensor-priority mirror
src/db/clickhouse-cdc.ts, src/db/peerdb/metric-stream-cdc.sql, src/db/clickhouse-cdc.test.ts
Adds dofek_sensor_priority_raw_analytics mirror for fitness.sensor_provider_priority and fitness.sensor_device_priority and a template replacement flag; tests adjusted to expect new mirror creation.
Activity sensor store & consumers
packages/server/src/repositories/clickhouse-activity-sensor-store.ts, packages/server/src/repositories/*
Adds windowStartedAt/windowEndedAt, constrains deduped_sensor.recorded_at to activity windows (with 12-hour fallback), enforces is_deleted = 0, rewrites joins to user_id+time-window across many repositories, and replaces some downsampling logic.
Repository query rewrites
packages/server/src/repositories/*, src/personalization/refit.ts
Rewrites power/normalized-power/VO2/efficiency/intervals/pmc/training/cycling-advanced/healthspan queries to associate samples by user_id + recorded_at windows and add is_deleted + channel constraints.
Post-sync job & worker wiring
src/jobs/process-post-sync-job.ts, src/jobs/worker.ts, tests
Injects processSensorDirtyKeys into processPostSyncJob, drains dirty-key backlog in batches up to maxSensorDirtyKeyBatches = 1000, logs/reports Sentry postSyncStep on error, and wires helper via worker.
Integration test helpers
packages/server/src/routers/clickhouse-integration-test-helpers.ts
Parses CREATE VIEW IF NOT EXISTS ... AS ..., captures SELECT for REBUILD TEST ANALYTICS TABLE, drains dirty-key backlog during sync, and updates test schema expectations for deduped_sensor.
Tests & migration tests
src/db/*.{test,ts}, packages/server/src/*test.ts
Updates tests to expect CREATE VIEW DDL, remove SYSTEM REFRESH/ALTER MODIFY REFRESH assertions, add dirty-key draining coverage, and adjust bootstrap/migration counts and SQL assertions.
Docs & runbooks
docs/clickhouse-metric-stream.md, docs/schema.md, docs/clickhouse-body-measurement-staleness-runbook.md, docs/production-incident-baseline.md
Documents incremental pipeline (sensor_scalar_sample → sensor_dirty_key → deduped_sensor → views), activity-agnostic dedupe keying (user_id, channel, recorded_at), verification steps for incremental progress, and incident baseline entries describing migration issues and fixes.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Asherlc/dofek#1141: changes related to computeCurrentStrain and HR/power selection—overlaps current-strain.ts filtering adjustments.
  • Asherlc/dofek#1161: earlier body-measurement refresh/read-model changes that this PR reverses/changes to a no-op and view-based builds.
  • Asherlc/dofek#1095: prior migration moving reads to ClickHouse; this PR continues that migration by changing consumer joins and removing refreshable views.

Suggested labels

area/server, area/db, type/feature, type/refactor, breaking-change

@github-actions

github-actions Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for 6bf922cb 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 3 issues, and left some high level feedback:

  • The new activity-window join pattern on analytics.deduped_sensor (user_id + [started_at, ended_at/12h] range) is duplicated across many queries (power curves, VO2max, efficiency, PMC, intervals, etc.); consider extracting a shared helper or view to reduce the risk of subtle divergence in those predicates over time.
  • The maximum dirty-key batch limits for sensor recompute (maxSensorDirtyKeyBatches in process-post-sync-job and clickHouseTestSensorDirtyKeyMaxBatches in the integration helpers) are both hard-coded to 1000; it may be safer to centralize these thresholds or derive the test limit from the production constant to keep behavior aligned.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new activity-window join pattern on `analytics.deduped_sensor` (user_id + [started_at, ended_at/12h] range) is duplicated across many queries (power curves, VO2max, efficiency, PMC, intervals, etc.); consider extracting a shared helper or view to reduce the risk of subtle divergence in those predicates over time.
- The maximum dirty-key batch limits for sensor recompute (`maxSensorDirtyKeyBatches` in `process-post-sync-job` and `clickHouseTestSensorDirtyKeyMaxBatches` in the integration helpers) are both hard-coded to 1000; it may be safer to centralize these thresholds or derive the test limit from the production constant to keep behavior aligned.

## Individual Comments

### Comment 1
<location path="src/db/clickhouse-deduped-sensor.ts" line_range="238-247" />
<code_context>
+  FROM analytics.sensor_scalar_sample FINAL`);
+}
+
+function buildPendingDirtyKeySql(limit: number): string {
+  return `SELECT
+    user_id,
+    channel,
+    recorded_at,
+    min(min_peerdb_synced_at) AS min_peerdb_synced_at,
+    max(max_peerdb_synced_at) AS max_peerdb_synced_at,
+    max(dirty_version) AS max_dirty_version
+  FROM analytics.sensor_dirty_key
+  GROUP BY user_id, channel, recorded_at
+  HAVING maxIf(dirty_version, processed_at IS NULL) > maxIf(dirty_version, processed_at IS NOT NULL)
+  ORDER BY max_peerdb_synced_at ASC
+  LIMIT ${limit}`;
</code_context>
<issue_to_address>
**issue (bug_risk):** Dirty-key selection and marking logic can miss updates due to a race between the two INSERTs

Using the same `pendingKeySql` in two separate INSERTs creates a race:

1. You compute `pendingKeySql` and use it to recompute `analytics.deduped_sensor`.
2. Before the second `INSERT INTO analytics.sensor_dirty_key ... SELECT ... FROM (pendingKeySql)` runs, new `sensor_dirty_key` rows can arrive for keys in that batch.
3. In the second INSERT, `max_dirty_version` now includes these newer rows, so the HAVING clause treats them as processed, even though the recompute in (1) never saw their `dirty_version`.

To fix this, either:
- Constrain the marking step to `dirty_version <= max_dirty_version` as of the first query, or
- Materialize the pending keys (e.g., temp table / `dirty_batch_id`) and drive both recompute and marking from that snapshot.

As written, concurrent ingestion can cause updates to be marked processed without being applied to `deduped_sensor`.
</issue_to_address>

### Comment 2
<location path="src/db/clickhouse-deduped-sensor.ts" line_range="308-309" />
<code_context>
         "analytics.activity_trend_daily",
       ]) {
         await client.command({
-          query: `CREATE MATERIALIZED VIEW IF NOT EXISTS ${viewName}
-REFRESH EVERY 1 MINUTE
</code_context>
<issue_to_address>
**suggestion (performance):** Recomputing deduped rows scans the full sensor_scalar_sample table on every batch, which will be expensive at scale

In `buildDedupedSensorRecomputeInsertSql`, this pattern:

```sql
LEFT JOIN (
  SELECT *
  FROM analytics.sensor_scalar_sample FINAL
) AS samples
  ON samples.user_id = pending_keys.user_id
 AND samples.channel = pending_keys.channel
 AND samples.recorded_at = pending_keys.recorded_at
```

applies `FINAL` to the entire `sensor_scalar_sample` table without a predicate, so each dirty-key batch forces a full-table read and collapse.

To avoid that, either:

```sql
LEFT JOIN (
  SELECT *
  FROM analytics.sensor_scalar_sample FINAL
  WHERE (user_id, channel, recorded_at) IN (
    SELECT user_id, channel, recorded_at FROM pending_keys
  )
) AS samples ON ...
```

or join directly to `analytics.sensor_scalar_sample FINAL` (without `SELECT * FROM (...)`) and let ClickHouse use the join keys for index pruning. The current subquery shape will likely be a major bottleneck as the table grows.
</issue_to_address>

### Comment 3
<location path="packages/server/src/routers/clickhouse-integration-test-helpers.test.ts" line_range="172" />
<code_context>
       ),
     ).toBe(true);
-    expect(commands.filter((command) => command === "SELECT 1")).toHaveLength(11);
+    expect(commands.filter((command) => command === "SELECT 1")).toHaveLength(0);
   });
 });
</code_context>
<issue_to_address>
**suggestion (testing):** Integration helpers tests don’t exercise the new REBUILD/dirty-key behavior

The expectations here correctly reflect that we no longer issue `SYSTEM REFRESH/WAIT VIEW` or emit the `SELECT 1` stub, but the new behaviors in `clickhouse-integration-test-helpers.ts` are not exercised.

Please add integration tests that:
- Cover `rewriteClickHouseTestCommand` rewriting `CREATE VIEW IF NOT EXISTS analytics.foo` into a table + precomputed SELECT, and `REBUILD TEST ANALYTICS TABLE analytics.foo` into `TRUNCATE` + `INSERT` using that SELECT.
- Cover `syncClickHouseTestActivitySensorStoreWithClient` calling `processDedupedSensorDirtyKeys` in a loop until it returns 0, and throwing when the test max‑batch limit is exceeded.

This will keep the test harness aligned with the production incremental pipeline behavior.

Suggested implementation:

```typescript
    ).toBe(true);
    expect(commands.filter((command) => command === "SELECT 1")).toHaveLength(0);
  });

  describe("rewriteClickHouseTestCommand", () => {
    it("rewrites CREATE VIEW into test table + precomputed SELECT and REBUILD into TRUNCATE + INSERT", () => {
      const viewName = "analytics.foo";

      const createViewCommand = `CREATE VIEW IF NOT EXISTS ${viewName} AS SELECT col1, col2 FROM source_table`;
      const rewrittenCreate = rewriteClickHouseTestCommand(createViewCommand);

      // Creates test table instead of view
      expect(rewrittenCreate).toMatch(/CREATE TABLE IF NOT EXISTS analytics_test_foo/);
      // Uses a precomputed SELECT based on the original query
      expect(rewrittenCreate).toMatch(/SELECT col1, col2 FROM source_table/);

      const rebuildCommand = `REBUILD TEST ANALYTICS TABLE ${viewName}`;
      const rewrittenRebuild = rewriteClickHouseTestCommand(rebuildCommand);

      // REBUILD uses TRUNCATE + INSERT ... SELECT from the precomputed query
      expect(rewrittenRebuild).toMatch(/TRUNCATE TABLE analytics_test_foo/);
      expect(rewrittenRebuild).toMatch(/INSERT INTO analytics_test_foo/);
      expect(rewrittenRebuild).toMatch(/SELECT col1, col2 FROM source_table/);
    });
  });

  describe("syncClickHouseTestActivitySensorStoreWithClient", () => {
    it("calls processDedupedSensorDirtyKeys until it returns 0", async () => {
      const client: any = {
        processDedupedSensorDirtyKeys: jest
          .fn()
          .mockResolvedValueOnce(5) // first batch
          .mockResolvedValueOnce(2) // second batch
          .mockResolvedValueOnce(0), // done
      };

      await syncClickHouseTestActivitySensorStoreWithClient(client);

      expect(client.processDedupedSensorDirtyKeys).toHaveBeenCalledTimes(3);
    });

    it("throws when the test max-batch limit is exceeded", async () => {
      const client: any = {
        processDedupedSensorDirtyKeys: jest.fn().mockResolvedValue(1),
      };

      await expect(
        syncClickHouseTestActivitySensorStoreWithClient(client, { maxBatches: 5 }),
      ).rejects.toThrow(/max batch/i);
    });
  });
});

```

To fully implement the requested behavior, you will also need to:

1. Ensure the helpers are imported into this test file, e.g. at the top of `clickhouse-integration-test-helpers.test.ts`:
   - Add `rewriteClickHouseTestCommand` and `syncClickHouseTestActivitySensorStoreWithClient` to the named imports from `./clickhouse-integration-test-helpers` (or whatever the existing import path is).
   - Example (adjust to match existing import style):
   ```ts
   import {
     rewriteClickHouseTestCommand,
     syncClickHouseTestActivitySensorStoreWithClient,
   } from "./clickhouse-integration-test-helpers";
   ```

2. Align the tests with the actual implementation details:
   - If `rewriteClickHouseTestCommand` uses a different naming convention for the test table (e.g. `analytics_test_analytics_foo` or a different prefix), adjust the `toMatch` expectations accordingly.
   - If the precomputed selection is stored under a different name or structure, ensure the regex expectations match the real SQL that `rewriteClickHouseTestCommand` emits.

3. Confirm the `syncClickHouseTestActivitySensorStoreWithClient` API:
   - If it does **not** take an options object as the second parameter, or the option is named differently (e.g. `maxBatchesForTests`, `maxSyncBatches`), update the call in the second test and the corresponding `toThrow` expectation to match the real signature and error message.
   - If the function returns a value instead of throwing on exceeding the max-batch limit, adjust the assertion to `resolves` and assert on the return value instead of using `rejects.toThrow`.

4. If your project uses a different test runner or assertion library (e.g. `vitest` instead of `jest`), replace `jest.fn()` and the Jest-specific `expect(...).rejects` syntax with the equivalents from your configured test framework.
</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 src/db/clickhouse-deduped-sensor.ts
Comment thread src/db/clickhouse-deduped-sensor.ts
Comment thread packages/server/src/routers/clickhouse-integration-test-helpers.test.ts Outdated

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

New security issues found

Comment thread src/db/clickhouse-deduped-sensor.ts
@github-actions

Copy link
Copy Markdown
Contributor

Review app is ready:

This environment runs on a dedicated Hetzner server for PR #1174 and updates on each push.

@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 (2)
docs/schema.md (1)

170-170: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stale documentation contradicts the new architecture.

Line 170 describes the old activity-scoped deduplication strategy ("per (activity_id, channel), the provider with the most samples wins"), which directly contradicts the new activity-agnostic strategy documented in lines 74-78 and 127. The new strategy uses (user_id, channel, recorded_at) keys with explicit priority tables, not activity-scoped sample counts.

Remove this line or update it to reflect the new incremental dirty-key-driven deduplication pipeline.

Suggested fix

Remove the stale sentence or replace it with:

-Sensor sample dedup: per (activity_id, channel), the provider with the most samples wins. This ensures the most granular source (e.g., BLE at 50Hz vs API at 1Hz) is automatically preferred.
+Sensor sample dedup is described in the **sensor_sample** section above: `analytics.deduped_sensor` selects the best sample per `(user_id, channel, recorded_at)` using explicit provider and device priority, with activity association determined by time-window queries.
🤖 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 `@docs/schema.md` at line 170, The sentence claiming "Sensor sample dedup: per
(activity_id, channel), the provider with the most samples wins" is stale and
contradicts the new activity-agnostic deduplication; remove or replace that
sentence so the doc reflects the current strategy: describe the incremental
dirty-key-driven pipeline using (user_id, channel, recorded_at) keys and the
explicit provider priority tables (i.e., mention the activity-agnostic dedup
logic and priority tables instead of activity-scoped sample counts).
src/db/clickhouse-read-models.ts (1)

977-1019: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this module before adding more read-model builders.

This change pushes src/db/clickhouse-read-models.ts past the repo's 1000-line limit. Move the new SQL builders into a sibling module instead of growing this file further.

As per coding guidelines "Max 1000 lines per file: No TypeScript file should exceed 1000 lines. If a file is approaching that limit, proactively split it into smaller, focused modules before it grows further."

🤖 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 `@src/db/clickhouse-read-models.ts` around lines 977 - 1019, This file has
grown past the 1000-line limit due to the new SQL builder functions; extract the
newly added read-model builders (at least
buildProviderStatsCreateReadModelStatements,
buildProviderStatsReadModelStatements,
buildActivityTrendDailyCreateReadModelStatements,
buildActivityTrendDailyReadModelStatements,
buildAnalyticsFitnessReadModelStatements, and
buildBodyMeasurementReadModelStatements) into a new sibling module (e.g.,
clickhouse-read-models-helpers or clickhouse-read-models-extra), export them
there, and replace their original definitions with imports (or re-exports) in
the current module so existing callers keep working; ensure
buildAnalyticsFitnessReadModelStatements still composes the moved helpers by
importing buildActivityReadModelSql, buildActivityMembersReadModelSql,
buildSleepReadModelSql, buildBodyMeasurementReadModelSql,
buildDailyMetricsReadModelSql, and the provider-stats/provider-trend builders
from the new module and update any exports/tests accordingly.
🤖 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/clickhouse-activity-sensor-store.test.ts`:
- Around line 60-65: Remove the test case titled "does not refresh the body
measurement view" from the clickhouse-activity-sensor-store.test suite: delete
the it(...) block that uses makeStore(), calls store.refreshBodyMeasurements(),
and asserts expect(command).not.toHaveBeenCalled(); this test asserts absence of
removed behavior and should be dropped per guidelines (look for the it block
referencing makeStore, store.refreshBodyMeasurements, and command).

In `@packages/server/src/repositories/clickhouse-activity-sensor-store.ts`:
- Around line 51-52: The code currently sets windowEndedAt to new
Date().toISOString() when window.endedAt is missing, which causes unbounded
scans; update the fallback used in clickhouse-activity-sensor-store.ts so
windowEndedAt is capped (e.g. use the same bounded fallback constant or helper
used elsewhere in this PR such as MAX_ACTIVITY_WINDOW or a
clampToMaxWindow(window.startedAt) helper) instead of wall-clock now, or
alternatively require the caller to supply an explicit end; adjust the
windowStartedAt/windowEndedAt assignment and any callers of getStream() or zone
query logic to use this capped end value.
- Around line 392-395: The downsampling divisor should use bucketized division
intDiv(total, maxPoints) instead of intDiv(total + maxPoints - 1, maxPoints)
because the latter collapses the maxPoints+1 case (e.g., total=501,
maxPoints=500) into 2; update the WHERE clause that currently reads "WHERE
row_number % greatest(1, intDiv(total + toUInt64({maxPoints:UInt32}) - 1,
toUInt64({maxPoints:UInt32}))) = 0" to use greatest(1, intDiv(total,
toUInt64({maxPoints:UInt32}))) so row selection preserves ~maxPoints entries,
and add a regression test in the clickhouse-activity-sensor-store tests that
asserts total = maxPoints + 1 returns roughly maxPoints rows.
- Around line 193-195: The SQL in the query that builds the JOIN with "INNER
JOIN analytics.v_activity AS activity" currently places non-equi predicates
(deduped_samples.recorded_at >= activity.started_at and
deduped_samples.recorded_at <= coalesce(activity.ended_at, activity.started_at +
INTERVAL 12 HOUR)) in the ON clause; change the JOIN to use only the equi-key
(activity.user_id = deduped_samples.user_id) and move the two
recorded_at/time-window predicates out of the ON into the query filter/windowing
logic (e.g., add them to the WHERE or implement a CROSS JOIN/windowing step that
enforces the activity window) so that the join remains an equi-join and the
time-range filtering happens after or as part of the windowing stage.

In `@packages/server/src/repositories/efficiency-repository.ts`:
- Around line 253-258: The LEFT JOIN in efficiency-repository.ts that uses
non-equi predicates on ds (the block using "LEFT JOIN analytics.deduped_sensor
ds ON ds.user_id = ea.user_id AND ds.recorded_at >= ea.started_at AND
ds.recorded_at <= coalesce(...)" ) must be replaced by a CTE/subquery that
pre-filters and aggregates sensor samples per activity_id using the time-range
and channel/is_deleted filters, then LEFT JOIN the aggregated result back to the
activities by equality on activity_id (or activity id + user_id) so zero-sample
activities are preserved; implement a new subquery/CTE (e.g.,
sensor_samples_by_activity) that SELECTs activity_id, COUNT(*) as sample_count
FROM analytics.deduped_sensor JOIN/WHERE against activity time bounds, channels
and is_deleted=0, GROUP BY activity_id, then LEFT JOIN that aggregated CTE to ea
using only equality predicates.

In `@packages/server/src/routers/clickhouse-integration-test-helpers.test.ts`:
- Around line 101-108: Remove the absence-only assertions that assert deleted
behavior is gone (the expect(...).toBe(false) checks that scan setupCommands for
"CREATE VIEW IF NOT EXISTS analytics_test_" and ".v_daily_metrics"); delete
these negative checks (both the one shown and the similar occurrence noted at
the other location) and ensure the test retains only positive assertions that
verify the expected current commands/outputs (i.e., keep or add explicit expects
for the commands we do want to see, using setupCommands and any existing helper
assertions).

In `@packages/server/src/routers/healthspan-query.ts`:
- Around line 147-153: The LEFT JOIN to analytics.deduped_sensor (alias ds) that
includes non-equi time predicates (ds.recorded_at >= am.started_at AND
ds.recorded_at <= coalesce(am.ended_at, am.started_at + INTERVAL 12 HOUR))
should be changed to an INNER JOIN so the non-equi predicates are evaluated
correctly; update the join in the query where ds is joined to am (and ensure the
GROUP BY on am.activity_id, am.duration_minutes, am.max_hr, am.ftp,
am.resting_hr remains unchanged) to use INNER JOIN analytics.deduped_sensor AS
ds with the same ON predicates.

In `@src/db/clickhouse-deduped-sensor.ts`:
- Around line 378-381: The dirty_version computation currently uses now64(9)
which can advance the watermark incorrectly; change the second argument of
greatest(...) to use the `_peerdb_synced_at` clock converted to the same UInt64
nanosecond timestamp type so the comparison stays on the `_peerdb_synced_at`
timeline. Specifically, replace the toUInt64(toUnixTimestamp64Nano(now64(9)))
part used in computing dirty_version (alongside pending_keys.max_dirty_version +
1) with an equivalent conversion of `_peerdb_synced_at` so dirty_version remains
aligned with the `_peerdb_synced_at` clock.

In `@src/db/clickhouse-migrations.test.ts`:
- Around line 45-48: Remove the "absence-only" assertions that check for legacy
refresh/materialized-view strings (e.g., expect(sql).not.toContain("REFRESH
EVERY"), expect(sql).not.toContain("SYSTEM REFRESH VIEW"),
expect(sql).not.toContain("SYSTEM WAIT VIEW") and any not.toHaveBeenCalledWith
checks) and keep only positive assertions that validate current expected output
(for example the existing expect(sql).toContain("CREATE TABLE IF NOT EXISTS
analytics.sensor_scalar_sample")); apply the same change to the other listed
ranges (72-79, 100-101, 226-232, 477-482, 588-602, 889-893) by removing
negated/refusal-style checks tied to removed refresh paths while retaining or
strengthening assertions that confirm the new SQL/behavior produced by the
functions under test.

In `@src/db/clickhouse-read-models.ts`:
- Around line 953-974: The query currently aggregates directly over the
analytics.v_activity × analytics.deduped_sensor join which double-counts a
sensor row if it joins to overlapping activities; introduce a sample-distinct
CTE (e.g., select distinct on the deduped sample identity: samples.user_id,
samples.recorded_at, samples.channel, samples.scalar or the unique samples.id if
present) and use that CTE (alias it e.g., distinct_samples) in place of
analytics.deduped_sensor in the main query so all sample-based metrics (avg_hr,
max_hr, avg_power, countIf*, total_samples, etc.) are computed from deduplicated
samples, while preserving activity_count (uniqExact(activity.id)) from the
activity side; update FROM/INNER JOIN to join activity to distinct_samples and
compute the CAST/avgIf/maxIf/countIf expressions against distinct_samples.

In `@src/db/clickhouse-resting-heart-rate-materialized-view.ts`:
- Around line 26-42: The LEFT JOIN is matching soft-deleted rows from
analytics.v_activity (alias activity) which causes deleted workouts to exclude
valid sleep heart-rate samples; fix by excluding deleted activities in the
join—either add a predicate filtering out activity.is_deleted (or the equivalent
soft-delete flag) inside the activity subquery or include AND
activity.is_deleted = 0 in the ON clause so samples, sleep_windows and the
activity alias behave correctly (ensure you reference activity.activity_id,
analytics.v_activity and samples when updating the join).

---

Outside diff comments:
In `@docs/schema.md`:
- Line 170: The sentence claiming "Sensor sample dedup: per (activity_id,
channel), the provider with the most samples wins" is stale and contradicts the
new activity-agnostic deduplication; remove or replace that sentence so the doc
reflects the current strategy: describe the incremental dirty-key-driven
pipeline using (user_id, channel, recorded_at) keys and the explicit provider
priority tables (i.e., mention the activity-agnostic dedup logic and priority
tables instead of activity-scoped sample counts).

In `@src/db/clickhouse-read-models.ts`:
- Around line 977-1019: This file has grown past the 1000-line limit due to the
new SQL builder functions; extract the newly added read-model builders (at least
buildProviderStatsCreateReadModelStatements,
buildProviderStatsReadModelStatements,
buildActivityTrendDailyCreateReadModelStatements,
buildActivityTrendDailyReadModelStatements,
buildAnalyticsFitnessReadModelStatements, and
buildBodyMeasurementReadModelStatements) into a new sibling module (e.g.,
clickhouse-read-models-helpers or clickhouse-read-models-extra), export them
there, and replace their original definitions with imports (or re-exports) in
the current module so existing callers keep working; ensure
buildAnalyticsFitnessReadModelStatements still composes the moved helpers by
importing buildActivityReadModelSql, buildActivityMembersReadModelSql,
buildSleepReadModelSql, buildBodyMeasurementReadModelSql,
buildDailyMetricsReadModelSql, and the provider-stats/provider-trend builders
from the new module and update any exports/tests accordingly.
🪄 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: 903ed73e-fd70-484d-a0fd-5cfa777c7ebd

📥 Commits

Reviewing files that changed from the base of the PR and between 83801b5 and 0810698.

📒 Files selected for processing (37)
  • docs/clickhouse-body-measurement-staleness-runbook.md
  • docs/clickhouse-metric-stream.md
  • docs/production-incident-baseline.md
  • docs/schema.md
  • packages/server/src/lib/current-strain.ts
  • packages/server/src/repositories/clickhouse-activity-sensor-analytics.ts
  • packages/server/src/repositories/clickhouse-activity-sensor-store.test.ts
  • packages/server/src/repositories/clickhouse-activity-sensor-store.ts
  • packages/server/src/repositories/cycling-advanced-repository.ts
  • packages/server/src/repositories/efficiency-repository.ts
  • packages/server/src/repositories/intervals-repository.ts
  • packages/server/src/repositories/pmc-repository.ts
  • packages/server/src/repositories/training-repository.ts
  • packages/server/src/routers/clickhouse-integration-test-helpers.test.ts
  • packages/server/src/routers/clickhouse-integration-test-helpers.ts
  • packages/server/src/routers/healthspan-query.ts
  • packages/server/src/routers/router-data.integration.test.ts
  • src/db/clickhouse-cdc.test.ts
  • src/db/clickhouse-cdc.ts
  • src/db/clickhouse-deduped-sensor.test.ts
  • src/db/clickhouse-deduped-sensor.ts
  • src/db/clickhouse-metric-stream-bootstrap.ts
  • src/db/clickhouse-migrations.test.ts
  • src/db/clickhouse-migrations.ts
  • src/db/clickhouse-read-model-refresh.test.ts
  • src/db/clickhouse-read-model-refresh.ts
  • src/db/clickhouse-read-models.ts
  • src/db/clickhouse-resting-heart-rate-materialized-view.ts
  • src/db/clickhouse-sql-helpers.ts
  • src/db/clickhouse.test.ts
  • src/db/clickhouse.ts
  • src/db/peerdb/metric-stream-cdc.sql
  • src/jobs/process-post-sync-job.test.ts
  • src/jobs/process-post-sync-job.ts
  • src/jobs/worker.test.ts
  • src/jobs/worker.ts
  • src/personalization/refit.ts

Comment thread packages/server/src/repositories/clickhouse-activity-sensor-store.test.ts Outdated
Comment thread packages/server/src/repositories/clickhouse-activity-sensor-store.ts Outdated
Comment thread packages/server/src/repositories/clickhouse-activity-sensor-store.ts Outdated
Comment thread packages/server/src/repositories/clickhouse-activity-sensor-store.ts Outdated
Comment thread packages/server/src/repositories/efficiency-repository.ts Outdated
Comment thread packages/server/src/routers/healthspan-query.ts Outdated
Comment thread src/db/clickhouse-deduped-sensor.ts Outdated
Comment thread src/db/clickhouse-migrations.test.ts Outdated
Comment thread src/db/clickhouse-read-models.ts Outdated
Comment thread src/db/clickhouse-resting-heart-rate-materialized-view.ts

@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: 1

🤖 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/training-repository.ts`:
- Around line 140-147: The short-circuit in getHrZones uses
this.#loadRawActivityCount(..., "AND ended_at IS NOT NULL") which excludes open
activities and causes an early return ({ maxHr: null, weeks: [] }) even when HR
samples exist; remove the "AND ended_at IS NOT NULL" filter from the
rawActivityCount call (i.e., call `#loadRawActivityCount` without that guard) so
open activities are counted consistently with the main query that uses
coalesce(am.ended_at, am.started_at + INTERVAL 12 HOUR), ensuring the
short-circuit no longer wrongly returns empty results when only in-progress
endurance activities exist.
🪄 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: b7e315b4-6a1f-4c4e-8462-5ca04bc7528a

📥 Commits

Reviewing files that changed from the base of the PR and between 9cee548 and 205c5f4.

📒 Files selected for processing (7)
  • docs/production-incident-baseline.md
  • packages/server/src/repositories/efficiency-repository.test.ts
  • packages/server/src/repositories/efficiency-repository.ts
  • packages/server/src/repositories/training-repository.test.ts
  • packages/server/src/repositories/training-repository.ts
  • packages/server/src/routers/efficiency.test.ts
  • packages/server/src/routers/training-access-window.test.ts

Comment thread packages/server/src/repositories/training-repository.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: 1

🤖 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/pmc-repository.ts`:
- Around line 90-104: The sample_counts CTE is unbounded because it only filters
by samples.user_id; update the CTE to also restrict samples.recorded_at to the
requested window (the same queryDays bounds used by the outer query) so the
aggregation is limited to the date range. Specifically, add the queryDays
start/end predicates (the same variables used elsewhere in this repo query
construction) to the sample_counts WHERE clause so samples.recorded_at is
between the window (or <= coalesce(activity.ended_at, activity.started_at +
INTERVAL 12 HOUR) AND >= queryDays.start), keeping the CTE scoped to the
requested time range.
🪄 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: 3321a625-0b05-4fe1-97da-73cd9e89e353

📥 Commits

Reviewing files that changed from the base of the PR and between 205c5f4 and d7d76f0.

📒 Files selected for processing (12)
  • docs/production-incident-baseline.md
  • packages/server/src/repositories/clickhouse-activity-sensor-analytics.ts
  • packages/server/src/repositories/clickhouse-activity-sensor-store.test.ts
  • packages/server/src/repositories/cycling-advanced-repository.test.ts
  • packages/server/src/repositories/cycling-advanced-repository.ts
  • packages/server/src/repositories/efficiency-repository.test.ts
  • packages/server/src/repositories/efficiency-repository.ts
  • packages/server/src/repositories/pmc-repository.test.ts
  • packages/server/src/repositories/pmc-repository.ts
  • packages/server/src/repositories/training-repository.test.ts
  • packages/server/src/repositories/training-repository.ts
  • packages/server/src/routers/training-access-window.test.ts

Comment thread packages/server/src/repositories/pmc-repository.ts

@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: 1

🤖 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/web/src/components/DofekChart.test.tsx`:
- Around line 169-170: Remove the negative absence assertion asserting the
removed loading skeleton: delete the line that calls
expect(screen.queryByTestId("loading-skeleton")).toBeNull() in the DofekChart
test so the test only keeps the positive empty-state assertion
(expect(screen.getByText("No data available")).toBeDefined()); this targets the
assertion in packages/web/src/components/DofekChart.test.tsx that references
test id "loading-skeleton".
🪄 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: 054c7f0e-35d6-405b-a6f9-d7b9bdd808f2

📥 Commits

Reviewing files that changed from the base of the PR and between d7d76f0 and 6859383.

📒 Files selected for processing (5)
  • docs/production-incident-baseline.md
  • packages/server/src/repositories/pmc-repository.test.ts
  • packages/server/src/repositories/pmc-repository.ts
  • packages/web/src/components/DofekChart.test.tsx
  • packages/web/src/components/DofekChart.tsx

Comment thread packages/web/src/components/DofekChart.test.tsx
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