From 0bc50fefff6f12463bc7eea90aabd84e01197338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vit=C3=B3ria=20Silva?= Date: Wed, 3 Dec 2025 14:30:52 +0000 Subject: [PATCH] Add FIT file_id parsing and tracker info extraction Introduces parsing of the FIT file_id frame to extract manufacturer, product, serial number, and creation time. Updates activity object creation to include tracker manufacturer and model, and propagates file_id data through parsing and splitting functions for improved device metadata handling. --- backend/app/fit/utils.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backend/app/fit/utils.py b/backend/app/fit/utils.py index c28bd72ad..319a516c7 100644 --- a/backend/app/fit/utils.py +++ b/backend/app/fit/utils.py @@ -1,4 +1,5 @@ import fitdecode +from enum import Enum from fastapi import HTTPException, status from datetime import datetime, timedelta @@ -188,6 +189,10 @@ def create_activity_objects( hide_workout_sets_steps=user_privacy_settings.hide_activity_workout_sets_steps or False, hide_gear=user_privacy_settings.hide_activity_gear or False, + tracker_manufacturer=session_record["file_id"].get( + "manufacturer", None + ), + tracker_model=str(session_record["file_id"].get("product", None)), ), "is_elevation_set": session_record["is_elevation_set"], "ele_waypoints": session_record["ele_waypoints"], @@ -305,6 +310,7 @@ def split_records_by_activity(parsed_data: dict) -> dict: "workout_steps": parsed_data["workout_steps"], "sets": parsed_data["sets"], "lengths": parsed_data["lengths"], + "file_id": parsed_data["file_id"], } # Only parse arrays if the respective flag is set @@ -470,6 +476,9 @@ def parse_fit_file( # Array to store lengths lengths = [] + # Dictionary to store file ID data + file_id = {} + # Initialize variables to store previous latitude and longitude prev_latitude, prev_longitude = None, None @@ -721,6 +730,9 @@ def parse_fit_file( if frame.name == "length": lengths.append(parse_frame_length(frame)) + if frame.name == "file_id": + file_id = parse_frame_file_id(frame) + # Check if exercises titles is not none if exercises_titles: activity_exercise_titles_crud.create_activity_exercise_titles( @@ -751,6 +763,7 @@ def parse_fit_file( "sets": sets, "workout_steps": workout_steps, "lengths": lengths, + "file_id": file_id, } except HTTPException as http_err: raise http_err @@ -1070,6 +1083,16 @@ def parse_frame_length(frame): } +def parse_frame_file_id(frame): + return { + "type": get_value_from_frame(frame, "type"), + "manufacturer": get_value_from_frame(frame, "manufacturer"), + "product": get_value_from_frame(frame, "product"), + "serial_number": get_value_from_frame(frame, "serial_number"), + "time_created": get_value_from_frame(frame, "time_created"), + } + + def interpret_time_offset(raw_offset): # Check for two's complement representation (values > 2^31) if raw_offset != 0 and raw_offset is not None: