Health page: VO2 max gauge, layout improvements, 3Y/5Y trends, biological sex profile
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 9s

- Add biological_sex field to User model, profile API, and ProfilePage toggle (male/female) — used to select the correct ACSM VO2 max threshold table
- Replace simple VO2 max number in daily snapshot with a colour-coded SVG radial gauge (Very Poor=red, Poor=orange, Fair=green, Good=blue, Excellent=purple) driven by sex- and age-appropriate thresholds
- Shrink Sleep widget to half-width, expand Heart & HRV to half-width; reorganise Heart & HRV internals into a 2×2 grid to reduce vertical height
- Add connectNulls + showDots to VO2 Max trend chart so sparse readings connect with a continuous line
- Add 3Y and 5Y range options to the Trends selector; increase allDays limit to 2000 for full 5yr snapshot navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 23:49:01 +01:00
parent 70c7e5c0a8
commit 8d304545a3
5 changed files with 209 additions and 55 deletions
+6
View File
@@ -19,6 +19,7 @@ class ProfileUpdate(BaseModel):
resting_heart_rate: Optional[int] = None
birth_year: Optional[int] = None
height_cm: Optional[float] = None
biological_sex: Optional[str] = None
class ProfileOut(BaseModel):
@@ -29,6 +30,7 @@ class ProfileOut(BaseModel):
resting_heart_rate: Optional[int]
birth_year: Optional[int]
height_cm: Optional[float]
biological_sex: Optional[str]
estimated_max_hr: Optional[int]
is_admin: bool
@@ -72,6 +74,10 @@ async def update_profile(
if not (50 <= body.height_cm <= 300):
raise HTTPException(400, "Height must be 50300 cm")
current_user.height_cm = body.height_cm
if body.biological_sex is not None:
if body.biological_sex not in ('male', 'female', ''):
raise HTTPException(400, "biological_sex must be 'male' or 'female'")
current_user.biological_sex = body.biological_sex or None
await db.commit()
await db.refresh(current_user)
+9
View File
@@ -64,6 +64,15 @@ async def init_db():
except Exception as e:
print(f"health_metrics column migration skipped: {e}")
# biological_sex column on users added after initial creation
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE users ADD COLUMN IF NOT EXISTS biological_sex VARCHAR(8)"
))
except Exception as e:
print(f"users.biological_sex column migration skipped: {e}")
# route_segments auto_generated column added after initial creation
try:
async with engine.begin() as conn:
+1
View File
@@ -27,6 +27,7 @@ class User(Base):
resting_heart_rate = Column(Integer, nullable=True)
birth_year = Column(Integer, nullable=True)
height_cm = Column(Float, nullable=True)
biological_sex = Column(String(8), nullable=True) # 'male' | 'female'
# PocketID config (stored per-user so admin can set via UI)
pocketid_issuer = Column(String(512), nullable=True)