fix
This commit is contained in:
75
backend/app/services/zones.py
Normal file
75
backend/app/services/zones.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import numpy as np
|
||||
|
||||
from backend.app.models.activity import DataPoint
|
||||
|
||||
# Coggan 7-zone power model (% of FTP)
|
||||
POWER_ZONES = [
|
||||
{"zone": 1, "name": "Active Recovery", "min_pct": 0, "max_pct": 55},
|
||||
{"zone": 2, "name": "Endurance", "min_pct": 55, "max_pct": 75},
|
||||
{"zone": 3, "name": "Tempo", "min_pct": 75, "max_pct": 90},
|
||||
{"zone": 4, "name": "Threshold", "min_pct": 90, "max_pct": 105},
|
||||
{"zone": 5, "name": "VO2max", "min_pct": 105, "max_pct": 120},
|
||||
{"zone": 6, "name": "Anaerobic", "min_pct": 120, "max_pct": 150},
|
||||
{"zone": 7, "name": "Neuromuscular", "min_pct": 150, "max_pct": 10000},
|
||||
]
|
||||
|
||||
# 5-zone HR model (% of LTHR)
|
||||
HR_ZONES = [
|
||||
{"zone": 1, "name": "Recovery", "min_pct": 0, "max_pct": 81},
|
||||
{"zone": 2, "name": "Aerobic", "min_pct": 81, "max_pct": 90},
|
||||
{"zone": 3, "name": "Tempo", "min_pct": 90, "max_pct": 95},
|
||||
{"zone": 4, "name": "Threshold", "min_pct": 95, "max_pct": 100},
|
||||
{"zone": 5, "name": "Anaerobic", "min_pct": 100, "max_pct": 10000},
|
||||
]
|
||||
|
||||
|
||||
def calculate_power_zones(
|
||||
data_points: list[DataPoint],
|
||||
ftp: float,
|
||||
) -> list[dict]:
|
||||
"""Calculate time-in-zone distribution for power."""
|
||||
powers = np.array([dp.power for dp in data_points if dp.power is not None], dtype=float)
|
||||
if len(powers) == 0 or ftp <= 0:
|
||||
return []
|
||||
|
||||
total = len(powers)
|
||||
result = []
|
||||
for z in POWER_ZONES:
|
||||
low = ftp * z["min_pct"] / 100
|
||||
high = ftp * z["max_pct"] / 100
|
||||
seconds = int(np.sum((powers >= low) & (powers < high)))
|
||||
result.append({
|
||||
"zone": z["zone"],
|
||||
"name": z["name"],
|
||||
"min_watts": round(low),
|
||||
"max_watts": round(high) if z["max_pct"] < 10000 else None,
|
||||
"seconds": seconds,
|
||||
"percentage": round(seconds / total * 100, 1) if total > 0 else 0,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def calculate_hr_zones(
|
||||
data_points: list[DataPoint],
|
||||
lthr: int,
|
||||
) -> list[dict]:
|
||||
"""Calculate time-in-zone distribution for heart rate."""
|
||||
hrs = np.array([dp.heart_rate for dp in data_points if dp.heart_rate is not None], dtype=float)
|
||||
if len(hrs) == 0 or lthr <= 0:
|
||||
return []
|
||||
|
||||
total = len(hrs)
|
||||
result = []
|
||||
for z in HR_ZONES:
|
||||
low = lthr * z["min_pct"] / 100
|
||||
high = lthr * z["max_pct"] / 100
|
||||
seconds = int(np.sum((hrs >= low) & (hrs < high)))
|
||||
result.append({
|
||||
"zone": z["zone"],
|
||||
"name": z["name"],
|
||||
"min_bpm": round(low),
|
||||
"max_bpm": round(high) if z["max_pct"] < 10000 else None,
|
||||
"seconds": seconds,
|
||||
"percentage": round(seconds / total * 100, 1) if total > 0 else 0,
|
||||
})
|
||||
return result
|
||||
Reference in New Issue
Block a user