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