![[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 %%