Skip to content

Distance issue clean#322

Open
Carlixgonzam wants to merge 8 commits into
tcgoetz:masterfrom
Carlixgonzam:distanceIssue-clean
Open

Distance issue clean#322
Carlixgonzam wants to merge 8 commits into
tcgoetz:masterfrom
Carlixgonzam:distanceIssue-clean

Conversation

@Carlixgonzam

Copy link
Copy Markdown

while investigating the issue, I noticed that min_temperature was defined in ActivitiesCommon and therefore available in both ActivityLaps and Activities, but it was never populated from the FIT file.
this PR adds the missing mapping so the value is persisted whenever it is present in the FIT activity data.

Background
issue #317 originally reported incorrect lap persistence for some FIT activity imports. While tracing the import pipeline and reviewing how lap data is written, I found that min_temperature was already part of the database schema but was omitted from both _write_lap_entry and _write_session_entry.
As a result, even when the FIT file contained this information, it was never stored in the database.
Changes
This PR adds the missing field mapping in two places:
_write_lap_entry
'min_temperature': message_fields.get('min_temperature'),
_write_session_entry
'min_temperature': message_fields.get('min_temperature'),
The field is inserted alongside the existing temperature mappings (max_temperature and avg_temperature), keeping the implementation consistent with the rest of the activity import code.

During the investigation I also noticed that FIT length messages are currently ignored because _write_length_entry is inherited from FitFileProcessor and is effectively a no-op.
Supporting pool swim length data would require introducing a dedicated ActivityLengths table and a database schema migration, which is a separate feature and intentionally outside the scope of this PR.
Verification
Confirmed that min_temperature is now persisted for both activity_laps and activities whenever the FIT message contains the field.
Verified that the resulting diff against develop only contains the two new mappings.
Checked that the modified file parses correctly and introduces no new lint warnings.

philippeauriach and others added 8 commits May 14, 2026 09:32
* 3.6.6 prerelease = false

* Fix multi-sport activity import (tcgoetz#289)

Multi-sport activities (triathlon / brick sessions) are stored by Garmin as
a single FIT file containing multiple `session` messages plus child sessions
for transitions. Previously the importer wrote every session under the bare
parent activity_id, so the last session silently overwrote all the others
and laps/records/HR zones/devices ended up attached to the wrong activity
(or dropped entirely when the hr_zones_timer processing order created empty
rows that blocked the real lap writes).

This change detects multi-sport files by checking `len(fit_file.session) > 1`
and, when detected:

- creates one activity row per session with a suffixed id `<parent>_<n>`
  (e.g. 22638574127_1 ... _5 for swim→T1→run→T2→swim)
- derives each session's `stop_time` from `start_time + total_elapsed_time`
  (Garmin reuses the parent start_time as every child's `timestamp`, which
  would otherwise result in stop_time <= start_time)
- partitions laps by session using each session's `num_laps` and rewrites
  `lap_num` to be session-local (0..N per session), so every child has its
  own contiguous lap sequence
- partitions records (GPS trackpoints) by matching their timestamp against
  each session's time window
- remaps session/lap HR-zone writes to the correct child activity_id
- associates device entries with every child activity

`_write_lap_entry` was also switched to `ActivityLaps.s_insert_or_update` so
lap data correctly coalesces with the hr_zones_timer rows that the generic
message dispatcher creates earlier in the import; the same `stop_time`
correction applies when the file is multi-sport.

Single-session FIT files go through the exact same code path as before.

Extends and supersedes the single-session subset fixed in tcgoetz#310 (same root
cause for `stop_time`, lap-ordering race, and the add-vs-insert-or-update
issue).

Fixes tcgoetz#289

---------

Co-authored-by: Tom Goetz <tcgoetz@gmail.com>
Co-authored-by: Tom Goetz <15090876+tcgoetz@users.noreply.github.com>
* Add PostgreSQL backend support

garmin_connect_config_manager.get_db_params builds a DbParams for
db.type='postgresql', reading user/password/host/port/database from
the JSON config. GarminConnectConfig.json.example shows the new keys.

Also fix two pre-existing sqlite-only constructs surfaced by the
postgres backend:

- activities_db._create_sport_view / _create_course_view: use SQL '='
  and single-quoted literals (sqlite tolerates '==' and double-quoted
  strings; postgres reads "foo" as an identifier).
- summarydb.summary_base.__create_weeks_months_years_selectable: the
  per-period view selected sweat_loss_avg twice. Postgres rejects
  duplicate output columns. Second slot was meant to be sweat_loss.

* Fix import-side type issues exposed by postgres backend

Four bugs surfaced when running `--import` against the new postgres
backend. Sqlite was permissive enough to mask each one; postgres is
not. Fixed in place — sqlite and mysql users see no behaviour change.

- Device.serial_number is declared `Integer` but Garmin's Descent Mk3i
  family reports serials > 2**31 (e.g. 3476167836). The hard-coded
  fallback `unknown_device_serial_number = 9999999999` would also
  overflow on a strict 32-bit backend. Switch to BigInteger on the
  primary key and on the matching FK columns in DeviceInfo / File and
  on activities.device_serial_number / activity_laps.device_serial_number.

- monitoring_info.file_id was `Integer`, but the value passed in is the
  string id from `File.id` (which itself is `String`). Sqlite quietly
  coerced "269351985837" to a numeric value; postgres rejects the
  string-to-int4 cast outright. Switch monitoring_info.file_id to
  `String` so the FK shape matches `File.id`.

- monitoring_fit_file_processor.__unpack_tuple only unpacked when
  type(value) is tuple, but fitfile yields per-activity-type arrays as
  plain `list`. The list bled through to a `Float` column and the
  failure poisoned the SQLAlchemy session for every subsequent insert
  in the same file. Accept both list and tuple via isinstance().

* Plug remaining import-side gaps + include today in --latest

Three small bugs hit while running --import + --download --latest
against the postgres backend; bundled here because they share the same
"sqlite tolerated, postgres does not" pattern.

- scripts/garmindb_cli.py __get_date_and_days set days = (today - date).days,
  which omits today. With a one-day-ago anchor that pulled yesterday and
  the day before but silently skipped today. Bumping both --latest and
  --all branches by 1 includes today; when upstream has nothing yet the
  API returns empty and the importer is a no-op for that day.

- garmindb/fit_file_processor._write_device_info_entry treated a serial
  of None as "unset" (and synthesised an id from parent + device_type)
  but kept the literal 0 that fitfile emits for an invalid/unset serial.
  Multiple local sensors per FIT then collide on a shared 0 PK, the
  failure poisons the session, and the rest of the file is dropped.
  Treat 0 the same as None on SourceType.local.

- garmindb/garmindb/garmin_db.Device.local_device_serial_number returned
  a string ('%s%06d' % ...); the value lands in an Integer column. Wrap
  in int() so postgres does not have to coerce.

* Update garmindb_cli.py

---------

Co-authored-by: no84by <silvius@duck.com>
Co-authored-by: Tom Goetz <15090876+tcgoetz@users.noreply.github.com>
replaced the s_exists + add pattern in _write_lap_entry with s_insert_or_update(ignore_none=True) so that existing lap rows are updated on re-import rather than silently skipped.
This preserves legitimate zero-valued fields such as distance=0.0 for rest laps, and adds the missing min_temperature mapping to both _write_lap_entry and _write_session_entry
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.

4 participants