coding

Using Google Fitness API to calculate my TDEE and more

From calories to code: Integrating Google Fitness API for personalized health insights

Written in August 2, 2023 - 🕒 16 min. read

Looking to achieve your fitness goals, such as losing some weight or building muscle? It’s all about knowing how many calories you burn in a day, which is called your Total Daily Energy Expenditure (TDEE).

Think of TDEE as the golden number that guides your food and workout choices. You have to know it to figure out how much to eat if you want to maintain, pack on some kilos, or slim down.

Now, there are a bunch of online tools that can guess your TDEE, but getting data from your own body will give you the real deal.

I get it, crunching these numbers can be a drag, especially if math isn’t your jam. But hey, the Google Fitness API’s got your back on this one!

In this blog post, we’ll show you how to use the Google Fitness API to calculate your TDEE and track your progress over time.

Of course, for this to work you need to consistently track your weight and food intake. I highly recommend getting a bioimpedance scale, which can measure your weight, body fat percentage, and muscle mass and the app Lifesum to track your food intake.

Using the Google Fitness API

Got the basics of TDEE down? Cool, let’s dive into how we can get the scoop using the Google Fitness API. This nifty tool lets coders grab data from Google Fit – that’s the app that gathers health info from your phone, smartwatches, and other apps.

To get started with the Google Fitness API, first, go to the Google Cloud Console and turn on the Fitness API. Then, we’ll need to create credentials for our project, so we can tap into the API. For more details, check their Getting Started guide.

Once we’re all set up, it’s all about using the API to get our hands on some juicy Google Fit data. We’re talking weight and food stats, which will help us nail down our TDEE calculation.

Retrieving and Parsing the Data

Now that we have our Google Fitness API set up and ready to go, it’s time to start retrieving and parsing the data that we need to calculate our TDEE. The first step is to make a request to the API, asking for our weight and nutrition data for the past 30 days.

Here’s an example of what this request might look like:

const { google } = require('googleapis');

const fetchData = async () => {
  const auth = await google.auth.fromJSON({
    type: process.env.TOKEN_TYPE,
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    refresh_token: process.env.REFRESH_TOKEN,
  });

  auth.scopes = [
    'https://www.googleapis.com/auth/fitness.nutrition.read',
    'https://www.googleapis.com/auth/fitness.body.read',
  ];


  const endDate = new Date();
  const startDate = new Date();
  startDate.setDate(startDate.getDate() - 30); // past 30 days
  const startTimeNs = startDate.getTime() * 1000000;
  const endTimeNs = endDate.getTime() * 1000000;

  const weightData = await fetchDataForDataSource(
    'derived:com.google.weight:com.google.android.gms:merge_weight',
    auth,
    startTimeNs,
    endTimeNs
  );

  const fatPercentageData = await fetchDataForDataSource(
    'derived:com.google.body.fat.percentage:com.google.android.gms:merged',
    auth,
    startTimeNs,
    endTimeNs
  );

  const nutritionData = await fetchDataForDataSource(
    'derived:com.google.nutrition:com.google.android.gms:merged',
    auth,
    startTimeNs,
    endTimeNs
  );
};

The function google.auth.fromJSON is used to create an authentication object that can be used to make requests to the Google Fitness API. We then set the scopes that we want to request data for, which in this case are https://www.googleapis.com/auth/fitness.nutrition.read and https://www.googleapis.com/auth/fitness.body.read. After that, we set the start date and end date for our request, which is 30 days ago and today respectively. Finally, we call the fetchDataForDataSource function three times, once for weight data, once for fat percentage data, and once for nutrition data.

But what does the fetchDataForDataSource function do? Let’s take a look:

const fitness = google.fitness('v1');

const fetchDataForDataSource = async (dataSourceId, auth, startTimeNs, endTimeNs) => {
  const response = await fitness.users.dataSources.datasets.get({
    userId: 'me',
    dataSourceId,
    datasetId: `${startTimeNs}-${endTimeNs}`,
    auth,
  });

  return response.data;
};

With the fitness API from googleapis we can make requests to the Google Fitness API. In this case, we’re using the users.dataSources.datasets.get method to get data for a specific data source. We pass in the userId as me to get data for the current user, the dataSourceId that we want to get data for, and the datasetId which is a combination of the start and end time in nanoseconds. Finally, we pass in the authentication object that we created earlier.

Aggregating the Data

Let’s take a look at the data we got back from the Google Fitness API:

Weight

{
  "minStartTimeNs": "1690662651313000000",
  "maxEndTimeNs": "1691008251313000000",
  "dataSourceId": "derived:com.google.weight:com.google.android.gms:merge_weight",
  "point": [
    {
      "startTimeNanos": "1690961397000000000",
      "endTimeNanos": "1690961397000000000",
      "dataTypeName": "com.google.weight",
      "originDataSourceId": "raw:com.google.weight:cn.fitdays.fitdays:",
      "value": [
        {
          "fpVal": 77.59,
          "mapVal": []
        }
      ],
      "modifiedTimeMillis": "1690963181330"
    }
  ]
}

Fat Percentage

{
  "minStartTimeNs": "1690662651313000000",
  "maxEndTimeNs": "1691008251313000000",
  "dataSourceId": "derived:com.google.body.fat.percentage:com.google.android.gms:merged",
  "point": [
    {
      "startTimeNanos": "1690961397000000000",
      "endTimeNanos": "1690961397000000000",
      "dataTypeName": "com.google.body.fat.percentage",
      "originDataSourceId": "raw:com.google.body.fat.percentage:cn.fitdays.fitdays:",
      "value": [
        {
          "fpVal": 20,
          "mapVal": []
        }
      ],
      "modifiedTimeMillis": "1690963181468"
    }
  ]
}

Nutrition

{
  "minStartTimeNs": "1690662651313000000",
  "maxEndTimeNs": "1691008251313000000",
  "dataSourceId": "derived:com.google.nutrition:com.google.android.gms:merged",
  "point": [
    {
      "startTimeNanos": "1690961397000000000",
      "endTimeNanos": "1690961397000000000",
      "dataTypeName": "com.google.nutrition",
      "originDataSourceId": "raw:com.google.nutrition:com.sillens.shapeupclub:health_platform",
      "value": [
        {
          "mapVal": [
            {
              "key": "fat.total",
              "value": {
                "fpVal": 16.349998474121094
              }
            },
            {
              "key": "sodium",
              "value": {
                "fpVal": 0
              }
            },
            {
              "key": "potassium",
              "value": {
                "fpVal": 0
              }
            },
            {
              "key": "fat.unsaturated",
              "value": {
                "fpVal": 3
              }
            },
            {
              "key": "fat.saturated",
              "value": {
                "fpVal": 13.34999942779541
              }
            },
            {
              "key": "protein",
              "value": {
                "fpVal": 5.25
              }
            },
            {
              "key": "carbs.total",
              "value": {
                "fpVal": 60.45000076293945
              }
            },
            {
              "key": "cholesterol",
              "value": {
                "fpVal": 0
              }
            },
            {
              "key": "calories",
              "value": {
                "fpVal": 412.5
              }
            },
            {
              "key": "sugar",
              "value": {
                "fpVal": 42.599998474121094
              }
            },
            {
              "key": "dietary_fiber",
              "value": {
                "fpVal": 0
              }
            }
          ]
        },
        {
          "intVal": 1,
          "mapVal": []
        },
        {
          "stringVal": "Salted caramel cornetto",
          "mapVal": []
        }
      ],
      "modifiedTimeMillis": "1690805813623"
    }
  ]
}

As you can see, the data is in a format that is not very easy to work with. We need to parse this data and aggregate it into a usable format.

const curatedWeightData = extractBodyData(weightData, 'weight');
const curatedFatPercentageData = extractBodyData(fatPercentageData, 'fatPercentage');
const curatedNutritionData = aggregateNutritionData(extractNutritionData(nutritionData));

After using these mysterious functions, our data will look like this:

console.log(curatedWeightData);
// [{ date: '02/08/2023', weight: 77.5999984741211 }]
console.log(curatedFatPercentageData);
// [{ date: '02/08/2023', fatPercentage: 20 }]
console.log(curatedNutritionData);
// [{ date: '02/08/2023', foods: ['Salted caramel cornetto'], protein: 5.25, fat: 16.349998474121094, carbs: 60.45000076293945, fiber: 0, sugar: 42.599998474121094 }]

Ok now let’s write the functions that do this magic, or better, let’s ask ChatGPT to do it for us.

function nanosToDateString(nanos) {
  const milliseconds = parseInt(nanos, 10) / 1000000;
  return new Date(milliseconds).toLocaleDateString('en-GB');
}

function extractBodyData(jsonArray, type) {
  return jsonArray.point.map((dataPoint) => {
    const date = nanosToDateString(dataPoint.startTimeNanos);
    const data = dataPoint.value[0].fpVal;
    return { date, [type]: data };
  });
}

And for the nutrition data aggregation:

function aggregateNutritionData(nutritionArray) {
  const aggregatedData = {};

  nutritionArray.forEach((nutrition) => {
    const { date, foodName, ...nutritionValues } = nutrition;

    if (!aggregatedData[date]) {
      aggregatedData[date] = {
        date,
        foods: [foodName],
        ...nutritionValues,
      };
    } else {
      aggregatedData[date].foods.push(foodName);

      Object.keys(nutritionValues).forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(nutritionValues, key)) {
          aggregatedData[date][key] += nutritionValues[key];
        }
      });
    }
  });

  return Object.values(aggregatedData);
}

The last thing to do is to merge these 3 arrays into one and sort them by date.

function mergeDataArrays(...arrays) {
  const mergedData = {};

  arrays.forEach((array) => {
    array.forEach((item) => {
      const { date, ...rest } = item;
      if (!mergedData[date]) {
        mergedData[date] = { date, ...rest };
      } else {
        Object.assign(mergedData[date], rest);
      }
    });
  });

  return Object.values(mergedData);
}

console.log(mergeDataArrays(curatedWeightData, curatedFatPercentageData, curatedNutritionData));
// [{"date":"02/08/2023","weight":77.5999984741211,"fatPercentage":20,"foods":["Salted caramel cornetto"],"protein":5.25,"fat":16.349998474121094,"carbs":60.45000076293945,"fiber":0,"sugar":42.599998474121094}]

Alright, time to use this data to get some insights. Actually, we need one more thing before we can do that.

Muscle mass and body fat composition

Before diving into the calculations, we need to understand how muscle and fat are distributed in the body.

I wanted to figure out how many calories are in 1kg of muscle and 1kg of fat, so I started looking for books on Google Books to find the exact composition of muscle and fat tissue, and I did find some information on The Nutritionist Food, Nutrition, and Optimal Health By Robert Wildman.

muscle and fat composition

But I still needed to know what is the energy cost to build that 1kg of muscle and 1kg of fat, so for that, I asked ChatGPT.

Hi. What is the exact composition in percentages of 1kg of skeletal muscle and 1kg of adipose tissue? Also, provide the source of the information. Please give me a short answer, thank you.

Sure! Here’s a short answer for the general composition of 1kg of skeletal muscle and 1kg of adipose tissue:

Skeletal Muscle (1kg):

  • Water: ~75%
  • Protein: ~20%
  • Fats: ~5%
  • Other (minerals, glycogen, etc.): Trace amounts

Adipose Tissue (1kg):

  • Lipids: ~80-90%
  • Water: ~10-20%
  • Protein: ~2%
  • Other (minerals, etc.): Trace amounts

Please note that these values can vary depending on factors like age, sex, diet, and overall health. The information is based on general scientific understanding and can be found in various human physiology and anatomy textbooks, such as “Human Anatomy & Physiology” by Elaine N. Marieb and Katja Hoehn.

And what is the synthesis efficiency in percentage to build 1 gram of muscle protein and 1 gram of lipids? Also, provide the source of the information. Please give me a short answer, thank you.

The synthesis efficiency for building muscle protein and lipids varies widely depending on factors like species, diet, age, and health. However, a general estimate can be provided:

  • For muscle protein, the efficiency is often around 20-25%. This means that only about 20-25% of the amino acids consumed are incorporated into muscle protein.
  • For lipids, the efficiency of conversion from dietary fats can be higher, around 90-95%.

These numbers are rough estimates and can vary significantly. The information is derived from general knowledge of nutritional biochemistry and can be found in textbooks like “Biochemical, Physiological, and Molecular Aspects of Human Nutrition” by Martha H. Stipanuk and Marie A. Caudill, and scientific papers on the subject. Always consult specific research or a professional in the field for more precise information tailored to a particular situation.

Cool. Let’s do some math now!

Muscle calculation

Now that we know a kilogram of muscle is made up of 20% protein and 5% fat. With 1g of protein having 4 calories and 1g of fat containing 9 calories, that means 1kg of muscle has 800 calories from protein and 450 from fat, totaling 1250 calories.

Fat calculation

As for fat, a kilogram is made up of 85% lipids and 2% protein. With 1g of lipids having 9 calories, and 1g of protein having 4 calories, that means 1kg of fat has 7650 calories from lipids and 80 calories from protein, totaling 7730 calories.

Synthesis cost

When it comes to building the essential components of our body, the efficiency varies for different substances.

For proteins, our body’s efficiency is about ~23%. That means for every 1g of protein, which has 4kcal, we need around ~17kcal to make it. Think of it like this: 4kcal goes into the actual amino acids in the protein, and the other 13kcal is the energy used up in the building process.

Now, lipids are a different story. Our body’s a bit more efficient here, at ~93%. So for 1g of lipids, which has 9kcal, it only takes about 10kcal to make it. That’s 9kcal for the amino acids in the lipids, and just 1kcal for the synthesis.

Calculating the energy cost

So, let’s wrap our heads around all these numbers and see what they mean for the energy cost of building muscle and fat.

For muscle, we’ve got 1kg with 200g of protein and 50g of lipids. Since it takes about 17kcal to make 1g of protein and 10kcal for 1g of lipids, the total energy cost for 1kg of muscle is:

  • Protein: 200 x 17 = 3400kcal
  • Fat: 50 x 10 = 500kcal
  • Grand Total: 3400 + 500 = 3900kcal

Now, for body fat, with 1kg having 850g of lipids and 20g of protein, the total energy cost is:

  • Lipids: 850 x 10 = 8500kcal
  • Protein: 20 x 17 = 340kcal
  • Grand Total: 8500 + 340 = 8840kcal

So there you go! With these numbers, I will add the following consts to my code:

const CALORIES_STORED_KG_FAT = 7730;
const CALORIES_BUILD_KG_FAT = 8840;
const CALORIES_STORED_KG_MUSCLE = 1250;
const CALORIES_BUILD_KG_MUSCLE = 3900;

A quick reminder that these are all estimates, and the actual numbers may vary depending on your body composition, diet, and other factors. Also, I’m not a doctor or a nutritionist, so please don’t take this as medical advice.

Calculating the TDEE

Now that we have our parsed weight and fat percentage data and have aggregated it into a usable format, we can start to calculate our TDEE.

To calculate your TDEE, follow these steps:

  1. Get the total number of calories consumed during the 30-day period.
  2. Calculate the weight difference between the start and end of the 30-day period.
  3. Convert the weight difference to calories by multiplying it by the number of muscle/fat gained/lost.
  4. Subtract the result from step 3 from the total calories consumed in the 30-day period.
  5. Divide the result from step 4 by 30 to get your average daily calorie intake, which should be your TDEE.

Here’s the code to calculate TDEE and more using the code:

function calculateStatistics(dataArray) {
  const totalDays = dataArray.length;

  const {
    totalFat,
    totalFiber,
    totalCarbs,
    finalWeight,
    totalProtein,
    totalCalories,
    initialWeight,
    initialFatPercentage,
    finalFatPercentage,
    initialDate,
    finalDate,
  } = accumulateData(dataArray);

  const {
    fatDifference,
    weightDifference,
    muscleDifference,
    fatDifferencePercentage,
  } = calculateWeightDifference(initialWeight, finalWeight, initialFatPercentage, finalFatPercentage);

  const { muscleCalories, fatCalories } = calculateMuscleAndFatCalories(muscleDifference, fatDifference);
  const tdee = calculateTDEE(totalCalories, muscleCalories, fatCalories, totalDays);

  return [
    `*From ${initialDate} to ${finalDate}*\n`,
    `Days Range: ${totalDays}`,
    `TDEE: ${tdee?.toFixed(2)} kcal`,
    `Average Calories: ${(totalCalories / totalDays)?.toFixed(2)} kcal`,
    `Average Protein: ${(totalProtein / totalDays)?.toFixed(2)} g`,
    `Average Carbs: ${(totalCarbs / totalDays)?.toFixed(2)} g`,
    `Average Fat: ${(totalFat / totalDays)?.toFixed(2)} g`,
    `Average Fiber: ${(totalFiber / totalDays)?.toFixed(2)} g`,
    `Weight Difference: ${weightDifference > 0 ? '+' : ''}${weightDifference?.toFixed(2)} kg (${initialWeight?.toFixed(2)} -> ${finalWeight?.toFixed(2)})`,
    `Fat Difference: ${fatDifference > 0 ? '+' : ''}${fatDifference?.toFixed(2)} kg`,
    `Non-Fat Difference: ${muscleDifference > 0 ? '+' : ''}${muscleDifference?.toFixed(2)} kg`,
    `Fat Percentage Difference: ${fatDifferencePercentage > 0 ? '+' : ''}${fatDifferencePercentage?.toFixed(2)}%  (${initialFatPercentage?.toFixed(2)} -> ${finalFatPercentage?.toFixed(2)})`,
  ].join('\n');
}

This function returns a string with details for a 30-day period, including the number of days, TDEE, average daily intake of calories, protein, carbohydrates, fat, and fiber, as well as the weight, fat, non-fat, and fat percentage differences between the start and end of the period.

As for the missing functions accumulateData, calculateWeightDifference and calculateTDEE, here they are:

function accumulateData(dataArray) {
  const lastOccurrence = dataArray[dataArray.length - 1];
  const firstOccurrence = dataArray[0];

  const accumulator = {
    totalCalories: 0,
    totalProtein: 0,
    totalCarbs: 0,
    totalFat: 0,
    totalFiber: 0,
    initialWeight: firstOccurrence?.weight,
    finalWeight: lastOccurrence?.weight,
    initialFatPercentage: firstOccurrence?.fatPercentage,
    finalFatPercentage: lastOccurrence?.fatPercentage,
    initialDate: firstOccurrence?.date || '',
    finalDate: lastOccurrence?.date || '',
  };

  dataArray.forEach((data) => {
    accumulator.totalCalories += data.caloriesConsumed;
    accumulator.totalProtein += data.protein;
    accumulator.totalCarbs += data.carbs;
    accumulator.totalFat += data.fat;
    accumulator.totalFiber += data.fiber;
  });

  return accumulator;
}

function calculateWeightDifference(initialWeight, finalWeight, initialFatPercentage, finalFatPercentage) {
  const weightDifference = finalWeight - initialWeight;
  const initialFat = (initialFatPercentage * initialWeight) / 100;
  const finalFat = (finalFatPercentage * finalWeight) / 100;
  const fatDifference = finalFat - initialFat;
  const muscleDifference = weightDifference - fatDifference;
  const fatDifferencePercentage = finalFatPercentage - initialFatPercentage;

  return { weightDifference, fatDifference, muscleDifference, fatDifferencePercentage };
}

function calculateTDEE(totalCalories, muscleCalories, fatCalories, totalDays) {
  const caloriesDifference = fatCalories + muscleCalories;

  return (totalCalories - caloriesDifference) / totalDays;
}

As you can see, the calculateStatistics function calls the accumulateData function to get the total calories, protein, carbohydrates, fat, and fiber consumed during the 30-day period, as well as the initial and final weight and fat percentage.

Then, it calls the calculateWeightDifference function to calculate the weight, fat, non-fat, and fat percentage differences between the start and end of the period.

Finally, it calls the calculateTDEE function to calculate the TDEE.

Send the data to Telegram

Once you have calculated your TDEE, you may want to share this information with others, such as a personal trainer or a friend. One easy way to do this is by sending the data to a Telegram group chat. This can be done using the Telegram Bot API.

Here’s an example code snippet that sends the TDEE result to a Telegram group chat:

const text = calculateStatistics(agragatedData);
await nodeFetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_ID}/sendMessage`, {
  method: 'POST',
  headers: {
  'Content-Type': 'application/json',
  },
  body: JSON.stringify({
  text,
  chat_id: process.env.TELEGRAM_GROUP_ID,
  parse_mode: 'markdown',
  }),
});

In this code, we are using the node-fetch package to make an HTTP POST request to the Telegram Bot API’s sendMessage method. We pass in the TDEE result as the text field in the request body, along with the chat_id of the Telegram group we want to send the message. We also set the parse_mode field to markdown to enable formatting in the message.

Note that in order to use this code, you will need to set the TELEGRAM_BOT_ID and TELEGRAM_GROUP_ID environment variables to the appropriate values.

Bonus

You can copy the text you got from this script and send it to ChatGPT for even more insights 👀.

Hi. What do you think of these data from my personal health track from the past 30 days or so? Can you give me a short and concise insight into what I’m doing well and where I should improve? Thanks <- My Text Data ->

Sure! Based on the data you’ve provided, here’s a brief analysis:

What You’re Doing Well: Caloric Balance: Your average caloric intake is very close to your Total Daily Energy Expenditure (TDEE), which suggests that you are maintaining your weight effectively. Weight Loss: You’ve lost 0.80 kg over the past month, which is a healthy and sustainable rate of weight loss if that’s your goal. Protein Intake: Your average protein intake seems to be in a good range, especially if you’re engaging in physical activities that require muscle repair and growth.

Areas for Improvement: Fat Percentage Increase: The increase in fat percentage and fat mass might be a concern if you are aiming to reduce body fat. This could be related to the type of foods you are consuming or the balance of macronutrients. Fiber Intake: Your average fiber intake is a bit on the lower side. Increasing fiber intake by consuming more whole grains, fruits, and vegetables can aid in digestion and may help with weight management.

Remember, these insights are based on the data provided and general guidelines. Individual needs can vary widely, so it’s always a good idea to consult with a healthcare provider or nutrition professional for personalized advice.

Conclusion

That’s it, now you will always know precisely what your TDEE is. You can use this information to adjust your diet and exercise routine to achieve your fitness goals, you can even put this script into a GitHub Actions workflow to run it automatically every day and send the results to a Telegram group chat.

If you want to see the full code, you can find it in my GitHub, which includes a GitHub Action workflow that runs the script every day and sends the results to a Telegram chat, so feel free to simply clone the repository and use it.

I hope you found this article useful. If you have any questions or comments, feel free to reach out or leave your feedback in the comments section below.

Tags:


Post a comment

Comments

No comments yet.