Source code for src.model.model

"""
Read workout data and calculate 1RM and training volume.
"""

from datetime import datetime
import logging
import sys
from typing import Final
import pandas as pd  # type: ignore
from src.utils.set_db_and_table import set_db_and_table  # type: ignore
from src.one_rep_max import (  # type: ignore
    OneRepMaxStrategy,
    ACSMStrategy,
    EpleyStrategy,
    BrzyckiStrategy
    )
from src.one_rep_max_calc import OneRepMaxCalculator  # type: ignore


[docs] def get_df( table, splits: list[str] = ["chest", "push", "chest_and_back"], exercise: str = "barbell_bench_press", ) -> pd.DataFrame: """Return one consolidated Pandas dataframe, containing workout date and training data, for specified split(s) and exercise. :param table: TinyDB table :type table: tinydb.table.Table :param splits: List of workout splits to include, defaults to ["chest", "push", "chest_and_back"] :type splits: list, optional :param exercise: Exercise to include, defaults to "barbell_bench_press" :type exercise: str, optional :return: Consolidated Pandas dataframe :rtype: pd.DataFrame """ frames = [] for item in table: if not any(x in item["split"] for x in splits): continue if exercise in item["exercises"].keys(): df = pd.DataFrame(item["exercises"][exercise]) df["date"] = item["date"] frames.append(df) return pd.concat(frames)
[docs] def get_weight(df: pd.DataFrame) -> pd.Series: """Extracts weight from the 'weight' column. :param df: Pandas dataframe with 'weight' column :type df: pd.DataFrame :return: Weight in kg :rtype: pd.Series """ return df["weight"].str.strip(" kg").astype(float)
[docs] def calc_volume(df: pd.DataFrame) -> pd.DataFrame: """Sets times reps times load. :param df: DataFrame containing weight, reps, and set_number data :type df: pd.DataFrame :return: DataFrame with volume per date :rtype: pd.DataFrame """ df_copy = df.copy() num_of_sets_df = df_copy.groupby("date")[["set_number"]].agg("max") reps_df = df_copy.groupby("date")[["reps"]].agg("max") df_copy["weight"] = get_weight(df_copy) weight_df = df_copy.groupby("date")[["weight"]].agg("max") df_res = pd.concat([num_of_sets_df, reps_df, weight_df], axis=1) df_res["volume"] = df_res["set_number"] * df_res["reps"] * df_res["weight"] return df_res.drop(["set_number", "reps", "weight"], axis=1)
[docs] def one_rep_max_estimator(df: pd.DataFrame, formula: str="acsm") -> pd.DataFrame: """Estimates 1RM using ACSM, Epley, or Brzycki formulas. :param df: DataFrame containing weight and reps data :type df: pd.DataFrame :param formula: Formula to use ('acsm', 'epley', or 'brzycki'), defaults to "acsm" :type formula: str, optional :return: DataFrame with estimated 1RM per date :rtype: pd.DataFrame """ df_copy = df.copy() # Define strategies based on the input formula strategy: OneRepMaxStrategy match formula.lower(): case "acsm": strategy = ACSMStrategy() case "epley": strategy = EpleyStrategy() case "brzycki": strategy = BrzyckiStrategy() case _: sys.exit("Invalid formula. Use 'acsm', 'epley', or 'brzycki'.") # Initialize calculator with the selected strategy calculator = OneRepMaxCalculator(strategy) # Vectorized calculation for the whole DataFrame weights = get_weight(df_copy) reps = df_copy['reps'] # .astype(int) df_copy["1RM"] = calculator.calculate(weights, reps) # Return the max 1RM per date return df_copy.groupby("date")[["1RM"]].agg("max")
[docs] def get_data(df, y_col="1RM") -> tuple[list[float], list[float]]: """Get workout-timestamps and 1RM estimates. :param df: Pandas dataframe with workout-timestamps and either 1RM estimates or volume :type df: pd.DataFrame :param y_col: String signifying whether to use 1RM estimates or volume :type y_col: str :return: workout-timestamps and either 1RM estimates or volume :rtype: tuple[list[float], list[float]] """ date_strs = df.index.tolist() # workout-dates x = [datetime.fromisoformat(i).timestamp() for i in date_strs] match y_col: case "1RM": y = df["1RM"].tolist() # max 1RM estimate in kg y = [float("{:.2f}".format(x)) for x in y] case "volume": y = df["volume"].tolist() # volume in kg case _: raise ValueError return x, y
[docs] def main() -> None: """Prepare dfs, calc 1RM and do linear regression.""" from utils.logger_config import setup_logger, log_running_file # type: ignore setup_logger(log_file="model.log") log_running_file(__file__) logger1 = logging.getLogger("model.area1") logger2 = logging.getLogger("model.area2") DATA_MODELS = ["real", "simulated"] FORMULAS = ["acsm", "epley", "brzycki"] datatype = DATA_MODELS[0] _EXERCISE: Final[str] = "squat" _SPLITS: Final[list[str]] = ["legs", "legs_and_abs"] db, table, _ = set_db_and_table(datatype) logger1.info("data_model: %s", datatype) logger1.debug("db: %s", db) logger1.debug("table: %s", table) workout_timestamps, one_rm_estimates = get_data( one_rep_max_estimator( get_df(table, splits=_SPLITS, exercise=_EXERCISE), formula=FORMULAS[0], ) ) workout_timestamps, volume = get_data( calc_volume(get_df(table, splits=_SPLITS, exercise=_EXERCISE)), y_col="volume", ) logger2.info("Exercise: %s", _EXERCISE) logger2.info("Workout timestamps: %s", workout_timestamps) logger2.info("1 RM estimates: %s", one_rm_estimates) logger2.info("Volume: %s", volume)
if __name__ == "__main__": main()