Backfill dates from create_time and add --repair-days; 1.9.0 - #59
Merged
Conversation
update_time is when Eufy last touched the record server-side, not when the weigh-in happened. Eufy rewrites it in bulk on some accounts (one report: 318 of 319 records sharing a single timestamp), so a backfill filed years of history under one date. create_time survives those rewrites, and the old create_time fallback was dead code: update_time is always present in the /device/data response, so the get() default never fired. Both the measurement timestamp and the measurement id now come from create_time, falling back to update_time when create_time is missing or zero. Ids are therefore stable across Eufy's rewrites. On an account that already synced under rewritten ids the old state rows go stale, and a backfill re-uploads with the correct dates; Garmin de-dupes by timestamp, so that is safe. The raw Wi-Fi endpoint is untouched: it carries its own timestamp field and never had this problem.
Local state can say a measurement was delivered while the target no longer has it: entries deleted by hand in Garmin Connect, or dates that the update_time bug filed wrong and the user cleaned up. Nothing could re-send those, because every path keyed off is_synced. --repair-days N sets the fetch window like --backfill-days, then ignores is_synced when deciding whether to upload. The state lookup itself stays, because it still decides whether to record the sync: re-uploading a known id must not insert a second row past the UNIQUE constraint. The two flags are rejected together, since they set the same window from opposite intents. What repair does not change: - The weight-only upgrade still replaces the Garmin entry rather than adding one beside it (issue #48). - The same-date guard still skips a date another source owns in Garmin. Our own deleted dates pass it, because we have a sync row for them. - Strava receives only the newest measurement in the window. It stores a single current weight, so replaying history there would spend the rate limit (100 non-upload calls per 15 minutes) to land the same value. The skip-because-already-in-Garmin row is now written only when there is no row yet. Repair reaches that branch a second time for the same measurement, and the insert would have hit the UNIQUE constraint.
The recovery path needs its own paragraph: --backfill-days looks like the flag for this and is not, since it trusts the local record.
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.
Two fixes that meet in the same place: getting the right dates onto Garmin, and getting them there when the local record disagrees with the target.
Weigh-in time comes from create_time
_parse_recordread the timestamp fromupdate_time, which is when Eufy last touched the record server-side, not when the weigh-in happened. Eufy rewrites that field in bulk on some accounts (one report: 318 of 319 records sharing a single timestamp), so--backfill-daysfiled years of history under one date. Forward syncing never showed it, because on a fresh weigh-in the two fields are seconds apart. The oldcreate_timefallback was dead code:update_timeis always present in the/device/dataresponse, so theget()default never fired.Both the timestamp and the measurement id now come from
create_time, falling back toupdate_timewhen it is missing or zero. Ids are stable across Eufy's rewrites as a result. On an account that already synced under rewritten ids the old state rows go stale, and a backfill or repair re-uploads with the correct dates; Garmin de-dupes by timestamp, so that is safe. The raw Wi-Fi endpoint is untouched, since it carries its owntimestampfield.Fixes #56.
--repair-days N
Local state can say a measurement was delivered while the target no longer has it: entries deleted by hand in Garmin Connect, or dates the bug above filed wrong and the user cleaned up. Nothing could re-send those, because every path keyed off
is_synced.--repair-days Nsets the fetch window the way--backfill-daysdoes, then ignoresis_syncedwhen deciding whether to upload. The state lookup stays, because it still decides whether the sync is recorded: re-uploading a known id must not insert a second row past the UNIQUE constraint. Passing both flags is rejected, since they set the same window from opposite intents.What repair leaves alone:
One related fix fell out of this: the skip-because-already-in-Garmin row is now written only when there is no row yet. Repair reaches that branch a second time for the same measurement, and the insert would have hit the UNIQUE constraint.
Closes #58.
Also
README documents the flag and the recovery path, including the part the tool cannot do for you: wrong-dated entries have to be deleted in Garmin Connect first, since eufy-sync never deletes data it did not just replace. Version bumped to 1.9.0 in both places.
345 tests, 13 new: create_time priority and its three fallbacks, the fetch window selecting on weigh-in time, and repair mode against an already-synced record, a weight-only row, a foreign same-date entry, an already-skipped date, Strava, dry run, and the two CLI paths.