Skip to content

[server] Fix duplicate Garmin dump activities - #1673

Merged
Asherlc merged 4 commits into
mainfrom
Asherlc/fix-garmin-duplicates
Jul 17, 2026
Merged

Asherlc merged 4 commits into
mainfrom
Asherlc/fix-garmin-duplicates

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Reuse a unique exact Garmin summary when dump FIT IDs differ, preserving summary metadata while importing sensor samples.
  • Include the year in shared web and mobile datetime formatting.
  • Document safe Docker disk recovery for required local validation.

Validation

  • pnpm lint
  • Root, server, and web TypeScript checks
  • pnpm test:unit (11,814 passed)
  • Garmin identity PostgreSQL integration test
  • GitHub CI (93 checks passed, including spell check, four integration shards, mutation testing, and web E2E)

Reuse uniquely matching Garmin summaries when dump FIT IDs differ. Include years in shared datetime labels and document safe Docker cleanup for local validation.
Copilot AI review requested due to automatic review settings July 17, 2026 19:50
@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 →

@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours. Upgrade to a paid plan for unlimited reviews.

@sourcery-ai

sourcery-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds an exact-identity lookup for Garmin dump activities to reuse existing summaries when FIT external IDs differ, updates shared date-time formatting to include the year, and documents safe Docker disk cleanup for running required validations.

Sequence diagram for Garmin dump exact-identity reuse in FIT import

sequenceDiagram
  participant processFitFileImportJob
  participant providerActivitySync
  participant db
  participant metricStreamWriter

  processFitFileImportJob->>providerActivitySync: findUniqueProviderActivityByExactIdentity(db, identity)
  providerActivitySync->>db: executeWithSchema(sql SELECT ... LIMIT 2)
  db-->>providerActivitySync: [{ id } | [] | multiple]

  alt [unique exact identity match]
    providerActivitySync-->>processFitFileImportJob: { id: summaryActivityId }
    processFitFileImportJob->>metricStreamWriter: replaceMetricStreamBatch(db, { activityId: summaryActivityId }, streams, "file")
  else [absent or ambiguous identity]
    providerActivitySync-->>processFitFileImportJob: undefined
    processFitFileImportJob->>providerActivitySync: upsertProviderActivity(db, insertValues, updateValues)
    providerActivitySync->>db: sql INSERT ... ON CONFLICT DO UPDATE
    db-->>providerActivitySync: { id: newActivityId }
    providerActivitySync-->>processFitFileImportJob: { id: newActivityId }
    processFitFileImportJob->>metricStreamWriter: replaceMetricStreamBatch(db, { activityId: newActivityId }, streams, "file")
  end
Loading

File-Level Changes

Change Details Files
Reuse an existing Garmin dump activity summary when a new FIT import matches by exact identity but has a different external ID.
  • Introduce findUniqueProviderActivityByExactIdentity to query a single active activity row by providerId, userId, activityType, startedAt, and endedAt with absence/deletion filters.
  • Update the FIT import job to call the exact-identity lookup for garmin-dump imports without a parsed summary and short-circuit upsertProviderActivity when a unique match is found.
  • Extend unit and integration tests to cover Garmin exact-identity reuse and absence/ambiguity cases, including metric stream replacement behavior.
src/db/provider-activity-sync.ts
src/db/provider-activity-sync.test.ts
src/db/provider-activity-sync.integration.test.ts
src/jobs/process-fit-file-import-job.ts
src/jobs/process-fit-file-import-job.test.ts
Adjust shared date-time formatting utilities to include the calendar year.
  • Add year:"numeric" to formatDateTime and update its documentation comment to reflect the new output.
  • Update date-time formatter tests to assert the year is included in formatted labels.
packages/format/src/format.ts
packages/format/src/format.test.ts
Document and standardize safe Docker disk cleanup procedures for running local validations.
  • Add a Docker Disk Recovery section to testing docs that walks through inspecting Docker disk usage and pruning disposable resources, build cache, and unused images in a safe order.
  • Clarify in AGENTS guidelines that Docker cleanup for required validation should focus on workspace-specific resources and must not aggressively prune shared volumes without explicit approval, referencing the new testing docs.
docs/testing.md
AGENTS.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

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

Asherlc has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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.

@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 1 issue, and left some high level feedback:

  • Relying on strict equality for startedAt/endedAt in findUniqueProviderActivityByExactIdentity could be fragile if provider timestamps differ even slightly between summary and FIT data (e.g., precision/rounding or timezone normalization); consider normalizing or using a small tolerance window to make matching more robust.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Relying on strict equality for `startedAt`/`endedAt` in `findUniqueProviderActivityByExactIdentity` could be fragile if provider timestamps differ even slightly between summary and FIT data (e.g., precision/rounding or timezone normalization); consider normalizing or using a small tolerance window to make matching more robust.

## Individual Comments

### Comment 1
<location path="AGENTS.md" line_range="83" />
<code_context>
+- **Docker cleanup is in scope for required validation**: When Docker disk exhaustion blocks lint, tests, or builds, remove disposable resources created by the current workspace and prune rebuildable build cache first (`docker builder prune -af`). Prune unused images only if more space is required. Preserve running containers and named volumes belonging to other workspaces; never run `docker volume prune` or `docker system prune --volumes` without explicit user approval. Follow [Docker's pruning guidance](https://docs.docker.com/engine/manage-resources/pruning/) and the local recovery steps in [`docs/testing.md`](docs/testing.md#docker-disk-recovery).
</code_context>
<issue_to_address>
**nitpick (typo):** Consider adding "the" before "rebuildable build cache" for grammatical consistency.

In this sentence, "prune rebuildable build cache first" should be "prune the rebuildable build cache first" for more natural phrasing and to match `docs/testing.md` ("prune the rebuildable build cache").

```suggestion
- **Docker cleanup is in scope for required validation**: When Docker disk exhaustion blocks lint, tests, or builds, remove disposable resources created by the current workspace and prune the rebuildable build cache first (`docker builder prune -af`). Prune unused images only if more space is required. Preserve running containers and named volumes belonging to other workspaces; never run `docker volume prune` or `docker system prune --volumes` without explicit user approval. Follow [Docker's pruning guidance](https://docs.docker.com/engine/manage-resources/pruning/) and the local recovery steps in [`docs/testing.md`](docs/testing.md#docker-disk-recovery).
```
</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 AGENTS.md Outdated
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds exact provider-activity identity reuse for Garmin FIT imports, includes the year in datetime formatting, and documents safe Docker disk-recovery steps for required validation.

Changes

Provider activity identity reuse

Layer / File(s) Summary
Exact activity lookup
src/db/provider-activity-sync.ts, src/db/provider-activity-sync.test.ts, src/db/provider-activity-sync.integration.test.ts
Adds an exact identity type and lookup that returns an ID only for one active matching activity; tests cover unique, absent, mismatched, and ambiguous results.
Garmin FIT import reuse
src/jobs/process-fit-file-import-job.ts, src/jobs/process-fit-file-import-job.test.ts
Garmin dump imports reuse a uniquely matched activity before falling back to upsert, with metric replacement verified by tests.

Datetime output update

Layer / File(s) Summary
Year-inclusive datetime formatting
packages/format/src/format.ts, packages/format/src/format.test.ts
formatDateTime now includes a numeric year, and its example and expected test output are updated.

Docker disk recovery guidance

Layer / File(s) Summary
Validation recovery instructions
AGENTS.md, docs/testing.md
Documents disk-usage checks, disposable-resource cleanup, builder-cache pruning, and restrictions on broad Docker pruning.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FITImportJob
  participant ProviderActivitySync
  participant ActivityDatabase
  participant MetricStreamStore
  FITImportJob->>ProviderActivitySync: Find Garmin activity by exact identity
  ProviderActivitySync->>ActivityDatabase: Query active matching rows
  ActivityDatabase-->>ProviderActivitySync: Return matching activity rows
  ProviderActivitySync-->>FITImportJob: Return one id or undefined
  FITImportJob->>MetricStreamStore: Replace metrics for resolved activity
Loading

Possibly related PRs

  • Asherlc/dofek#1597: Both modify the FIT activity import flow in process-fit-file-import-job.ts.
  • Asherlc/dofek#1646: Both modify the beginActivityImport path for FIT activity processing.

Suggested labels: area/db, type/bug, breaking-change

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is imperative, under 70 characters, properly prefixed with [server], and accurately summarizes the Garmin deduplication change.

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.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for f023ccd1 are ready:

This comment updates automatically on each PR push.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours. Upgrade to a paid plan for unlimited reviews.

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

Asherlc has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@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 `@src/db/provider-activity-sync.ts`:
- Line 43: Update providerActivityIdSchema to validate id with Zod’s strict UUID
string format instead of accepting any string, preserving the existing object
schema and runtime boundary behavior.

In `@src/jobs/process-fit-file-import-job.test.ts`:
- Around line 34-36: Update the provider-activity-sync module mock in
process-fit-file-import-job.test.ts to include an upsertProviderActivity export
alongside findUniqueProviderActivityByExactIdentity, wiring it to the existing
mock implementation used by the test upsert flow.
🪄 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: c9bbe054-ea68-483e-a4cb-328014e81b61

📥 Commits

Reviewing files that changed from the base of the PR and between 0b307fd and 66e4c3b.

📒 Files selected for processing (9)
  • AGENTS.md
  • docs/testing.md
  • packages/format/src/format.test.ts
  • packages/format/src/format.ts
  • src/db/provider-activity-sync.integration.test.ts
  • src/db/provider-activity-sync.test.ts
  • src/db/provider-activity-sync.ts
  • src/jobs/process-fit-file-import-job.test.ts
  • src/jobs/process-fit-file-import-job.ts

Comment thread src/db/provider-activity-sync.ts Outdated
Comment thread src/jobs/process-fit-file-import-job.test.ts
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours. Upgrade to a paid plan for unlimited reviews.

@Asherlc Asherlc changed the title Fix duplicate Garmin dump activities [server] Fix duplicate Garmin dump activities Jul 17, 2026

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

Asherlc has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Asherlc

Asherlc commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Review follow-up: the exact timestamp equality is intentional. The supplied Garmin summary and FIT session resolve to the same millisecond timestamps, while a tolerance window could merge two distinct activities; absent or ambiguous matches deliberately fall back to preserving both raw imports. The lookup therefore stays conservative and is covered by the one-millisecond mismatch integration assertion.

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

Asherlc has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@codereviewbot-ai

codereviewbot-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

🤖 Review failed: an error occurred during analysis.

@Asherlc
Asherlc merged commit 7210a90 into main Jul 17, 2026
100 checks passed
@Asherlc
Asherlc deleted the Asherlc/fix-garmin-duplicates branch July 17, 2026 20:33
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