@@ -62,6 +62,96 @@ def safe_get(value: Any, keys: list[Any], default: Any = "N/A") -> Any:
6262 return cursor
6363
6464
65+ def _looks_like_training_status_record (value : Any ) -> bool :
66+ if not isinstance (value , dict ):
67+ return False
68+ return any (
69+ key in value
70+ for key in (
71+ "trainingStatusFeedbackPhrase" ,
72+ "acuteTrainingLoadDTO" ,
73+ "weeklyTrainingLoad" ,
74+ "fitnessTrend" ,
75+ "loadLevelTrend" ,
76+ )
77+ )
78+
79+
80+ def _select_training_status_record (
81+ training_status : dict [str , Any ],
82+ * ,
83+ last_activity : dict [str , Any ] | None = None ,
84+ ) -> dict [str , Any ]:
85+ latest_training_status_data = safe_get (
86+ training_status ,
87+ ["mostRecentTrainingStatus" , "latestTrainingStatusData" ],
88+ default = {},
89+ )
90+ if _looks_like_training_status_record (latest_training_status_data ):
91+ return latest_training_status_data
92+ if not isinstance (latest_training_status_data , dict ):
93+ return {}
94+
95+ device_id = None
96+ if isinstance (last_activity , dict ):
97+ device_id = last_activity .get ("deviceId" )
98+ if device_id not in (None , "" ):
99+ by_device = latest_training_status_data .get (str (device_id ))
100+ if _looks_like_training_status_record (by_device ):
101+ return by_device
102+
103+ for candidate in latest_training_status_data .values ():
104+ if _looks_like_training_status_record (candidate ):
105+ return candidate
106+ return {}
107+
108+
109+ def _looks_like_training_readiness_entry (value : Any ) -> bool :
110+ if not isinstance (value , dict ):
111+ return False
112+ return any (
113+ key in value
114+ for key in (
115+ "level" ,
116+ "score" ,
117+ "sleepScore" ,
118+ "feedbackShort" ,
119+ "feedbackLong" ,
120+ "recoveryTime" ,
121+ )
122+ )
123+
124+
125+ def _select_training_readiness_entry (payload : Any ) -> dict [str , Any ]:
126+ if _looks_like_training_readiness_entry (payload ):
127+ return payload
128+ if isinstance (payload , list ):
129+ for item in payload :
130+ if _looks_like_training_readiness_entry (item ):
131+ return item
132+ return {}
133+ if not isinstance (payload , dict ):
134+ return {}
135+
136+ for key in ("dailyReadiness" , "trainingReadiness" , "readiness" , "item" , "result" ):
137+ nested = payload .get (key )
138+ if _looks_like_training_readiness_entry (nested ):
139+ return nested
140+ if isinstance (nested , list ):
141+ for item in nested :
142+ if _looks_like_training_readiness_entry (item ):
143+ return item
144+
145+ for nested in payload .values ():
146+ if _looks_like_training_readiness_entry (nested ):
147+ return nested
148+ if isinstance (nested , list ):
149+ for item in nested :
150+ if _looks_like_training_readiness_entry (item ):
151+ return item
152+ return {}
153+
154+
65155def _default_metrics () -> dict [str , Any ]:
66156 return {
67157 "vo2max" : "N/A" ,
@@ -976,35 +1066,17 @@ def fetch_training_status_and_scores(client: Any) -> dict[str, Any]:
9761066 logger .error ("Failed to fetch Garmin training status: %s" , exc )
9771067 training_status = {}
9781068
979- latest_status_data = safe_get (
980- training_status ,
981- ["mostRecentTrainingStatus" , "latestTrainingStatusData" , "3417115846" ],
982- default = {},
983- )
984- if not isinstance (latest_status_data , dict ):
985- latest_status_data = {}
1069+ latest_status_data = _select_training_status_record (training_status , last_activity = last_activity )
9861070 acute_load_dto = latest_status_data .get ("acuteTrainingLoadDTO" )
9871071 if not isinstance (acute_load_dto , dict ):
9881072 acute_load_dto = {}
9891073
990- feedback = safe_get (
991- training_status ,
992- ["mostRecentTrainingStatus" , "latestTrainingStatusData" , "3417115846" , "trainingStatusFeedbackPhrase" ],
993- )
994- acwr_status_raw = safe_get (
995- training_status ,
996- ["mostRecentTrainingStatus" , "latestTrainingStatusData" , "3417115846" , "acuteTrainingLoadDTO" , "acwrStatus" ],
997- )
1074+ feedback = latest_status_data .get ("trainingStatusFeedbackPhrase" , "N/A" )
1075+ acwr_status_raw = safe_get (latest_status_data , ["acuteTrainingLoadDTO" , "acwrStatus" ])
9981076
9991077 metrics ["vo2max" ] = safe_get (training_status , ["mostRecentVO2Max" , "generic" , "vo2MaxPreciseValue" ])
1000- metrics ["chronic_load" ] = safe_get (
1001- training_status ,
1002- ["mostRecentTrainingStatus" , "latestTrainingStatusData" , "3417115846" , "acuteTrainingLoadDTO" , "dailyTrainingLoadChronic" ],
1003- )
1004- metrics ["acute_load" ] = safe_get (
1005- training_status ,
1006- ["mostRecentTrainingStatus" , "latestTrainingStatusData" , "3417115846" , "acuteTrainingLoadDTO" , "dailyTrainingLoadAcute" ],
1007- )
1078+ metrics ["chronic_load" ] = safe_get (latest_status_data , ["acuteTrainingLoadDTO" , "dailyTrainingLoadChronic" ])
1079+ metrics ["acute_load" ] = safe_get (latest_status_data , ["acuteTrainingLoadDTO" , "dailyTrainingLoadAcute" ])
10081080 metrics ["load_tunnel_min" ] = latest_status_data .get ("loadTunnelMin" , "N/A" )
10091081 metrics ["load_tunnel_max" ] = latest_status_data .get ("loadTunnelMax" , "N/A" )
10101082 metrics ["weekly_training_load" ] = latest_status_data .get ("weeklyTrainingLoad" , "N/A" )
@@ -1063,7 +1135,7 @@ def fetch_training_status_and_scores(client: Any) -> dict[str, Any]:
10631135
10641136 try :
10651137 readiness = client .get_training_readiness (start_date )
1066- readiness_entry = readiness [ 0 ] if isinstance (readiness , list ) and readiness else {}
1138+ readiness_entry = _select_training_readiness_entry (readiness )
10671139 readiness_level = safe_get (readiness_entry , ["level" ], default = "N/A" )
10681140 metrics ["training_readiness_score" ] = safe_get (readiness_entry , ["score" ], default = "N/A" )
10691141 metrics ["sleep_score" ] = safe_get (readiness_entry , ["sleepScore" ], default = "N/A" )
0 commit comments