Read distance, calories, max_rpm, min_rpm, average_rpm etc from workout rather then recalculating on each render, still need to modify add workout endpoint to calculate properties and set on workout

This commit is contained in:
Peter Stockings
2023-07-29 20:24:41 +10:00
parent a4004c6e00
commit 2e37b1e22a

126
app.py
View File

@@ -14,6 +14,7 @@ import sparklines
app = Flask(__name__) app = Flask(__name__)
# TODO CHANGE SECRET KEY TO ENVIRONMENT VARIABLE # TODO CHANGE SECRET KEY TO ENVIRONMENT VARIABLE
app.config['SECRET_KEY'] = 'secret!' app.config['SECRET_KEY'] = 'secret!'
app.config["SQLALCHEMY_ECHO"] = False # True for debugging
uri = os.getenv("DATABASE_URL") uri = os.getenv("DATABASE_URL")
if uri and uri.startswith("postgres://"): if uri and uri.startswith("postgres://"):
uri = uri.replace("postgres://", "postgresql://", 1) uri = uri.replace("postgres://", "postgresql://", 1)
@@ -50,8 +51,23 @@ class Workout(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=db.func.now()) created_at = db.Column(db.DateTime, nullable=False, default=db.func.now())
bike_id = db.Column(db.Integer, db.ForeignKey( bike_id = db.Column(db.Integer, db.ForeignKey(
'bikes.id', ondelete='CASCADE'), nullable=False) 'bikes.id', ondelete='CASCADE'), nullable=False)
started_at = db.Column(db.DateTime, nullable=False)
duration = db.Column(db.Numeric, nullable=False)
average_rpm = db.Column(db.Numeric, nullable=False)
min_rpm = db.Column(db.Integer, nullable=False)
max_rpm = db.Column(db.Integer, nullable=False)
calories = db.Column(db.Numeric, nullable=False)
distance = db.Column(db.Numeric, nullable=False)
average_bpm = db.Column(db.Numeric, nullable=False)
min_bpm = db.Column(db.Integer, nullable=False)
max_bpm = db.Column(db.Integer, nullable=False)
is_heart_rate_available = db.Column(
db.Boolean, nullable=False, default=False)
is_cadence_available = db.Column(db.Boolean, nullable=False, default=False)
cadence_readings = db.relationship( cadence_readings = db.relationship(
'CadenceReading', backref='workout', lazy=True) 'CadenceReading', backref='workout', lazy=True)
heart_rate_readings = db.relationship(
'HeartRateReading', backref='workout', lazy=True)
bike = db.relationship('Bike', backref='workouts', lazy=True) bike = db.relationship('Bike', backref='workouts', lazy=True)
@@ -154,39 +170,39 @@ def workouts(user_id):
@app.route('/user/<int:user_id>/workout/<int:workout_id>/<string:graph_type>', methods=['GET', 'DELETE']) @app.route('/user/<int:user_id>/workout/<int:workout_id>/<string:graph_type>', methods=['GET', 'DELETE'])
def workout(user_id, workout_id, graph_type): def workout(user_id, workout_id, graph_type):
workout = Workout.query.filter_by(user_id=user_id, id=workout_id).first() workout = Workout.query.filter_by(user_id=user_id, id=workout_id).join(
if workout: Workout.cadence_readings).join(Workout.heart_rate_readings).first()
# Get the cadence readings for the workout
cadence_readings = CadenceReading.query.filter_by( if not workout:
workout_id=workout_id).all()
if cadence_readings:
x_values = [reading.created_at for reading in cadence_readings]
if graph_type == 'cadence':
y_values = [reading.rpm for reading in cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Cadence (RPM)', filename='cadence'), 200
elif graph_type == 'speed':
y_values = [reading.speed for reading in cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Speed (KPH)', filename='speed'), 200
elif graph_type == 'distance':
y_values = [reading.distance for reading in cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Distance (KM)', filename='distance'), 200
elif graph_type == 'calories':
y_values = [reading.calories for reading in cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Calories (KCAL)', filename='calories'), 200
elif graph_type == 'power':
y_values = [reading.power for reading in cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Power (WATTS)', filename='power'), 200
heart_rate_readings = HeartRateReading.query.filter_by(
workout_id=workout_id).all()
if heart_rate_readings:
x_values = [reading.created_at for reading in heart_rate_readings]
y_values = [reading.bpm for reading in heart_rate_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Heart Rate (BPM)', filename='heart_rate'), 200
else:
return jsonify({'message': 'No cadence readings for workout {}.'.format(workout_id)}), 404
else:
return jsonify({'message': 'Workout {} not found for user {}.'.format(workout_id, user_id)}), 404 return jsonify({'message': 'Workout {} not found for user {}.'.format(workout_id, user_id)}), 404
if workout.is_cadence_available:
x_values = [reading.created_at for reading in workout.cadence_readings]
if graph_type == 'cadence':
y_values = [reading.rpm for reading in workout.cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Cadence (RPM)', filename='cadence'), 200
elif graph_type == 'speed':
y_values = [reading.speed for reading in workout.cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Speed (KPH)', filename='speed'), 200
elif graph_type == 'distance':
y_values = [
reading.distance for reading in workout.cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Distance (KM)', filename='distance'), 200
elif graph_type == 'calories':
y_values = [
reading.calories for reading in workout.cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Calories (KCAL)', filename='calories'), 200
elif graph_type == 'power':
y_values = [reading.power for reading in workout.cadence_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Power (WATTS)', filename='power'), 200
if workout.is_heart_rate_available:
x_values = [
reading.created_at for reading in workout.heart_rate_readings]
y_values = [reading.bpm for reading in workout.heart_rate_readings]
return create_graph(x_values=x_values, y_values=y_values, y_label='Heart Rate (BPM)', filename='heart_rate'), 200
return jsonify({'message': 'Unable to generate {} for workout {}.'.format(graph_type, workout_id)}), 409
@app.route('/user/<int:user_id>/workout/<int:workout_id>/view', methods=['GET']) @app.route('/user/<int:user_id>/workout/<int:workout_id>/view', methods=['GET'])
def view_workout(user_id, workout_id): def view_workout(user_id, workout_id):
@@ -296,32 +312,28 @@ def get_workouts_for_user(user_id):
workouts_data = [] workouts_data = []
workouts = Workout.query.filter_by(user_id=user_id).order_by( workouts = Workout.query.filter_by(user_id=user_id).order_by(
Workout.created_at.desc()).all() Workout.created_at.desc()).all()
for workout in workouts: for workout in workouts:
cadence_readings = CadenceReading.query.filter_by(
workout_id=workout.id).all()
if cadence_readings: duration = timedelta(
start_time = min( seconds=int(workout.duration)) if workout.duration else timedelta(seconds=0)
reading.created_at for reading in cadence_readings) average_rpm = workout.average_rpm if workout.average_rpm else 0
end_time = max( min_rpm = workout.min_rpm if workout.min_rpm else 0
reading.created_at for reading in cadence_readings) max_rpm = workout.max_rpm if workout.max_rpm else 0
duration = end_time - start_time calories = workout.calories if workout.calories else 0
average_rpm = sum( distance = workout.distance if workout.distance else 0
reading.rpm for reading in cadence_readings) / len(cadence_readings) average_bpm = workout.average_bpm if workout.average_bpm else 0
calories = cadence_readings[-1].calories min_bpm = workout.min_bpm if workout.min_bpm else 0
distance = cadence_readings[-1].distance max_bpm = workout.max_bpm if workout.max_bpm else 0
is_heart_rate_available = workout.is_heart_rate_available
is_cadence_available = workout.is_cadence_available
start_time = workout.started_at
selected_graph_types = ['speed'] selected_graph_types = ['speed']
is_heart_rate_available = False if is_heart_rate_available:
heart_rate_readings = HeartRateReading.query.filter_by( selected_graph_types.append('heart_rate')
workout_id=workout.id).all()
average_bpm = 0
if heart_rate_readings:
selected_graph_types.append('heart_rate')
is_heart_rate_available = True
average_bpm = sum(heartrate.bpm for heartrate in heart_rate_readings) / \
len(heart_rate_readings)
if is_cadence_available or is_heart_rate_available:
workouts_data.append({ workouts_data.append({
'id': workout.id, 'id': workout.id,
'user_id': user_id, 'user_id': user_id,
@@ -331,13 +343,19 @@ def get_workouts_for_user(user_id):
'duration': format_duration(duration), 'duration': format_duration(duration),
'duration_minutes': duration.total_seconds() / 60, 'duration_minutes': duration.total_seconds() / 60,
'average_rpm': int(average_rpm), 'average_rpm': int(average_rpm),
'min_rpm': int(min_rpm),
'max_rpm': int(max_rpm),
'calories': int(calories), 'calories': int(calories),
'distance': int(distance), 'distance': int(distance),
'bike_display_name': workout.bike.display_name, 'bike_display_name': workout.bike.display_name,
'selected_graph_types': selected_graph_types, 'selected_graph_types': selected_graph_types,
'is_heart_rate_available': is_heart_rate_available, 'is_heart_rate_available': is_heart_rate_available,
'average_bpm': int(average_bpm) 'is_cadence_available': is_cadence_available,
'average_bpm': int(average_bpm),
'min_bpm': int(min_bpm),
'max_bpm': int(max_bpm),
}) })
return workouts_data return workouts_data