Skip to content

Commit 0008dd5

Browse files
bewestCopilot
andcommitted
docs: V3 profile dedup fix landed; update impact analysis
V3 (POST /api/v3/profile) now dedups AAPS profile-store edits via a profile-shape-aware identifier (uuidv5 of "profilestore_<app>_<defaultProfile>") and a relaxed immutability rule for date/created_at/startDate during profile-store deduplication. V1 and V3 paths now both converge AAPS profile edits onto a single MongoDB doc per source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 132cc26 commit 0008dd5

1 file changed

Lines changed: 14 additions & 11 deletions

File tree

docs/aaps-profile-sync-impact-analysis-2026-04-20.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -294,22 +294,25 @@ A `DataSyncSelectorV1Test` does not currently exist (only V3 has a test). The si
294294
- **Should `PairProfileStore` track `nightscoutId`?** Every other `Pair*` does. Tracking would let AAPS use stable `_id` references and allow the c-r-m server to do `_id`-based dedup rather than `startDate`-based. It would also unblock a future `nsUpdate` path for profiles.
295295
- **The `createdAt % 1000 == 0L` heuristic in `NsIncomingDataProcessor.processProfile:283-295`** ("whole second means edited in NS") is fragile — any AAPS-originated profile whose epoch happens to land on a whole second will be re-imported as if NS-edited. Worth replacing with explicit provenance.
296296

297-
## V3 path verification
297+
## V3 path verification and fix
298298

299299
V3 hits `POST /api/v3/profile` (`NightscoutRemoteService.kt:102-103`), which routes to `lib/api3/generic/create/operation.js`. The handler computes an identifier as `uuid.v5("undefined_<doc.date>")` (since profile docs have no `device` or `eventType`) and uses it for dedup via `identifyingFilter`. Verified with three new tests in `tests/api3.aaps-patterns.test.js` under `Profile sync via REST (V3) - AAPS createProfileStore behavior`:
300300

301-
| Scenario | V3 result |
302-
|---|---|
303-
| First POST | 201, one doc inserted |
304-
| Resend identical payload (retry) | **200, deduped in place** (request-level dedup works) |
305-
| Edit profile in AAPS → new `LocalProfileLastChange` → new `date` | **201, new doc inserted** (different identifier) |
301+
| Scenario | V3 pre-fix | V3 post-fix |
302+
|---|---|---|
303+
| First POST | 201, one doc inserted | 201, one doc inserted |
304+
| Resend identical payload (retry) | 200, deduped in place (request-level dedup works) | 200, deduped in place |
305+
| Edit profile in AAPS → new `LocalProfileLastChange` → new `date` | **201, new doc inserted** (different identifier) | **200, same identifier, single doc** |
306+
| Distinct `defaultProfile` names from same `app` | (n/a) | 201 each, two docs (no collision) |
307+
308+
So V3 had **request-level dedup but not edit-level dedup**. We patched it in two places:
309+
310+
1. `lib/api3/shared/operationTools.js``calculateIdentifier` now special-cases profile-store-shaped documents (`defaultProfile` + `store`, no `eventType`) and computes `uuid.v5("profilestore_<app>_<defaultProfile>")`. Edits and retries from the same `(app, defaultProfile)` collapse onto a single identifier.
311+
2. `lib/api3/generic/update/validate.js` — relaxed immutability of `date`, `created_at`, `startDate` during deduplication when the storage document is a profile-store, since those fields are expected to advance per edit.
306312

307-
So V3 has **request-level dedup but not edit-level dedup**. Each user edit still accumulates a new MongoDB profile document — the same architectural symptom as pre-fix V1.
313+
After the fix, V3 (REST) and V1 (websocket) both converge AAPS profile updates onto a single MongoDB document per source. The secondary `_id` sort in `lib/server/profile.js:last()` continues to ensure deterministic display when multiple profile rows do exist (e.g., legacy data from before the fix).
308314

309-
**Implications:**
310-
- After the c-r-m fix, V1 is now *better* than V3 for AAPS-shaped profile edits: V1 dedups by `startDate` and converges to a single document; V3 still accumulates one document per edit.
311-
- Both paths rely on `ctx.profile.last()` to choose the displayed profile, so the secondary sort key fix (`{startDate: -1, _id: -1}`) helps both.
312-
- A symmetric fix for V3 would extend `calculateIdentifier` to dedup profile docs by `startDate` (or by `(app, startDate)`), or AAPS could be modified to issue `nsUpdate` (PUT/PATCH) instead of `nsAdd` for profile re-saves so the existing identifier is reused.
315+
**Migration note:** existing duplicate profile rows in production databases are not retroactively merged. New POSTs from updated servers will pick the most recently-edited row (via the `dedupFallbackFields: ['created_at']` path) only if the standard identifier path doesn't match — in practice, after this fix new edits from any given AAPS source converge on one identifier, but pre-existing duplicates remain until manually cleaned up.
313316

314317
---
315318

0 commit comments

Comments
 (0)