# Wattage predictions See: * [concept2_prediction.py](concept2_prediction.py) * [concept2_prediction.js](concept2_prediction.js) ## Zones See: * Indoor Rowing Training Guide, Version 2 by Terry O’Neill and Alex Skelton. * [Interactive_Training_Plans.xlsx](Interact_Training_Plans.xlsx) Heart rate zones are calculated based on HRR calculated from: Resting heart rate (RHR) 58 Maximum heart rate (MHR) 165 Heart rate reserve (HRR) (MHR minus RHR) 107 | Training Band | s.min-1 | %HRR | %2k (W) | range | |------------------------------|---------|--------|---------------|-------| | Oxygen Utilisation 2 (UT2) | 16-20 | 65-70 | 45-60 | 15% | | Oxygen Utilisation 1 (UT1) | 20-24 | 70-80 | 60-70 | 10% | | Anaerobic Threshold (AT) | 24-28 | 80-85 | 70-80 | 10% | | VO2 Max/Transport (TR) | 28-32 | 85-95 | 80-105 | 25% | | Peak Power/Anaerobic (AN) | 32+ | 95-100 | 105-115 | 10% | *s.min-1 is scientific notation for strokes/minute **%2k (w) is the % of power output, measured in watts of a 2000m test – from this it is possible to calculate pace (time/500m) ***HRR% is Heart Rate Reserve %. When resting and maximum heart rates are know it is possible to calculate the heart rate at which one should train to have the most appropriate effect on the body’s systems. ## Example 1 ### Heart rate 132 for 110W after 10 minutes UT2 is 65%-70%, so 128-133 bpm. 132 is 4 (132 minus 128) into UT2 range of 5 (133 minus 128) UT2 power is 45%-60% of max 2k power. UT2 power range is 15% (60% minus 45%) so 132 bmp should be 4 fifths into that 15% = 12% so expected power is 110W/(45%+12%) = 193W ## Example 2 ### Take the result of example 1 and a zone and provide a suitable training session Given zone: UT2 is midpoint of range: 0.5*(45+60) = 52.5% of 192W = 101.3W Given zone: AT: 0.5*(70+80) = 75% of 193W = 144.7W ## Examples 3 ### Return a training zone from a [training table](Interactive_Training_Plans.xlsx) See: [parse_ITP_spreadsheet.py](parse_ITP_spreadsheet.py) for a pandas dataframe parsing and querying the spreadsheet. Given a level 1-5, a session number and a week number return the associated session eg 3x16'UT1 #### Available Levels Level 1 | 3 Sessions | 26 Weeks Level 2 | 3 Sessions | 26 Weeks Level 2 | 4 Sessions | 26 Weeks Level 3 | 4 Sessions | 26 Weeks Level 4 | 4 Sessions | 26 Weeks Level 4 | 5 Sessions | 26 Weeks Level 4 | 6 Sessions | 26 Weeks Level 5 | 6 Sessions | 26 Weeks Level 5 | 7 Sessions | 26 Weeks Level 5 | 8 Sessions | 26 Weeks cat level-1_3-session_26-weeks.csv ```csv "Interactive 2,000m Programme",,, 2000m 26 Week Training Programme,,, "Athlete Level 1, 3 Sessions a Week",,, Week,Session 1,Session 2,Session 3 1,TEST,12'UT2,15'UT2 2,14'UT2,16'UT2,18'UT2 3,17'UT2,19'UT2,20'UT2 4,10'UT1,25'UT2,30'UT2 5,12'UT1,18'UT1,8'AT 6,30'UT2,2x10'UT1,2x7'AT 7,15'UT1,20'UT2,7'AT 8,18'UT1,25UT2,9'AT 9,4x2'TR,30'UT2,2X12'UT1 10,2x2'TR,15'UT1,20'UT2 11,4x2'TR,18'UT1,25'UT2 12,6x2'TR,2X12'UT1,30'UT2 13,TEST,2x3'TR,2x7'AT 14,4x2'TR,16'UT1,25'UT2 15,2x4'TR,2x12'UT1,3x7'AT 16,6x2'TR,2x8'UT1,20'UT2 17,2x9'AT,18'UT1,30'UT2 18,2x10'AT,3x2'TR,20'UT1 19,4x1.5'AN,2x12UT1,2x8'AT 20,3x2'TR,25'UT1,2x9'AT 21,2x4'TR,30'UT2,2x10'AT 22,2x4'TR,15'UT1,2x7'AT 23,30'UT2,18'UT1,2x9'AT 24,3x2'TR,30'UT2,2x12'UT1 25,5x2'TR,6x1.5'AN,3x3'TR 26,2x1.5'AN,3x45s AN,RACE ``` Interactive 2,000m Programme 2000m 26 Week Training Programme Athlete Level 5, 8 Sessions a Week | Week | Session 1 | Session 2 | Session 3 | Session 4 | Session 5 | Session 6 | Session 7 | Session 8 | |------|-----------|-----------|-----------|-----------|-----------|-----------|-----------|-----------| | 1 | TEST | 2x15'UT1 | 55'UT2 | 2x18'UT1 | 68'UT2 | 3x15'UT1 | 55'UT2 | 2x15'UT1 | | 2 | 65'UT2 | 2x21'UT1 | 72'UT2 | 3x16'UT1 | 80'UT2 | 4x13'UT1 | 72'UT2 | 3x12'UT1 | | 3 | 76'UT2 | 3x17'UT1 | 85'UT2 | 3x19'UT1 | 90'UT2 | 3x20'UT1 | 85'UT2 | 2x25'UT1 | | 4 | 45'UT2 | 2x15'UT1 | 2x6'AT | 3x12'UT1 | 2x7'AT | 3x15'UT1 | 2x10'AT | 3x12'UT2 | | 5 | 65'UT2 | 2x24'UT1 | 4x5'AT | 3x16'UT1 | 3x8'AT | 3x18'UT1 | 2x12'AT | 3x16'UT1 | | 6 | 76'UT2 | 2x25'UT1 | 3x7'AT | 3x19'UT1 | 3x10'AT | 5x12'UT1 | 3x8'AT | 3x18'UT1 | | 7 | 45'UT2 | 2x15'UT1 | 2x8'AT | 3x12'UT1 | 2x10'AT | 3x15'UT1 | 2x9'AT | 2x3'TR | | 8 | 65'UT2 | 3x14'UT1 | 2x9'AT | 75'UT2 | 2x8' AT | 4x14'UT1 | 2x7'AT | 3x4'TR | | 9 | 76'UT2 | 3x17'UT1 | 3x7'AT | 90'UT2 | 4x5'TR | 4X15'UT1 | 3x8'AT | 4x4'TR | | 10 | 45'UT2 | 2x15'UT1 | 2x8'AT | 60'UT2 | 4x2'TR | 3x12'UT1 | 2x9'AT | 3x3'TR | | 11 | 65'UT2 | 3x15'UT1 | 2x10'AT | 75'UT2 | 4x3'TR | 3x15'UT1 | 2x10'AT | 4x3'TR | | 12 | 75'UT2 | 4x15'UT1 | 3x8'AT | 90'UT2 | 4x4'TR | 4x12'UT1 | 3x10'AT | 5x4'TR | | 13 | TEST | 2X15'UT1 | 2x8'AT | 60'UT2 | 3x1'AN | 3x15'UT1 | 2x8'AT | 4x2'TR | | 14 | 65'UT2 | 3X15'UT1 | 2x10'AT | 75'UT2 | 4x1.5'AN | 4x12'UT1 | 3x7'AT | 6x2'TR | | 15 | 75'UT2 | 5x12'UT1 | 3x10'AT | 90'UT2 | 6x1'AN | 5x12'UT1 | 3x10'AT | 6x4'TR | | 16 | 45'UT2 | 2X15'UT1 | 2x9'AT | 60'UT2 | 8x45sAN | 4x14'UT1 | 2x10'AT | 5x2'TR | | 17 | 65'UT2 | 3X15'UT1 | 3x10'AT | 75'UT2 | 6x1.5'AN | 3x12'UT1 | 3x8'AT | 6x3'TR | | 18 | 75'UT2 | 4x15'UT1 | 4x8'AT | 90'UT2 | 8x1'AN | 2x15'UT1 | 4x9'AT | 7x4'TR | | 19 | 45'UT2 | 2X15'UT1 | 2x10'AT | 60'UT2 | 4x1.5'AN | 3x12'UT1 | 3x8'AT | 6x2'TR | | 20 | 65'UT2 | 3X15'UT1 | 3x12'AT | 75'UT2 | 6x1'AN | 3x15'UT1 | 3x10'AT | 7x3'TR | | 21 | 75'UT2 | 5x12'UT1 | 5x8'AT | 90'UT2 | 8x45sAN | 5x12'UT1 | 4x10'AT | 8x4'TR | | 22 | 45'UT2 | 2x15'UT1 | 2x10'AT | 60'UT2 | 8x1.5'AN | 3x12'UT1 | 2x7'AT | 4x2'TR | | 23 | 65'UT2 | 3x15'UT1 | 3x8'AT | 75'UT2 | 10x45sAN | 3x15'UT1 | 3x7'AT | 4x3'TR | | 24 | 76'UT2 | 4x15'UT1 | 4x8'AT | 90'UT2 | 2(6x1')AN | 4x15'UT1 | 2x10'AT | 4x4'TR | | 25 | 50'UT2 | 2x12'UT1 | 6' AT | 40'UT2 | 2x1.5'AN | 20'UT2 | 2x2'TR | 3x45sAN | | 26 | OFF | 1x15'UT1 | 5'AT | 1x3'TR | 20'UT2 | 2x2'TR | 3x45sAN | RACE | cat level-5_8-session_26-weeks.csv ```csv "Interactive 2,000m Programme",,,,,,,, 2000m 26 Week Training Programme,,,,,,,, "Athlete Level 5, 8 Sessions a Week",,,,,,,, Week,Session 1,Session 2,Session 3,Session 4,Session 5,Session 6,Session 7,Session 8 1,TEST,2x15'UT1,55'UT2,2x18'UT1,68'UT2,3x15'UT1,55'UT2,2x15'UT1 2,65'UT2,2x21'UT1,72'UT2,3x16'UT1,80'UT2,4x13'UT1,72'UT2,3x12'UT1 3,76'UT2,3x17'UT1,85'UT2,3x19'UT1,90'UT2,3x20'UT1,85'UT2,2x25'UT1 4,45'UT2,2x15'UT1,2x6'AT,3x12'UT1,2x7'AT,3x15'UT1,2x10'AT,3x12'UT2 5,65'UT2,2x24'UT1,4x5'AT,3x16'UT1,3x8'AT,3x18'UT1,2x12'AT,3x16'UT1 6,76'UT2,2x25'UT1,3x7'AT,3x19'UT1,3x10'AT,5x12'UT1,3x8'AT,3x18'UT1 7,45'UT2,2x15'UT1,2x8'AT,3x12'UT1,2x10'AT,3x15'UT1,2x9'AT,2x3'TR 8,65'UT2,3x14'UT1,2x9'AT,75'UT2,2x8' AT,4x14'UT1,2x7'AT,3x4'TR 9,76'UT2,3x17'UT1,3x7'AT,90'UT2,4x5'TR,4X15'UT1,3x8'AT,4x4'TR 10,45'UT2,2x15'UT1,2x8'AT,60'UT2,4x2'TR,3x12'UT1,2x9'AT,3x3'TR 11,65'UT2,3x15'UT1,2x10'AT,75'UT2,4x3'TR,3x15'UT1,2x10'AT,4x3'TR 12,75'UT2,4x15'UT1,3x8'AT,90'UT2,4x4'TR,4x12'UT1,3x10'AT,5x4'TR 13,TEST,2X15'UT1,2x8'AT,60'UT2,3x1'AN,3x15'UT1,2x8'AT,4x2'TR 14,65'UT2,3X15'UT1,2x10'AT,75'UT2,4x1.5'AN,4x12'UT1,3x7'AT,6x2'TR 15,75'UT2,5x12'UT1,3x10'AT,90'UT2,6x1'AN,5x12'UT1,3x10'AT,6x4'TR 16,45'UT2,2X15'UT1,2x9'AT,60'UT2,8x45sAN,4x14'UT1,2x10'AT,5x2'TR 17,65'UT2,3X15'UT1,3x10'AT,75'UT2,6x1.5'AN,3x12'UT1,3x8'AT,6x3'TR 18,75'UT2,4x15'UT1,4x8'AT,90'UT2,8x1'AN,2x15'UT1,4x9'AT,7x4'TR 19,45'UT2,2X15'UT1,2x10'AT,60'UT2,4x1.5'AN,3x12'UT1,3x8'AT,6x2'TR 20,65'UT2,3X15'UT1,3x12'AT,75'UT2,6x1'AN,3x15'UT1,3x10'AT,7x3'TR 21,75'UT2,5x12'UT1,5x8'AT,90'UT2,8x45sAN,5x12'UT1,4x10'AT,8X4'TR 22,45'UT2,2x15'UT1,2x10'AT,60'UT2,8x1.5'AN,3x12'UT1,2x7'AT,4x2'TR 23,65'UT2,3x15'UT1,3x8'AT,75'UT2,10x45sAN,3x15'UT1,3x7'AT,4x3'TR 24,76'UT2,4x15'UT1,4x8'AT,90'UT2,2(6x1')AN,4x15'UT1,2x10'AT,4x4'TR 25,50'UT2,2x12'UT1,6' AT,40'UT2,2x1.5'AN,2x15'UT1,4'TR,2x12'UT1 26,OFF,1x15'UT1,5'AT,1x3'TR,20'UT2,2x2'TR,3x45sAN,RACE ``` cat concept2_prediction.py ```python def calculate_wattage(resting_heart_rate, maximum_heart_rate, heart_rate, base_power_output): def get_zone_percentile(heart_rate, resting_heart_rate, maximum_heart_rate): HRR = maximum_heart_rate - resting_heart_rate zone = ((heart_rate - resting_heart_rate) / HRR) * 100 return round(zone, 2) def get_hrr_range(zone): if zone <= 65: return (0, 65) elif zone <= 70: print("UT2 Zone") return (65, 70) elif zone <= 80: print("UT1 Zone") return (70, 80) elif zone <= 85: print("AT Zone") return (80, 85) elif zone <= 95: print("TR Zone") return (85, 95) else: print("AN Zone") return (95, 100) def get_power_range(zone): if zone <= 65: return (0, 45) elif zone <= 70: print("UT2 Zone") return (45, 60) elif zone <= 80: print("UT1 Zone") return (60, 70) elif zone <= 85: print("AT Zone") return (70, 80) elif zone <= 95: print("TR Zone") return (80, 105) else: print("AN Zone") return (105, 115) zone_percentage = get_zone_percentile(heart_rate, resting_heart_rate, maximum_heart_rate) print("Zone: " + str(zone_percentage)) min_power, max_power = get_power_range(zone_percentage) print("Min power: " + str(min_power)) print("Max power: " + str(max_power)) min_hrr, max_hrr = get_hrr_range(zone_percentage) print("Min hrr: " + str(min_hrr)) print("Max hrr: " + str(max_hrr)) power_range = max_power - min_power print("Power range: " + str(power_range)) hrr_range = max_hrr - min_hrr print("HRR range: " + str(hrr_range)) print("Zone minus min hrr: " + str(zone_percentage - min_hrr)) # Calculate percentage of hrr range hrr_percentage = ((zone_percentage - min_hrr) / hrr_range) print("hrr %: " + str(hrr_percentage)) power_percentage = (min_power + hrr_percentage * power_range) / 100 print("Power percentage: " + str(power_percentage)) # Add base power output # predicted_power = power_percentage + base_power_output predicted_power = base_power_output / power_percentage return round(predicted_power, 0) # Example usage resting_heart_rate = 58 maximum_heart_rate = 165 heart_rate = 132 base_power_output = 110 watts = calculate_wattage(resting_heart_rate, maximum_heart_rate, heart_rate, base_power_output) print(f"Predicted power: {watts} watts") ```