Jacob Hinkle 8162af63b6 Initial commit: fitness agent project
- opencode agent (fitness-trainer) for personal training conversations
- fitness-workout skill with programming methodology guidelines
- workout.py script (1RM, volume, cycle helpers)
- logs/ directory for workout and check-in markdown files
- inputs/ with user profile (equipment, goals, medical, Juggernaut history)
- google-sheets-automation skill for optional Sheets integration
- AGENTS.md with setup documentation
2026-06-25 19:08:30 -04:00

120 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Helper utilities for the fitness trainer agent.
Usage:
workout.py e1rm <weight> <reps> # Estimate 1RM (Epley)
workout.py volume <sets> <reps> <weight> # Total tonnage
workout.py training-max <e1rm> [pct] # Training max (default 90%)
workout.py cycle <tm> <rep-scheme> # Suggest weights for a wave
Examples:
workout.py e1rm 185 5
workout.py volume 3 5 185
workout.py training-max 210
workout.py cycle 200 5x5
"""
import sys
import math
def e1rm(weight: float, reps: int) -> float:
"""Estimate 1RM using the Epley formula."""
if reps == 0:
return weight
if reps == 1:
return weight
return weight * (1 + reps / 30)
def volume(sets: int, reps: int, weight: float) -> float:
"""Calculate total tonnage."""
return sets * reps * weight
def training_max(e1rm_val: float, pct: float = 0.90) -> float:
"""Calculate training max as a percentage of e1RM."""
return e1rm_val * pct
def round_to_nearest(weight: float, increment: float = 5.0) -> float:
"""Round weight to the nearest plate increment."""
return round(weight / increment) * increment
def suggest_cycle_weights(tm: float, sets: int, reps: int, steps: int = 4):
"""Suggest wave weights from ~80% to ~95% of training max.
Args:
tm: Training max
sets: Sets per exercise
reps: Reps per set (target)
steps: Number of weeks in the wave
"""
print(f"Training max: {tm}lb")
print(f"Scheme: {sets}x{reps}")
print()
for week in range(steps):
pct = 0.80 + week * 0.05
week_weight = round_to_nearest(tm * pct)
print(f" Week {week + 1}: {int(pct * 100)}% = {int(week_weight)}lb")
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
cmd = sys.argv[1]
if cmd == "e1rm":
if len(sys.argv) < 4:
print("Usage: workout.py e1rm <weight> <reps>")
sys.exit(1)
w = float(sys.argv[2])
r = int(sys.argv[3])
est = round_to_nearest(e1rm(w, r))
print(f"e1RM: {int(est)}lb")
elif cmd == "volume":
if len(sys.argv) < 5:
print("Usage: workout.py volume <sets> <reps> <weight>")
sys.exit(1)
s = int(sys.argv[2])
r = int(sys.argv[3])
w = float(sys.argv[4])
print(f"Volume: {int(volume(s, r, w))}lb")
elif cmd == "training-max":
if len(sys.argv) < 3:
print("Usage: workout.py training-max <e1rm> [pct]")
sys.exit(1)
e = float(sys.argv[2])
p = float(sys.argv[3]) if len(sys.argv) > 3 else 0.90
tm = round_to_nearest(training_max(e, p))
print(f"Training max: {int(tm)}lb")
elif cmd == "cycle":
if len(sys.argv) < 4:
print("Usage: workout.py cycle <tm> <rep-scheme>")
sys.exit(1)
tm = float(sys.argv[2])
scheme = sys.argv[3]
try:
parts = scheme.lower().split("x")
sets = int(parts[0])
reps = int(parts[1])
except (IndexError, ValueError):
print(f"Invalid scheme '{scheme}'. Use format like 3x5 or 5x5.")
sys.exit(1)
suggest_cycle_weights(tm, sets, reps)
else:
print(f"Unknown command: {cmd}")
print(__doc__)
sys.exit(1)
if __name__ == "__main__":
main()