Add initial project

This commit is contained in:
2026-07-30 23:25:20 -04:00
commit cff6aa8289
164 changed files with 26672 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
"""SQLAlchemy models module."""
from backend.models.user import User
from backend.models.challenge import Challenge
from backend.models.tracking import Tracking
from backend.models.daily_health_metrics import DailyHealthMetrics
from backend.models.workout_session import WorkoutSession
from backend.models.daily_context import DailyContext
from backend.models.health_connect import (
HealthConnectSyncLog,
HealthConnectDailyMetrics,
HealthConnectExerciseSession,
)
from backend.models.health_connect_raw import (
HCRawSteps,
HCRawHeartRate,
HCRawHeartRateSample,
HCRawRestingHeartRate,
HCRawSleepSession,
HCRawSleepStage,
HCRawDistance,
HCRawCalories,
HCRawOxygenSaturation,
HCRawWeight,
HCRawHeight,
HCRawBodyFat,
HCRawExerciseSession,
HCRawNutrition,
HCRawHydration,
HCRawHeartRateVariabilityRmssd,
)
__all__ = [
"User",
"Challenge",
"Tracking",
"DailyHealthMetrics",
"WorkoutSession",
"DailyContext",
"HealthConnectSyncLog",
"HealthConnectDailyMetrics",
"HealthConnectExerciseSession",
# Raw data models
"HCRawSteps",
"HCRawHeartRate",
"HCRawHeartRateSample",
"HCRawRestingHeartRate",
"HCRawSleepSession",
"HCRawSleepStage",
"HCRawDistance",
"HCRawCalories",
"HCRawOxygenSaturation",
"HCRawWeight",
"HCRawHeight",
"HCRawBodyFat",
"HCRawExerciseSession",
"HCRawNutrition",
"HCRawHydration",
"HCRawHeartRateVariabilityRmssd",
]
+30
View File
@@ -0,0 +1,30 @@
"""Challenge SQLAlchemy model."""
from sqlalchemy import Column, Integer, String, Date, ForeignKey
from sqlalchemy.orm import relationship
from datetime import date
from backend.database import Base
class Challenge(Base):
"""Challenge model for tracking habits."""
__tablename__ = "challenges"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
period_type = Column(String) # 'week', 'month', 'year'
daily_target = Column(Integer)
start_date = Column(Date)
end_date = Column(Date)
created_at = Column(Date, default=date.today)
display_order = Column(Integer, default=0)
user_id = Column(Integer, ForeignKey("users.id"))
unit_type = Column(String, default="reps") # 'reps', 'minutes', 'pages', 'km'
rest_days = Column(String, nullable=True) # comma-separated list of days
icon = Column(String, default="fa-dumbbell")
image_url = Column(String, nullable=True)
description = Column(String, nullable=True)
frequency_count = Column(Integer, nullable=True)
selected_days = Column(String, nullable=True)
owner = relationship("User", back_populates="challenges")
trackings = relationship("Tracking", back_populates="challenge", cascade="all, delete-orphan")
+18
View File
@@ -0,0 +1,18 @@
"""Daily Context Model - Stores subjective user inputs like mood, tags, etc."""
from sqlalchemy import Column, Integer, String, Date, ForeignKey, UniqueConstraint
from backend.database import Base
class DailyContext(Base):
__tablename__ = "daily_context"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
date = Column(Date, index=True)
mood_score = Column(Integer) # 1-100 slider
tags = Column(String, nullable=True) # Comma separated tags e.g. "Alcohol,Stress"
notes = Column(String, nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'date', name='uq_daily_context'),
)
+37
View File
@@ -0,0 +1,37 @@
"""DailyHealthMetrics SQLAlchemy model."""
from sqlalchemy import Column, Integer, Date, Float, ForeignKey, UniqueConstraint, String
from sqlalchemy.orm import relationship
from backend.database import Base
class DailyHealthMetrics(Base):
"""Daily health metrics model for tracking health data."""
__tablename__ = "daily_health_metrics"
__table_args__ = (
UniqueConstraint('user_id', 'date', name='uq_user_date'),
)
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
date = Column(Date, index=True)
step_count = Column(Integer, default=0)
distance_meters = Column(Float, default=0.0)
calories_burned = Column(Float, default=0.0)
sleep_duration_minutes = Column(Integer, default=0)
deep_sleep_minutes = Column(Integer, default=0)
light_sleep_minutes = Column(Integer, default=0)
rem_sleep_minutes = Column(Integer, default=0)
awake_duration_minutes = Column(Integer, default=0)
avg_heart_rate = Column(Integer, nullable=True)
min_heart_rate = Column(Integer, nullable=True)
max_heart_rate = Column(Integer, nullable=True)
avg_spo2 = Column(Float, nullable=True)
pai_score = Column(Float, default=0.0)
weight = Column(Float, nullable=True)
resting_heart_rate = Column(Integer, nullable=True)
hrv = Column(Float, nullable=True)
# Data provenance tracking
data_source = Column(String(50), default="health_connect") # "health_connect", "manual"
user = relationship("User", back_populates="health_metrics")
+181
View File
@@ -0,0 +1,181 @@
"""
Health Connect Data Models - Storage for data pushed from Android companion app.
These models store health data received from the Health Connect Android app,
enabling the HealthConnectProvider to serve data to the API layer.
"""
from sqlalchemy import Column, Integer, String, Float, Date, DateTime, ForeignKey, Text
from sqlalchemy.orm import relationship
from backend.database import Base
import datetime
class HealthConnectSyncLog(Base):
"""
Tracks sync operations from the Android companion app.
Used for debugging and monitoring data flow.
"""
__tablename__ = "health_connect_sync_logs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
device_id = Column(String(100)) # Android device identifier
sync_timestamp = Column(DateTime, default=datetime.datetime.utcnow)
records_pushed = Column(Integer, default=0)
status = Column(String(50)) # "success", "partial", "error"
error_message = Column(Text, nullable=True)
user = relationship("User")
class HealthConnectDailyMetrics(Base):
"""
Daily health metrics received from Health Connect.
Mirrors DailyHealthMetrics but with Health Connect-specific metadata.
"""
__tablename__ = "health_connect_daily_metrics"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
date = Column(Date, index=True)
# Data source identification
data_source = Column(String(200), nullable=True) # Package name of originating app (e.g., com.huami.watch.hmwatchmanager)
data_source_name = Column(String(100), nullable=True) # Friendly name (e.g., "Zepp")
# Activity metrics
step_count = Column(Integer, default=0)
distance_meters = Column(Float, default=0.0)
calories_burned = Column(Float, default=0.0) # ActiveCaloriesBurnedRecord
total_calories = Column(Float, default=0.0) # TotalCaloriesBurnedRecord
# Sleep metrics (from SleepSessionRecord)
sleep_duration_minutes = Column(Integer, default=0)
deep_sleep_minutes = Column(Integer, default=0)
light_sleep_minutes = Column(Integer, default=0)
rem_sleep_minutes = Column(Integer, default=0)
awake_duration_minutes = Column(Integer, default=0)
# Heart rate metrics (from HeartRateRecord)
avg_heart_rate = Column(Integer, nullable=True)
min_heart_rate = Column(Integer, nullable=True)
max_heart_rate = Column(Integer, nullable=True)
resting_heart_rate = Column(Integer, nullable=True)
# SpO2 (from OxygenSaturationRecord)
avg_spo2 = Column(Float, nullable=True)
min_spo2 = Column(Float, nullable=True)
max_spo2 = Column(Float, nullable=True)
# Body metrics
weight = Column(Float, nullable=True) # WeightRecord
# Metadata
last_synced = Column(DateTime, default=datetime.datetime.utcnow)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
user = relationship("User")
class HealthConnectExerciseSession(Base):
"""
Exercise sessions from Health Connect ExerciseSessionRecord.
"""
__tablename__ = "health_connect_exercise_sessions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
# Session timing
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
# Exercise type (Health Connect ExerciseType enum)
exercise_type = Column(Integer) # Health Connect type ID
exercise_type_name = Column(String(100)) # Human-readable name
# Metrics
duration_seconds = Column(Integer, default=0)
distance_meters = Column(Float, nullable=True)
calories_burned = Column(Float, nullable=True)
# Heart rate during exercise
avg_heart_rate = Column(Integer, nullable=True)
max_heart_rate = Column(Integer, nullable=True)
min_heart_rate = Column(Integer, nullable=True)
# Additional metrics
steps = Column(Integer, nullable=True)
elevation_gained = Column(Float, nullable=True)
# Metadata
notes = Column(Text, nullable=True)
last_synced = Column(DateTime, default=datetime.datetime.utcnow)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
user = relationship("User")
# Health Connect Exercise Type mapping (subset)
HEALTH_CONNECT_EXERCISE_TYPES = {
0: "Unknown",
2: "Badminton",
4: "Baseball",
5: "Basketball",
8: "Biking",
9: "Biking Stationary",
10: "Boot Camp",
11: "Boxing",
13: "Calisthenics",
14: "Cricket",
16: "Dancing",
25: "Elliptical",
26: "Exercise Class",
27: "Fencing",
28: "Football American",
29: "Football Australian",
31: "Frisbee Disc",
32: "Golf",
33: "Guided Breathing",
34: "Gymnastics",
35: "Handball",
36: "High Intensity Interval Training",
37: "Hiking",
38: "Ice Hockey",
39: "Ice Skating",
44: "Martial Arts",
46: "Paddling",
48: "Pilates",
50: "Racquetball",
51: "Rock Climbing",
52: "Roller Hockey",
53: "Rowing",
54: "Rowing Machine",
55: "Rugby",
56: "Running",
57: "Running Treadmill",
58: "Sailing",
59: "Scuba Diving",
60: "Skating",
61: "Skiing",
62: "Snowboarding",
63: "Snowshoeing",
64: "Soccer",
65: "Softball",
67: "Squash",
68: "Stair Climbing",
69: "Stair Climbing Machine",
70: "Strength Training",
71: "Stretching",
72: "Surfing",
73: "Swimming Open Water",
74: "Swimming Pool",
75: "Table Tennis",
76: "Tennis",
78: "Volleyball",
79: "Walking",
80: "Water Polo",
81: "Weightlifting",
82: "Wheelchair",
84: "Yoga",
}
+461
View File
@@ -0,0 +1,461 @@
"""
Health Connect Raw Data Models - Storage for raw health records from Health Connect.
These models store individual health records (not aggregated) received from
the Android companion app, enabling accurate aggregation on-demand.
"""
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Text, UniqueConstraint, Index
from sqlalchemy.orm import relationship
from backend.database import Base
import datetime
# ==================== STEPS ====================
class HCRawSteps(Base):
"""Individual step records from Health Connect StepsRecord"""
__tablename__ = "hc_raw_steps"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
# Record identification (from Health Connect metadata)
record_id = Column(String(100), index=True) # metadata.id - unique per source
data_source = Column(String(200)) # Package name (e.g., com.huami.watch.hmwatchmanager)
data_source_name = Column(String(100)) # Friendly name (e.g., "Zepp")
# Timestamps
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
# Data
count = Column(Integer)
# Sync tracking
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
# Unique constraint: same user + record_id + source = unique record
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_steps_record'),
Index('ix_steps_user_date', 'user_id', 'start_time'),
)
# ==================== HEART RATE ====================
class HCRawHeartRate(Base):
"""Heart rate record containers from Health Connect HeartRateRecord"""
__tablename__ = "hc_raw_heart_rate"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
# Relationship to samples
samples = relationship("HCRawHeartRateSample", back_populates="record", cascade="all, delete-orphan")
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_hr_record'),
Index('ix_hr_user_date', 'user_id', 'start_time'),
)
class HCRawHeartRateSample(Base):
"""Individual heart rate samples within a HeartRateRecord"""
__tablename__ = "hc_raw_heart_rate_samples"
id = Column(Integer, primary_key=True, index=True)
record_id = Column(Integer, ForeignKey("hc_raw_heart_rate.id", ondelete="CASCADE"), index=True)
time = Column(DateTime, index=True)
bpm = Column(Integer)
record = relationship("HCRawHeartRate", back_populates="samples")
# ==================== RESTING HEART RATE ====================
class HCRawRestingHeartRate(Base):
"""Resting heart rate records from Health Connect RestingHeartRateRecord"""
__tablename__ = "hc_raw_resting_hr"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
bpm = Column(Integer)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_resting_hr_record'),
Index('ix_resting_hr_user_date', 'user_id', 'time'),
)
# ==================== SLEEP ====================
class HCRawSleepSession(Base):
"""Sleep session records from Health Connect SleepSessionRecord"""
__tablename__ = "hc_raw_sleep_sessions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
title = Column(String(200), nullable=True)
notes = Column(Text, nullable=True)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
# Relationship to stages
stages = relationship("HCRawSleepStage", back_populates="session", cascade="all, delete-orphan")
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_sleep_record'),
Index('ix_sleep_user_date', 'user_id', 'start_time'),
)
class HCRawSleepStage(Base):
"""Sleep stages within a sleep session"""
__tablename__ = "hc_raw_sleep_stages"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(Integer, ForeignKey("hc_raw_sleep_sessions.id", ondelete="CASCADE"), index=True)
start_time = Column(DateTime)
end_time = Column(DateTime)
stage_type = Column(Integer) # Health Connect stage type enum
stage_name = Column(String(50)) # Human readable: "awake", "light", "deep", "rem", etc.
session = relationship("HCRawSleepSession", back_populates="stages")
# Sleep stage type constants (from Health Connect)
SLEEP_STAGE_TYPES = {
0: "unknown",
1: "awake",
2: "sleeping",
3: "out_of_bed",
4: "light",
5: "deep",
6: "rem",
7: "awake_in_bed",
}
# ==================== DISTANCE ====================
class HCRawDistance(Base):
"""Distance records from Health Connect DistanceRecord"""
__tablename__ = "hc_raw_distance"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
distance_meters = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_distance_record'),
Index('ix_distance_user_date', 'user_id', 'start_time'),
)
# ==================== CALORIES ====================
class HCRawCalories(Base):
"""Calories records from Health Connect (ActiveCaloriesBurned or TotalCaloriesBurned)"""
__tablename__ = "hc_raw_calories"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
calories = Column(Float)
record_type = Column(String(20)) # "active" or "total"
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_calories_record'),
Index('ix_calories_user_date', 'user_id', 'start_time'),
)
# ==================== OXYGEN SATURATION (SpO2) ====================
class HCRawOxygenSaturation(Base):
"""Oxygen saturation records from Health Connect OxygenSaturationRecord"""
__tablename__ = "hc_raw_spo2"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
percentage = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_spo2_record'),
Index('ix_spo2_user_date', 'user_id', 'time'),
)
# ==================== WEIGHT ====================
class HCRawWeight(Base):
"""Weight records from Health Connect WeightRecord"""
__tablename__ = "hc_raw_weight"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
weight_kg = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_weight_record'),
Index('ix_weight_user_date', 'user_id', 'time'),
)
# ==================== HEIGHT ====================
class HCRawHeight(Base):
"""Height records from Health Connect HeightRecord"""
__tablename__ = "hc_raw_height"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
height_meters = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_height_record'),
Index('ix_height_user_date', 'user_id', 'time'),
)
# ==================== BODY FAT ====================
class HCRawBodyFat(Base):
"""Body fat records from Health Connect BodyFatRecord"""
__tablename__ = "hc_raw_body_fat"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
percentage = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_body_fat_record'),
Index('ix_body_fat_user_date', 'user_id', 'time'),
)
# ==================== EXERCISE SESSIONS ====================
class HCRawExerciseSession(Base):
"""Exercise session records from Health Connect ExerciseSessionRecord"""
__tablename__ = "hc_raw_exercise_sessions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
# Exercise details
exercise_type = Column(Integer) # Health Connect ExerciseType enum
exercise_type_name = Column(String(100))
title = Column(String(200), nullable=True)
notes = Column(Text, nullable=True)
# Associated metrics (if available in the session)
distance_meters = Column(Float, nullable=True)
calories = Column(Float, nullable=True)
# Heart rate during exercise
avg_heart_rate = Column(Integer, nullable=True)
max_heart_rate = Column(Integer, nullable=True)
min_heart_rate = Column(Integer, nullable=True)
# Additional
steps = Column(Integer, nullable=True)
elevation_gained_meters = Column(Float, nullable=True)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_exercise_record'),
Index('ix_exercise_user_date', 'user_id', 'start_time'),
)
# ==================== NUTRITION ====================
class HCRawNutrition(Base):
"""Nutrition records from Health Connect NutritionRecord"""
__tablename__ = "hc_raw_nutrition"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
# Basic nutrition info
name = Column(String(200), nullable=True)
meal_type = Column(Integer, nullable=True) # 0=unknown, 1=breakfast, 2=lunch, 3=dinner, 4=snack
# Macros
calories = Column(Float, nullable=True)
protein_grams = Column(Float, nullable=True)
carbohydrates_grams = Column(Float, nullable=True)
fat_grams = Column(Float, nullable=True)
fiber_grams = Column(Float, nullable=True)
sugar_grams = Column(Float, nullable=True)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_nutrition_record'),
Index('ix_nutrition_user_date', 'user_id', 'start_time'),
)
# ==================== HYDRATION ====================
class HCRawHydration(Base):
"""Hydration records from Health Connect HydrationRecord"""
__tablename__ = "hc_raw_hydration"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
volume_liters = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_hydration_record'),
Index('ix_hydration_user_date', 'user_id', 'start_time'),
)
# ==================== HEART RATE VARIABILITY ====================
class HCRawHeartRateVariabilityRmssd(Base):
"""HRV records from Health Connect HeartRateVariabilityRmssdRecord"""
__tablename__ = "hc_raw_hrv_rmssd"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), index=True)
record_id = Column(String(100), index=True)
data_source = Column(String(200))
data_source_name = Column(String(100))
time = Column(DateTime, index=True)
synced_at = Column(DateTime, default=datetime.datetime.utcnow)
rmssd = Column(Float)
sync_id = Column(Integer, ForeignKey("health_connect_sync_logs.id"), nullable=True)
__table_args__ = (
UniqueConstraint('user_id', 'record_id', 'data_source', name='uq_hrv_rmssd_record'),
Index('ix_hrv_rmssd_user_date', 'user_id', 'time'),
)
+18
View File
@@ -0,0 +1,18 @@
"""Tracking SQLAlchemy model."""
from sqlalchemy import Column, Integer, Date, Boolean, Text, ForeignKey
from sqlalchemy.orm import relationship
from backend.database import Base
class Tracking(Base):
"""Tracking model for recording challenge progress."""
__tablename__ = "tracking"
id = Column(Integer, primary_key=True, index=True)
challenge_id = Column(Integer, ForeignKey("challenges.id"))
date = Column(Date)
reps = Column(Integer, default=0)
completed = Column(Boolean, default=False)
notes = Column(Text, nullable=True)
challenge = relationship("Challenge", back_populates="trackings")
+17
View File
@@ -0,0 +1,17 @@
"""User SQLAlchemy model."""
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from backend.database import Base
class User(Base):
"""User model for authentication and ownership."""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
challenges = relationship("Challenge", back_populates="owner")
health_metrics = relationship("DailyHealthMetrics", back_populates="user")
workouts = relationship("WorkoutSession", back_populates="user")
+48
View File
@@ -0,0 +1,48 @@
"""WorkoutSession SQLAlchemy model."""
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from backend.database import Base
# Zepp activity type mapping
ZEPP_ACTIVITY_TYPES = {
1: "Running",
6: "Walking",
8: "Cycling",
10: "Elliptical",
16: "Free Training",
23: "HIIT",
52: "Stretching",
60: "Yoga",
}
class WorkoutSession(Base):
"""Workout data model."""
__tablename__ = "workout_sessions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
start_time = Column(DateTime, index=True)
end_time = Column(DateTime)
activity_type = Column(String) # Human readable name
activity_type_id = Column(Integer, nullable=True) # Original Zepp type ID
duration_seconds = Column(Integer, default=0)
distance_meters = Column(Float, default=0.0)
calories = Column(Float, default=0.0)
avg_pace = Column(Float, nullable=True) # seconds per meter
max_pace = Column(Float, nullable=True)
min_pace = Column(Float, nullable=True)
avg_hr = Column(Integer, nullable=True)
max_hr = Column(Integer, nullable=True)
min_hr = Column(Integer, nullable=True)
notes = Column(String, nullable=True)
# Data provenance tracking
data_source = Column(String(50), default="health_connect") # "health_connect", "manual"
user = relationship("User", back_populates="workouts")
@staticmethod
def get_activity_name(type_id: int) -> str:
return ZEPP_ACTIVITY_TYPES.get(type_id, f"Activity {type_id}")