Compare commits

...
8 Commits
14 changed files with 47 additions and 2224 deletions
+4
View File
@@ -22,12 +22,16 @@ from flask_htmx import HTMX
import minify_html import minify_html
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from flask_compress import Compress
# Load environment variables from .env file in non-production environments # Load environment variables from .env file in non-production environments
if os.environ.get('FLASK_ENV') != 'production': if os.environ.get('FLASK_ENV') != 'production':
load_dotenv() load_dotenv()
app = Flask(__name__) app = Flask(__name__)
app.config['COMPRESS_REGISTER'] = True
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 31536000 # 1 year
Compress(app)
app.config.from_pyfile('config.py') app.config.from_pyfile('config.py')
app.secret_key = os.environ.get('SECRET_KEY', '2a661781919643cb8a5a8bc57642d99f') app.secret_key = os.environ.get('SECRET_KEY', '2a661781919643cb8a5a8bc57642d99f')
jinja_partials.register_extensions(app) jinja_partials.register_extensions(app)
+16 -13
View File
@@ -1,10 +1,11 @@
import os import os
import psycopg2 import psycopg2
from psycopg2 import pool
from psycopg2.extras import RealDictCursor from psycopg2.extras import RealDictCursor
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from urllib.parse import urlparse from urllib.parse import urlparse
from flask import g from flask import g, current_app
from features.exercises import Exercises from features.exercises import Exercises
from features.people_graphs import PeopleGraphs from features.people_graphs import PeopleGraphs
from features.person_overview import PersonOverview from features.person_overview import PersonOverview
@@ -16,6 +17,8 @@ from utils import get_exercise_graph_model
class DataBase(): class DataBase():
_pool = None
def __init__(self, app=None): def __init__(self, app=None):
self.stats = Stats(self.execute) self.stats = Stats(self.execute)
self.exercises = Exercises(self.execute) self.exercises = Exercises(self.execute)
@@ -25,29 +28,29 @@ class DataBase():
self.schema = Schema(self.execute) self.schema = Schema(self.execute)
self.activityRequest = Activity(self.execute) self.activityRequest = Activity(self.execute)
db_url = urlparse(os.environ['DATABASE_URL']) if not os.environ.get('DATABASE_URL'):
# if db_url is null then throw error
if not db_url:
raise Exception("No DATABASE_URL environment variable set") raise Exception("No DATABASE_URL environment variable set")
def getDB(self): if DataBase._pool is None:
db = getattr(g, 'database', None)
if db is None:
db_url = urlparse(os.environ['DATABASE_URL']) db_url = urlparse(os.environ['DATABASE_URL'])
g.database = psycopg2.connect( DataBase._pool = pool.ThreadedConnectionPool(
1, 20, # minconn, maxconn
database=db_url.path[1:], database=db_url.path[1:],
user=db_url.username, user=db_url.username,
password=db_url.password, password=db_url.password,
host=db_url.hostname, host=db_url.hostname,
port=db_url.port port=db_url.port
) )
db = g.database
return db
def close_connection(exception): def getDB(self):
db = getattr(g, 'database', None) if 'database' not in g:
g.database = self._pool.getconn()
return g.database
def close_connection(self, exception=None):
db = g.pop('database', None)
if db is not None: if db is not None:
db.close() self._pool.putconn(db)
def execute(self, query, args=(), one=False, commit=False): def execute(self, query, args=(), one=False, commit=False):
conn = self.getDB() conn = self.getDB()
+1
View File
@@ -18,3 +18,4 @@ email-validator==2.2.0
requests==2.26.0 requests==2.26.0
polars>=0.20.0 polars>=0.20.0
pyarrow>=14.0.0 pyarrow>=14.0.0
Flask-Compress==1.13
-63
View File
File diff suppressed because one or more lines are too long
-12
View File
File diff suppressed because one or more lines are too long
-2029
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-18
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -12,7 +12,7 @@
<link href="/static/css/style.css" rel="stylesheet"> <link href="/static/css/style.css" rel="stylesheet">
<script src="/static/js/htmx.min.js" defer></script> <script src="/static/js/htmx.min.js" defer></script>
<script src="/static/js/hyperscript.min.js"></script> <script src="/static/js/hyperscript.min.js" defer></script>
</head> </head>
<body> <body>
+6 -2
View File
@@ -130,8 +130,12 @@
<div class="overflow-x-auto rounded-lg"> <div class="overflow-x-auto rounded-lg">
<div class="align-middle inline-block min-w-full"> <div class="align-middle inline-block min-w-full">
<div class="shadow overflow-hidden sm:rounded-lg"> <div class="shadow overflow-hidden sm:rounded-lg">
<div class="w-full mt-2 pb-2 aspect-video"> <div class="w-full mt-2 pb-2 aspect-video"
{{ render_partial('partials/sparkline.html', **exercise.graph) }} hx-get="{{ url_for('get_exercise_progress_for_user', person_id=person.id, exercise_id=exercise.id, min_date=min_date, max_date=max_date) }}"
hx-trigger="intersect once" hx-swap="outerHTML">
<div class="flex items-center justify-center h-full bg-gray-50 rounded-lg">
<div class="text-sm text-gray-400 animate-pulse font-medium">Loading graph...</div>
</div>
</div> </div>
<table class="min-w-full divide-y divide-gray-200"> <table class="min-w-full divide-y divide-gray-200">
+7 -1
View File
@@ -105,7 +105,13 @@
<div class="mt-4 mb-4 w-full grid grid-cols-1 2xl:grid-cols-2 gap-4"> <div class="mt-4 mb-4 w-full grid grid-cols-1 2xl:grid-cols-2 gap-4">
{% for graph in exercise_progress_graphs %} {% for graph in exercise_progress_graphs %}
{{ render_partial('partials/sparkline.html', **graph.progress_graph) }} <div hx-get="{{ url_for('get_exercise_progress_for_user', person_id=person_id, exercise_id=graph.exercise_id, min_date=min_date, max_date=max_date) }}"
hx-trigger="intersect once" hx-swap="outerHTML">
<div class="flex items-center justify-center h-48 bg-gray-50 rounded-lg">
<div class="text-sm text-gray-400 animate-pulse font-medium">Loading {{ graph.exercise_name }}...
</div>
</div>
</div>
{% endfor %} {% endfor %}
</div> </div>
+11 -7
View File
@@ -48,18 +48,22 @@ def get_exercise_graph_model(title, estimated_1rm, repetitions, weight, start_da
best_fit_points = [] best_fit_points = []
try: try:
if len(relative_positions) > 1: # Ensure there are enough points for polyfit # Filter out NaNs if any (though scaled values shouldn't have them if ranges are correct)
# Fit a polynomial of the given degree mask = ~np.isnan(estimated_1rm_scaled)
coeffs = np.polyfit(relative_positions, estimated_1rm_scaled, degree) x_fit = relative_positions[mask]
y_fit = estimated_1rm_scaled[mask]
# Ensure we have enough unique X positions for the given degree
if len(np.unique(x_fit)) > degree:
coeffs = np.polyfit(x_fit, y_fit, degree)
poly_fit = np.poly1d(coeffs) poly_fit = np.poly1d(coeffs)
y_best_fit = poly_fit(relative_positions) y_best_fit = poly_fit(relative_positions)
best_fit_points = list(zip(y_best_fit.tolist(), relative_positions.tolist())) best_fit_points = list(zip(y_best_fit.tolist(), relative_positions.tolist()))
else: else:
raise ValueError("Not enough data points for polyfit") best_fit_points = []
except (np.linalg.LinAlgError, ValueError) as e: except (np.linalg.LinAlgError, ValueError, TypeError) as e:
# Handle cases where polyfit fails # Handle cases where polyfit fails or input is invalid
best_fit_points = [] best_fit_points = []
m, b = 0, 0
# Prepare data for plots # Prepare data for plots
repetitions_data = { repetitions_data = {