Fix body battery and intensity minutes Garmin API endpoints
All checks were successful
Deploy / deploy (push) Successful in 2m27s

Body Battery:
- Change endpoint from /usersummary-service/stats/bodyBattery/dates/
  to /wellness-service/wellness/bodyBattery/reports/daily
- Parse new response format: array with bodyBatteryValuesArray time series
- Current value = last entry's level (index 2)
- YesterdayLow = min level from yesterday's data

Intensity Minutes:
- Change endpoint from /fitnessstats-service/activity
  to /usersummary-service/stats/im/weekly
- Add date parameter to function signature
- Parse new response format: array with moderateValue/vigorousValue

Endpoints verified against python-garminconnect source code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 13:58:04 +00:00
parent 59d70ee414
commit cf89675b92
4 changed files with 117 additions and 66 deletions

View File

@@ -109,8 +109,13 @@ export async function fetchBodyBattery(
oauth2Token: string,
): Promise<BodyBatteryData> {
try {
// Calculate yesterday's date for the API request
const dateObj = new Date(date);
dateObj.setDate(dateObj.getDate() - 1);
const yesterday = dateObj.toISOString().split("T")[0];
const response = await fetch(
`${GARMIN_API_URL}/usersummary-service/stats/bodyBattery/dates/${date}`,
`${GARMIN_API_URL}/wellness-service/wellness/bodyBattery/reports/daily?startDate=${yesterday}&endDate=${date}`,
{
headers: getGarminHeaders(oauth2Token),
},
@@ -132,20 +137,33 @@ export async function fetchBodyBattery(
);
return { current: null, yesterdayLow: null };
}
const data = JSON.parse(text);
const data = JSON.parse(text) as Array<{
date: string;
bodyBatteryValuesArray?: Array<[number, string, number, number]>;
}>;
const currentData = data?.bodyBatteryValuesArray?.[0];
const current = currentData?.charged ?? null;
// Find today's and yesterday's data from the response array
const todayData = data?.find((d) => d.date === date);
const yesterdayData = data?.find((d) => d.date === yesterday);
const yesterdayStats = data?.bodyBatteryStatList?.[0];
const yesterdayLow = yesterdayStats?.min ?? null;
// Current = last value in today's bodyBatteryValuesArray (index 2 is the level)
const todayValues = todayData?.bodyBatteryValuesArray ?? [];
const current =
todayValues.length > 0 ? todayValues[todayValues.length - 1][2] : null;
// Yesterday low = minimum level in yesterday's bodyBatteryValuesArray
const yesterdayValues = yesterdayData?.bodyBatteryValuesArray ?? [];
const yesterdayLow =
yesterdayValues.length > 0
? Math.min(...yesterdayValues.map((v) => v[2]))
: null;
logger.info(
{
current,
yesterdayLow,
hasCurrentData: !!currentData,
hasYesterdayData: !!yesterdayStats,
hasCurrentData: todayValues.length > 0,
hasYesterdayData: yesterdayValues.length > 0,
},
"Garmin body battery data received",
);
@@ -161,11 +179,18 @@ export async function fetchBodyBattery(
}
export async function fetchIntensityMinutes(
date: string,
oauth2Token: string,
): Promise<number> {
try {
// Calculate 7 days before the date for weekly range
const endDate = date;
const startDateObj = new Date(date);
startDateObj.setDate(startDateObj.getDate() - 7);
const startDate = startDateObj.toISOString().split("T")[0];
const response = await fetch(
`${GARMIN_API_URL}/fitnessstats-service/activity`,
`${GARMIN_API_URL}/usersummary-service/stats/im/weekly?start=${startDate}&end=${endDate}`,
{
headers: getGarminHeaders(oauth2Token),
},
@@ -173,7 +198,7 @@ export async function fetchIntensityMinutes(
if (!response.ok) {
logger.warn(
{ status: response.status, endpoint: "fitnessstats" },
{ status: response.status, endpoint: "intensityMinutes" },
"Garmin intensity minutes API error",
);
return 0;
@@ -182,24 +207,28 @@ export async function fetchIntensityMinutes(
const text = await response.text();
if (!text.startsWith("{") && !text.startsWith("[")) {
logger.error(
{ endpoint: "fitnessstats", responseBody: text.slice(0, 1000) },
{ endpoint: "intensityMinutes", responseBody: text.slice(0, 1000) },
"Garmin returned non-JSON response",
);
return 0;
}
const data = JSON.parse(text);
const weeklyTotal = data?.weeklyTotal;
const data = JSON.parse(text) as Array<{
calendarDate: string;
moderateValue?: number;
vigorousValue?: number;
}>;
if (!weeklyTotal) {
const entry = data?.[0];
if (!entry) {
logger.info(
{ hasWeeklyTotal: false },
{ hasData: false },
"Garmin intensity minutes: no weekly data",
);
return 0;
}
const moderate = weeklyTotal.moderateIntensityMinutes ?? 0;
const vigorous = weeklyTotal.vigorousIntensityMinutes ?? 0;
const moderate = entry.moderateValue ?? 0;
const vigorous = entry.vigorousValue ?? 0;
const total = moderate + vigorous;
logger.info(
@@ -210,7 +239,7 @@ export async function fetchIntensityMinutes(
return total;
} catch (error) {
logger.error(
{ err: error, endpoint: "fitnessstats" },
{ err: error, endpoint: "intensityMinutes" },
"Garmin intensity minutes fetch failed",
);
return 0;