fix(timeplanning): startup repair for corrupted pauseNId (last 7 days, 5-min sites)#1608
Merged
Merged
Conversation
…, 5-min sites) Recomputes pauseNId from the intact pause timestamps for rows whose stored value grossly overstates the real pause (the absolute-stop-tick corruption), idempotently, on plugin startup. Re-establishes persisted netto/flex consistency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a startup-time, idempotent data repair to correct historically corrupted pauseNId values for 5‑minute sites (last 7 days) by recomputing pause IDs from pause timestamps and re-running the production netto writer so persisted netto fields are restored.
Changes:
- Introduces
CorruptedPauseIdRepairhelper that detects and corrects clearly corrupted pause IDs, recomputing persisted netto fields and emitting console/Sentry observability. - Hooks the repair into plugin startup right after database seeding.
- Adds NUnit + Testcontainers MariaDB coverage for in-scope fixes, out-of-scope no-ops, idempotency, and “no timestamps => no write”.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/Infrastructure/Helpers/CorruptedPauseIdRepair.cs | Implements the targeted pause-id repair logic and netto recomputation, plus logging/anomaly reporting. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn/EformTimePlanningPlugin.cs | Runs the repair at startup after seeding. |
| eFormAPI/Plugins/TimePlanning.Pn/TimePlanning.Pn.Test/CorruptedPauseIdRepairTests.cs | Adds integration-style tests validating the repair behavior and idempotency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+79
to
+83
| .Where(p => p.WorkflowState != Constants.WorkflowStates.Removed | ||
| && p.Date >= windowStart | ||
| && fiveMinuteSiteIds.Contains(p.SdkSitId)) | ||
| .ToListAsync() | ||
| .ConfigureAwait(false); |
Comment on lines
+915
to
+920
| public void RepairCorruptedPauseIds(string connectionString) | ||
| { | ||
| var contextFactory = new TimePlanningPnContextFactory(); | ||
| using var dbContext = contextFactory.CreateDbContext([connectionString]); | ||
| CorruptedPauseIdRepair.Run(dbContext).GetAwaiter().GetResult(); | ||
| } |
Comment on lines
+85
to
+89
| Assert.That((await Reload(ctx, corrupted)).Pause1Id, Is.EqualTo(7)); // fixed | ||
| Assert.That((await Reload(ctx, correct)).Pause1Id, Is.EqualTo(7)); // unchanged | ||
| Assert.That((await Reload(ctx, offByOne)).Pause1Id, Is.EqualTo(6)); // left alone | ||
| Assert.That((await Reload(ctx, oldRow)).Pause1Id, Is.EqualTo(145)); // out of window | ||
| Assert.That((await Reload(ctx, oneMin)).Pause1Id, Is.EqualTo(30)); // 1-min site |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The mobile punch-clock had been sending the absolute stop-time tick as
pauseNIdon pause-stop for 5-minute sites instead of the pause duration. The server decodespauseNIdas(pauseNId-1)*5minutes, so corrupted ids (e.g. 145) became ~12h breaks and drove netto hours negative. Verified on real rows 17003/17024/19545 (ids 116/115/115 → ~9.5h decoded vs ~30min real pause; persisted NettoHours ≈ -0.92h).The app-side fix (stops new corruption) is in the flutter-time repo. This PR repairs the already-corrupted historical rows.
Fix
New idempotent, corrupted-rows-only startup repair (
CorruptedPauseIdRepair), mirroring the existingSeedDatabasestartup idiom, scoped to the last 7 days and to 5-minute sites (UseOneMinuteIntervals == false):(pauseNId-1)*5 - actualMinutes > 15(catches the absolute-tick overstatement; ignores the known 5-min off-by-one).(int)(actualMinutes/5)+1, matching the server's/5+1encode. Writes only on change (idempotent).ComputeTimeTrackingFieldsso persistedNettoHours/NettoHoursInSecondsare restored (verified this is the flag-off save path's final netto writer).No schema change / no migration (pure data update via the DbContext). No base-package edits.
Tests (NUnit + Testcontainers MariaDB)
Control matrix: corrupted-in-window FIXED, correct UNCHANGED, off-by-one LEFT ALONE, out-of-window UNCHANGED, 1-minute-site UNCHANGED; idempotency; netto recompute assertion; and no-write-when-corrupt-id-has-no-timestamps.
🤖 Generated with Claude Code