![[IMG-Automate Your Gains-20250713163417973.png|500]]
---
In [[Automate Your Gains, Part 4 - Read & Display Data from Google Sheets in Your App|Automate Your Gains - Part 4]], the "read path" was built which allowed the app to fetch and display historical workout data. This next step will focus on creating a functional logbook and automated progressive overload logic. The new code will analyze a user's performance on a given exercise and automatically decides whether they are ready to progress to the next level of difficulty for their next session. The logic is triggered after a user submits their workout.
</br>
---
### **Section 1: The Progression Logic Explained**
**The Rule**
<p>
When a user completes a workout at or below a specific RPE threshold (e.g., RPE 8), they will have demonstrated that the current difficulty is manageable, and they are ready for a greater challenge.</p>
**The Action**
<p>
When this condition is met, the application will increase the `CurrentStepNumber` for that specific exercise in the `UserExerciseProgression` sheet.</p>
**The Feedback Loop**
<p>
The next time the user loads that workout plan, the app will use this new, higher step number to look up the prescribed sets, reps, and weight from the `ProgressionModelSteps` sheet. This means the user is automatically assigned a more challenging workout, creating a cycle of continuous, performance-based improvement.</p>
<p>
If the user logs an RPE _above_ the threshold, the app keeps them on the same step number, allowing them to attempt it again next time.
</p>
---
### **Section 2: The Trigger - Kicking Off the Update**
This entire process begins after a workout is successfully saved. In [[Automate Your Gains, Part 3 - Save Form Data to Google Sheets with Apps Script|Part 3]], the `processLogForm` function in `main.js` was created. At the end of this function a call to a new function: `updateUserProgressionAfterLog` was made.
>[!tip]- main.js code snippet
>```js
>// From main.js inside the processLogForm function
>
>// ... code to log the workout entry ...
>logWorkoutEntryToSheet(logEntry, sheets
> .workoutLogSheet,
> workoutLogHeaders);
>SCRIPT_CACHE.remove(
> `workoutLogData_singleUser`);
>
>// --- This is our trigger ---
>updateUserProgressionAfterLog(
> userId,
> formData.templateId,
> formData.exerciseId,
> formData.progressionModelId,
> parseInt(formData
> .performedStepNumber),
> parseInt(formData.rpe),
> formData.repsPerformed,
> formData.actualAmrapReps ?
> parseInt(formData
> .actualAmrapReps) : null,
> sheets // Pass all sheets for convenience
>);
>
>return {
> message: `${
> formData.exerciseName || formData.exerciseId
>} logged successfully!`,
>};
>```
</br>
This function passes all the necessary information, such as who the user is, what they did, and how difficult the exercise workout felt, to our progression engine.
</br>
---
### **Section 3: The Engine - The `updateUserProgressionAfterLog` Function**
This function is the heart of the app's “intelligence.” It contains the logic for retrieving the rules, evaluating performance, and updating the user's progress.
>[!tip]- main.js code snippet
>```js
> // Add this function to main.js
>
>
>function updateUserProgressionAfterLog(
> userId,
> templateId,
> exerciseId,
> progressionModelId,
> performedStepNumber,
> loggedRPE,
> repsPerformedDisplay,
> actualAmrapReps,
> appSheets
>) {
>
> try {
> // 1. Get all necessary data from our sheets
>
> const {
>
> userExerciseProgressionSheet,
> progressionModelsSheet
>
> } = appSheets;
> const {
>
> data: uepData,
> headerMap:
> uepHeaderMap,
> headers: uepHeaders
> } =
> getSheetDataWithHeadersAndMap(
> userExerciseProgressionSheet,
> "userExerciseProgressionData"
> );
> const {
>
> data: pmData,
> headerMap: pmHeaderMap
> } =
> getSheetDataWithHeadersAndMap(
> progressionModelsSheet,
> "progressionModelsData");
> // 2. Find the rules for the current progression model
>
> const modelInfo =
> findRowInDataByCriteria(
> pmData, pmHeaderMap, {
>
> ProgressionModelID: progressionModelId
> });
> if (!modelInfo)
> return; // Exit if the model isn't found
> // e.g., "loggedRPE <= 8"
>
> const triggerLogic = String(
> modelInfo[pmHeaderMap
> .TriggerConditionLogic
> ]).trim();
> // 3. Find the user's current progression state for this exercise
>
> let userProgRowIndex = uepData
> .findIndex(row =
> /* ... criteria to find the user's row ... */
> );
> let currentUepData;
> let isNewEntry = false;
> if (userProgRowIndex !== -1) {
> // Existing user progress found
>
> currentUepData = {};
> uepHeaders.forEach((header,
> index) =
> currentUepData[
> header] =
> uepData[
> userProgRowIndex
> ][index]);
> }
> else {
> // No existing progress found, create a new entry starting at step 1
>
> isNewEntry = true;
> currentUepData = {
> // ... default values for a new progression entry ...
>
> CurrentStepNumber: 1,
>
> };
> }
> // 4. THE DECISION: Compare logged RPE to the trigger logic
>
> let meetsTriggerCondition =
> false;
> if (triggerLogic
> .toLowerCase() ===
> "loggedrpe <= 8" &&
> loggedRPE <= 8) {
>
> meetsTriggerCondition =
> true;
> }
> // 5. THE UPDATE: Advance the step number if the condition was met
>
> if (meetsTriggerCondition) {
>
> let nextStepNumber =
> performedStepNumber +
> 1;
> // (Includes more advanced logic for handling the end of a cycle)
>
> currentUepData
> .CurrentStepNumber =
> nextStepNumber;
> Logger.log(
> ` Advanced to Step: ${currentUepData.CurrentStepNumber}`
> );
> }
> else {
> // RPE was too high, stay on the same step
>
> currentUepData
> .CurrentStepNumber =
> performedStepNumber;
> Logger.log(
> ` RPE condition not met. Staying on Step: ${currentUepData.CurrentStepNumber}`
> );
> }
> currentUepData.LastWorkoutRPE =
> loggedRPE;
> currentUepData
> .DateOfLastAttempt =
> new Date();
> // 6. SAVE THE CHANGES: Write the updated data back to the sheet
>
> if (isNewEntry) {
>
> const newRowValues =
> uepHeaders.map(header =
> currentUepData[
> header] || null
> );
> userExerciseProgressionSheet
> .appendRow(
> newRowValues);
> }
> else {
>
> const rowToUpdate =
> userProgRowIndex + 2;
> uepHeaders.forEach((header,
> colIndex) = {
>
> if (currentUepData[
> header
> ] !==
> undefined) {
>
> userExerciseProgressionSheet
> .getRange(
> rowToUpdate,
> colIndex +
> 1)
> .setValue(
> currentUepData[
> header
> ]
> );
> }
> });
> }
> SCRIPT_CACHE.remove(
> "userExerciseProgressionData"
> ); // Clear cache to ensure next load is >fresh
>
> }
> catch (error) {
>
> Logger.log(
> `ERROR in updateUserProgressionAfterLog: ${error.message}`
> );
> }
>}
</br>
This function performs the logic that was outlined before:
- Gets the rules from `ProgressionModels`
- Finds the user's current state in `UserExerciseProgression`
- Checks if the logged RPE meets the trigger condition
- Updates the `CurrentStepNumber` accordingly before saving the data back to the sheet.
</br>
### **Conclusion**
The application can now be considered "smart." It is now able to use user performance data to make intelligent recommendations for future workouts.
The final post of this series, [[Automate Your Gains, Part 6 - When to Scale Your Google Apps Script Project|Part 6]], will take a step back to reflect on the project as a whole. It will explore the discoveries made, the pros and cons of this architecture, and potential future improvements.
</br>
---
## Resources
</br>
### Github and Demo
You can find the completed code for the entire project, including all features and documentation, on GitHub.
- **[View Project on GitHub](https://github.com/drusho/workout-logger-google-apps-script)**
- **[Try the Live Web App Demo](https://script.google.com/macros/s/AKfycbwiQyKHvKap9oiKqSpAhFdbq9xH36wOZCr0a6QRZEgSL0ErCWXhaUoVAIPcqD1zM_2I/exec)**
</br>
### Related Articles
Check out the other articles from **Automate Your Gains** series:
%% DATAVIEW_PUBLISH_CONVERT start
```dataview
LIST WITHOUT ID
"**" + file.link + "** </br>" + description + "</br></br>"
FROM "07 - Publish - Obsidian"
WHERE
publish = true
AND file.name != "About Me"
AND file.name != "Home"
AND file.name != "Series - Automate Your Gains"
AND series = "Automate Your Gains"
SORT date DESC
```
%%
- **[[07 - Publish - Obsidian/Articles/A Deep Dive into the 'Automate Your Gains' Workout App UI & Features.md|A Deep Dive into the 'Automate Your Gains' Workout App UI & Features]]** </br>Take a tour of a custom workout logger built with Google Apps Script. See its mobile-friendly UI, dynamic workout planning, "last workout recall," and automated progression features in action.</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 1 - Plan a Custom Workout App with Google Sheets.md|Automate Your Gains, Part 1 - Plan a Custom Workout App with Google Sheets]]** </br>A fitness app project that shows how to plan a smart workout logger using Google Apps Script and Google Sheets to automate your training.</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 2 - Build a Workout App UI with Apps Script.md|Automate Your Gains, Part 2 - Build a Workout App UI with Apps Script]]** </br>Turn a Google Sheet into a database and build a mobile-friendly UI with Google Apps Script. A step-by-step guide to creating the foundation for the workout logger.</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 3 - Save Form Data to Google Sheets with Apps Script.md|Automate Your Gains, Part 3 - Save Form Data to Google Sheets with Apps Script]]** </br>Connect front-end to your back-end. This guide covers using google.script.run to capture HTML form data and save it directly to Google Sheets, creating a complete "write path."</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 4 - Read & Display Data from Google Sheets in Your App.md|Automate Your Gains, Part 4 - Read & Display Data from Google Sheets in Your App]]** </br>Close the data loop for the workout app. Fetch, filter, and sort data from a Google Sheet backend and display it dynamically into web app's UI for a richer user experience.</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 5 - Code Smart Automation for Your Fitness App.md|Automate Your Gains, Part 5 - Code Smart Automation for Your Fitness App]]** </br>Elevate the app from a simple logger to a smart training partner. Code automation logic that analyzes user performance (RPE) to recommend workout progressions.</br></br>
- **[[07 - Publish - Obsidian/Articles/Automate Your Gains, Part 6 - When to Scale Your Google Apps Script Project.md|Automate Your Gains, Part 6 - When to Scale Your Google Apps Script Project]]** </br>A complete review of the workout app project. Covers the pros and cons of using Google Sheets as a database, ideas for future features, and how to know when it's time to migrate.</br></br>
- **[[07 - Publish - Obsidian/Posts/Series/Automate Your Gains.md|Automate Your Gains]]** </br>Articles related to creating a workout logger web application using Google Sheets and Apps Script</br></br>
%% DATAVIEW_PUBLISH_CONVERT end %%