mirror of
https://github.com/sstent/Garmin_Analyser.git
synced 2025-12-05 23:52:11 +00:00
193 lines
6.1 KiB
Python
193 lines
6.1 KiB
Python
"""Zone definitions and calculations for workouts."""
|
|
|
|
from typing import Dict, Tuple, List
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass
|
|
class ZoneDefinition:
|
|
"""Definition of a training zone."""
|
|
|
|
name: str
|
|
min_value: float
|
|
max_value: float
|
|
color: str
|
|
description: str
|
|
|
|
|
|
class ZoneCalculator:
|
|
"""Calculator for various training zones."""
|
|
|
|
@staticmethod
|
|
def get_power_zones() -> Dict[str, ZoneDefinition]:
|
|
"""Get power zone definitions."""
|
|
return {
|
|
'Recovery': ZoneDefinition(
|
|
name='Recovery',
|
|
min_value=0,
|
|
max_value=150,
|
|
color='lightblue',
|
|
description='Active recovery, very light effort'
|
|
),
|
|
'Endurance': ZoneDefinition(
|
|
name='Endurance',
|
|
min_value=150,
|
|
max_value=200,
|
|
color='green',
|
|
description='Aerobic base, sustainable for hours'
|
|
),
|
|
'Tempo': ZoneDefinition(
|
|
name='Tempo',
|
|
min_value=200,
|
|
max_value=250,
|
|
color='yellow',
|
|
description='Sweet spot, sustainable for 20-60 minutes'
|
|
),
|
|
'Threshold': ZoneDefinition(
|
|
name='Threshold',
|
|
min_value=250,
|
|
max_value=300,
|
|
color='orange',
|
|
description='Functional threshold power, 20-60 minutes'
|
|
),
|
|
'VO2 Max': ZoneDefinition(
|
|
name='VO2 Max',
|
|
min_value=300,
|
|
max_value=1000,
|
|
color='red',
|
|
description='Maximum aerobic capacity, 3-8 minutes'
|
|
)
|
|
}
|
|
|
|
@staticmethod
|
|
def get_heart_rate_zones(lthr: int = 170) -> Dict[str, ZoneDefinition]:
|
|
"""Get heart rate zone definitions based on lactate threshold.
|
|
|
|
Args:
|
|
lthr: Lactate threshold heart rate in bpm
|
|
|
|
Returns:
|
|
Dictionary of heart rate zones
|
|
"""
|
|
return {
|
|
'Z1': ZoneDefinition(
|
|
name='Zone 1',
|
|
min_value=0,
|
|
max_value=int(lthr * 0.8),
|
|
color='lightblue',
|
|
description='Active recovery, <80% LTHR'
|
|
),
|
|
'Z2': ZoneDefinition(
|
|
name='Zone 2',
|
|
min_value=int(lthr * 0.8),
|
|
max_value=int(lthr * 0.87),
|
|
color='green',
|
|
description='Aerobic base, 80-87% LTHR'
|
|
),
|
|
'Z3': ZoneDefinition(
|
|
name='Zone 3',
|
|
min_value=int(lthr * 0.87) + 1,
|
|
max_value=int(lthr * 0.93),
|
|
color='yellow',
|
|
description='Tempo, 88-93% LTHR'
|
|
),
|
|
'Z4': ZoneDefinition(
|
|
name='Zone 4',
|
|
min_value=int(lthr * 0.93) + 1,
|
|
max_value=int(lthr * 0.99),
|
|
color='orange',
|
|
description='Threshold, 94-99% LTHR'
|
|
),
|
|
'Z5': ZoneDefinition(
|
|
name='Zone 5',
|
|
min_value=int(lthr * 0.99) + 1,
|
|
max_value=300,
|
|
color='red',
|
|
description='VO2 Max, >99% LTHR'
|
|
)
|
|
}
|
|
|
|
@staticmethod
|
|
def calculate_zone_distribution(values: List[float], zones: Dict[str, ZoneDefinition]) -> Dict[str, float]:
|
|
"""Calculate time spent in each zone.
|
|
|
|
Args:
|
|
values: List of values (power, heart rate, etc.)
|
|
zones: Zone definitions
|
|
|
|
Returns:
|
|
Dictionary with percentage time in each zone
|
|
"""
|
|
if not values:
|
|
return {zone_name: 0.0 for zone_name in zones.keys()}
|
|
|
|
zone_counts = {zone_name: 0 for zone_name in zones.keys()}
|
|
|
|
for value in values:
|
|
for zone_name, zone_def in zones.items():
|
|
if zone_def.min_value <= value <= zone_def.max_value:
|
|
zone_counts[zone_name] += 1
|
|
break
|
|
|
|
total_count = len(values)
|
|
return {
|
|
zone_name: (count / total_count) * 100
|
|
for zone_name, count in zone_counts.items()
|
|
}
|
|
|
|
@staticmethod
|
|
def get_zone_for_value(value: float, zones: Dict[str, ZoneDefinition]) -> str:
|
|
"""Get the zone name for a given value.
|
|
|
|
Args:
|
|
value: The value to check
|
|
zones: Zone definitions
|
|
|
|
Returns:
|
|
Zone name or 'Unknown' if not found
|
|
"""
|
|
for zone_name, zone_def in zones.items():
|
|
if zone_def.min_value <= value <= zone_def.max_value:
|
|
return zone_name
|
|
return 'Unknown'
|
|
|
|
@staticmethod
|
|
def get_cadence_zones() -> Dict[str, ZoneDefinition]:
|
|
"""Get cadence zone definitions."""
|
|
return {
|
|
'Recovery': ZoneDefinition(
|
|
name='Recovery',
|
|
min_value=0,
|
|
max_value=80,
|
|
color='lightblue',
|
|
description='Low cadence, recovery pace'
|
|
),
|
|
'Endurance': ZoneDefinition(
|
|
name='Endurance',
|
|
min_value=80,
|
|
max_value=90,
|
|
color='green',
|
|
description='Comfortable cadence, sustainable'
|
|
),
|
|
'Tempo': ZoneDefinition(
|
|
name='Tempo',
|
|
min_value=90,
|
|
max_value=100,
|
|
color='yellow',
|
|
description='Moderate cadence, tempo effort'
|
|
),
|
|
'Threshold': ZoneDefinition(
|
|
name='Threshold',
|
|
min_value=100,
|
|
max_value=110,
|
|
color='orange',
|
|
description='High cadence, threshold effort'
|
|
),
|
|
'Sprint': ZoneDefinition(
|
|
name='Sprint',
|
|
min_value=110,
|
|
max_value=200,
|
|
color='red',
|
|
description='Maximum cadence, sprint effort'
|
|
)
|
|
} |