DECLARE @LocalRID int DECLARE @AveragingPeriod int DECLARE @WarmupSecond int DECLARE @PowerWeightingFactor float SET @LocalRID = 14847 SET @AveragingPeriod = 30 SET @PowerWeightingFactor = 4 /****1. Calculate a 30 second SMA for watts The SMo2 recorded for any given second pretty obviously results from effort incurred prior to that second. To that end, this formula still makes use of watts averaging. Both the Coggan & Skiba formulas involve an averaging period to account for the body's reaction to the physiologic stress of a ride, although they disagree as to averaging method and averaging period. The averaging method doesn't seem to make too much difference overall in terms of deriving weighted average power (the exponentially weighted moving average (EMA) will be a a watt or 2 less). However the EMA calculation does involve significantly more memory and processor time so I decided to use a simple moving average (SMA) for this. Changing the averaging period from 30 to 25 seconds resulted in as much as a 5 watt gain to the SMA for my rides. As a user of the formula I like this because it would make the rides seem tougher! For my own usage though I decided to make the averaging period 30 seconds because this period results in a better correlation to Sm02. For the implementation into the KOM Informatics system I made averaging period a parameter. What if an optimal choice ends up being dependent on an individual's physiology? */ SELECT T.SecondsQty, Watts, SMAWatts, S.SMo2 INTO #BaseSet FROM (SELECT SecondsQty, Watts, AVG(CAST(MAWatts AS FLOAT)) AS SMAWatts FROM (SELECT SecondsQty, Watts FROM YourBigRideTable WHERE RID = @LocalRID AND SecondsQty >= @AveragingPeriod) AS R INNER JOIN (SELECT SecondsQty AS MASecondsQty, Watts AS MAWatts FROM YourBigRideTable WHERE RID = @LocalRID ) AS P ON R.SecondsQty >= P.MASecondsQty AND P.MASecondsQty > R.SecondsQty - @AveragingPeriod GROUP BY SecondsQty,Watts) AS T INNER JOIN (SELECT SecondsQty, SMo2 FROM YourBigRideTable WHERE RID = @LocalRID) AS S ON T.SecondsQty = S.SecondsQty /****2. Calculate the earliest point in the ride where the average SmO2 is attained After looking at a bunch of ride graphs I realized that warmup periods for me are characterized by an association between low SmO2 values and low Watts. My instinct here is that not a lot of fitness building work is occurring in this stage of the ride and that including this data in the later stages of this might unfairly dilute the results. */ --@WarmupSecond is the earliest time SMo2 reaches ride average SELECT @WarmupSecond = MIN(SecondsQty) FROM #BaseSet WHERE SMo2 >= (SELECT Avg(SMo2) AS AvgSMo2 FROM #BaseSet) /**** 3. Calculate average SMA watts per SMo2 Level The formula figures out the worth of a change in SMo2 Level in terms of SMA watts. The first step is to calculate the average SMA watts per SMo2 Level for all of those rows in the BaseSet where the seconds > WarmupSecond. If the results are returned in SMo2 ascending order then for most of my rides it's pretty easy to see the pattern of higher SMA watts associated with lower SMo2 levels. */ SELECT ROW_NUMBER() OVER(Order By SMo2 ASC) AS RowNum, SMo2, AVG(SMAWatts) AS AvgSMAWattsPerSMo2Level INTO #AvgSMAWattsPerSMo2LevelSet FROM #BaseSet WHERE SecondsQty > @WarmupSecond GROUP BY SMo2 /**** 4. Calculate difference in SMA watts for contiguous SMo2 levels The next step in the formula is to calculate the difference in average SMA watts for contiguous SMo2 levels. First calculate the CountToAverage. For example assume that the lowest SMo2 level in your ride is 65.5 and your second lowest is 65.6. That would work out to (65.6 - 65.5) * 10 or 1 Another example that skips levels: assume that the lowest SMo2 level in your ride is 65.5 and your second lowest is 65.7. That would work out to (65.7 - 65.5) * 10 or 2 For my data 95% + of the time CountToAverage works out to 1.0. For those times when it doesn't CountToAverage allows a little more weight to those rows. Next calculate the average watts associated with the difference in SMo2 levels (AvgSMAWattsPerSMo2LevelDiff) For example assume that the lowest SMo2 Level in your ride is 65.5 and your second lowest is 65.6. Subtract the AvgSMAWatts (250 AvgSMAWatts) associated with the lowest SMo2 Level from the AvgSMAWatts associated with the second lowest SMo2 Level (240 SMAWatts). The AvgSMAWattsPerSMo2LevelDiff for this row would be -10 Do the same calculations for each set of contiguous SMo2 Levels. The results of each calculation are stored in the AvgSMAWattsPerSMo2LevelDiff column. If you ride like me, then for most rides, and especially interval rides you will generally see a drop in AvgSMAWattsPerSMo2LevelDiff as SMo2 ascends. However, the drop won't be steady, and for some of the contiguous pairs there will be a gain instead of a drop. */ SELECT *, (T2.SMo22 - T1.SMo21) * 10 AS CountToAverage, -- The number of units apart SMo21 and SMo22 are. (T2.AvgSMAWattsPerSMo2Level2 - T1.AvgSMAWattsPerSMo2Level1) AS AvgSMAWattsPerSMo2LevelDiff INTO #AvgSMAWattsPerSMo2LevelDiffSet FROM (SELECT RowNum AS RowNum1, SMo2 AS SMo21, AvgSMAWattsPerSMo2Level AS AvgSMAWattsPerSMo2Level1 FROM #AvgSMAWattsPerSMo2LevelSet) AS T1 INNER JOIN (SELECT RowNum AS RowNum2, SMo2 AS SMo22, AvgSMAWattsPerSMo2Level AS AvgSMAWattsPerSMo2Level2 FROM #AvgSMAWattsPerSMo2LevelSet) AS T2 ON T2.RowNum2 = T1.RowNum1 + 1 /**** 5. Calculate the average AvgSMAWattsPerSMo2LevelDiff for the entire ride This number is what the a change in SMo2 Level is worth in watts, on average, for the ride. For 92% of my rides, this is a negative number. The higher watts are associated with lower SMo2 Levels. It's easy to see this in real time using the Humon Hex app or watching the SMo2 readings drop on the ELEMNT while doing intervals. For 8% of my rides (endurance involving mostly zone 2 efforts), this number is positive meaning that higher watts are associated with higher SMo2 Levels. The problem we're trying to solve - where a formula doesn't do full justice to rides that involve high intensity efforts - isn't occurring here. Higher watts are not resulting in lower SmO2 levels and presumably the increased lactate associated with those lower levels. Therefore we won't assign a positive watts adjustment at all for this type of ride. It'll be a negative adjustment based on how close the recorded SmO2 is to the minimum SmO2. The closer it is, the smaller the adjustment. */ /**** 6. Calculate the average SMo2 for the ride The average SMo2 for the ride will be used in the next step as a tipping point to decide whether to apply a positive or negative SMo2 based watts differential in the next step. This is based on the entire ride. (Implemented as a join in step 7) */ /**** 7. Calculate the SMo2AdjustedWatts & SMo2WattsDifferential for the ride Next the formula calculates the SMo2AdjustedWatts & SMo2WattsDifferential for the ride. 1. First, evaluate the overall AvgSMAWattsPerSMo2LevelDiff from step 5. When AvgSMAWattsPerSMo2LevelDiff <= 0 then this is a harder ride where as SMo2 increases, watts decrease. If this is the case for the ride then For each second of the ride: a. Evaluate whether SMo2 <= AvgSMo2 for that second. 1. If it is then we want to apply a positive SMo2WattsAdjustment as a lower then average SMo2 generally means we're working harder. Take the absolute value of the AvgSMAWattsPerSMo2LevelDiff and multiply that by the AvgSMo2 - SMo2. The second term just evaluates how far from the average the SMo2 for the current second is. The further away from average, the lower the SMo2, the greater the multiplier effect. (ABS(AvgSMAWattsPerSMo2LevelDiff) * (AvgSMo2 - SMo2)) is the SMo2WattsDifferential for the second. To get the SMo2AdjustedWatts for the second, just add the raw watts value for the second. 2. If SMo2 > AvgSMo2 we want to apply a negative SMo2WattsAdjustment as a higher then average SMo2 generally means we aren't working as hard. Take the AvgSMAWattsPerSMo2LevelDiff and multiply that by the SMo2 - AvgSMo2. The second term just evaluates how far from the average the SMo2 for the current second is. The further away from average, the higher the SMo2, the greater the multiplier effect. (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - AvgSMo2)) is the SMo2WattsDifferential for the second. To get the SMo2AdjustedWatts for the second, just add the raw watts value for the second. When AvgSMAWattsPerSMo2LevelDiff > 0: Endurance ride where as SMo2 increases watts increase. This case is happening for me about 8% of the time. The problem we're trying to solve - where a formula doesn't do full justice to rides that involve high intensity efforts - isn't occurring here. Higher watts are not resulting in lower SmO2 levels and presumably the increased lactate associated with those lower levels. Therefore we won't assign a positive watts adjustment at all for this type of ride. It'll be a negative adjustment based on how close the recorded SmO2 is to the minimum SmO2. The closer it is, the smaller the adjustment. 1. SMo2AdjustedWatts = Watts - (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - MinSMo2)) for this case. The watts adjustment is (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - MinSMo2)) */ SELECT *, SMAWatts - Watts AS SMAWattsDifferential, AvgSMo2 - SMo2 AS SMo2Diff, CASE WHEN AvgSMAWattsPerSMo2LevelDiff <= 0 THEN -- Harder ride where as SMo2 increases, watts decrease CASE WHEN SMo2 < AvgSMo2 THEN Watts + (ABS(AvgSMAWattsPerSMo2LevelDiff) * (AvgSMo2 - SMo2)) --We want to credit watts here as a low SMo2 means we're working harder. Using ABS on --the first term ensures a positive, and the WHEN stipulates that the second term -- will result in a positive as well. A positive * a postive results in a positvie WHEN SMo2 > AvgSMo2 THEN Watts + (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - AvgSMo2)) --We want to debit watts here as a high SMo2 means we're not working as hard --The first term is a negative, so that means the second term has to be a positive --for the final result to be negative. WHEN SMo2 = AvgSMo2 THEN Watts END WHEN AvgSMAWattsPerSMo2LevelDiff > 0 THEN Watts - (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - MinSMo2)) -- Endurance ride where as SMo2 increases watts increase. This case is happening for me about 8% of the time. -- The problem we're trying to solve - where a formula doesn't do full justice to rides that involve high -- intensity efforts - isn't occurring here. Higher watts are not resulting in lower SmO2 levels -- and presumably the increased lactate associated with those lower levels. Therefore we won't -- assign a positive watts adjustment at all for this type of ride. It'll be a negative adjustment -- based on how close the recorded SmO2 is to the minimum SmO2. The closer it is, the smaller the --adjustment. END AS SMo2AdjustedWatts, CASE WHEN AvgSMAWattsPerSMo2LevelDiff < 0 THEN CASE WHEN SMo2 < AvgSMo2 THEN (ABS(AvgSMAWattsPerSMo2LevelDiff) * (AvgSMo2 - SMo2)) WHEN SMo2 > AvgSMo2 THEN (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - AvgSMo2)) WHEN SMo2 = AvgSMo2 THEN 0 END WHEN AvgSMAWattsPerSMo2LevelDiff > 0 THEN (AvgSMAWattsPerSMo2LevelDiff * (SMo2 - MinSMo2)) WHEN AvgSMAWattsPerSMo2LevelDiff = 0 THEN 0 END AS SMo2WattsDifferential INTO #SMo2AdjustedWattsCalculator FROM #BaseSet JOIN (SELECT CAST(SUM(AvgSMAWattsPerSMo2LevelDiff)/SUM(CountToAverage) AS FLOAT) AS AvgSMAWattsPerSMo2LevelDiff FROM #AvgSMAWattsPerSMo2LevelDiffSet) AS T ON 1=1 JOIN (SELECT Avg(SMo2) AS AvgSMo2 FROM #BaseSet) AS A ON 1=1 JOIN (SELECT Min(SMo2) AS MinSMo2 FROM #BaseSet) AS B ON 1=1 ORDER BY SMo2 /**** 8. Weight the higher watts using an exponential boost and derive the SMo2 Adjusted Weighted Average Power Both SMA and EMA use a "power dependent intensity weighting factor derived directly from a plot of blood lactate concentration as a percentage of concentration at threshold against % of threshold power. His [Coggan's] work indicated a near 4th power relationship between the two." This formula continues to use this insight. I made the power dependent intensity weighting factor a parameter. If it ends up being there are individual differences the code will adapt automatically. The steps to attain this are 1. For rows in which the SMo2AdjustedWatts >= 0: Raise the SMo2AdjustedWatts for each second to the @PowerWeightingFactor power For rows in which the SMo2AdjustedWatts < 0: Raise the SMo2AdjustedWatts for each second to the @PowerWeightingFactor power, then subtract this number from 0 2. Calculate the average of the values in step 1 3. Calculate the @PowerWeightingFactor root on the number from step 2. The SMo2WeightedAveragePower for the ride is the number from step 3 */ SELECT @LocalRID AS RID, * FROM (SELECT POWER(AVG(SMo2AdjustedWatts4), 1/@PowerWeightingFactor) AS SMo2WeightedAveragePower, POWER(AVG(SMAWatts4), 1/@PowerWeightingFactor) AS WeightedAveragePower FROM (SELECT CASE WHEN SMo2AdjustedWatts > 0 THEN POWER(SMo2AdjustedWatts, @PowerWeightingFactor) ELSE 0 - POWER(SMo2AdjustedWatts, @PowerWeightingFactor) END AS SMo2AdjustedWatts4, POWER(SMAWatts, @PowerWeightingFactor) AS SMAWatts4 FROM #SMo2AdjustedWattsCalculator) AS T) AS J JOIN (SELECT CAST(SUM(AvgSMAWattsPerSMo2LevelDiff)/SUM(CountToAverage) AS FLOAT) AS AvgSMAWattsPerSMo2LevelDiff FROM #AvgSMAWattsPerSMo2LevelDiffSet) AS A ON 1=1 DROP TABLE #BaseSet DROP TABLE #AvgSMAWattsPerSMo2LevelSet DROP TABLE #AvgSMAWattsPerSMo2LevelDiffSet DROP TABLE #SMo2AdjustedWattsCalculator