import uuid from datetime import datetime from sqlalchemy import String, Float, Integer, DateTime, ForeignKey, func from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.core.database import Base class Activity(Base): __tablename__ = "activities" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) rider_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("riders.id")) name: Mapped[str | None] = mapped_column(String(200), nullable=True) activity_type: Mapped[str] = mapped_column(String(50), default="road") date: Mapped[datetime] = mapped_column(DateTime(timezone=True)) duration: Mapped[int] = mapped_column(Integer) # seconds distance: Mapped[float | None] = mapped_column(Float, nullable=True) # meters elevation_gain: Mapped[float | None] = mapped_column(Float, nullable=True) # meters file_path: Mapped[str | None] = mapped_column(String(500), nullable=True) exercise_sets: Mapped[list | None] = mapped_column(JSONB, nullable=True) # [{exercise_name, reps, weight, duration}] # Link to training plan workout training_plan_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("training_plans.id"), nullable=True) plan_week: Mapped[int | None] = mapped_column(Integer, nullable=True) plan_day: Mapped[str | None] = mapped_column(String(20), nullable=True) # monday, tuesday, ... created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) rider = relationship("Rider", back_populates="activities") metrics = relationship("ActivityMetrics", back_populates="activity", uselist=False, lazy="joined") intervals = relationship("Interval", back_populates="activity", lazy="selectin") data_points = relationship("DataPoint", back_populates="activity", lazy="noload") class ActivityMetrics(Base): __tablename__ = "activity_metrics" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) activity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("activities.id"), unique=True) tss: Mapped[float | None] = mapped_column(Float, nullable=True) normalized_power: Mapped[float | None] = mapped_column(Float, nullable=True) intensity_factor: Mapped[float | None] = mapped_column(Float, nullable=True) variability_index: Mapped[float | None] = mapped_column(Float, nullable=True) avg_power: Mapped[float | None] = mapped_column(Float, nullable=True) max_power: Mapped[int | None] = mapped_column(Integer, nullable=True) avg_hr: Mapped[int | None] = mapped_column(Integer, nullable=True) max_hr: Mapped[int | None] = mapped_column(Integer, nullable=True) avg_cadence: Mapped[int | None] = mapped_column(Integer, nullable=True) avg_speed: Mapped[float | None] = mapped_column(Float, nullable=True) # m/s calories: Mapped[int | None] = mapped_column(Integer, nullable=True) activity = relationship("Activity", back_populates="metrics") class DataPoint(Base): __tablename__ = "data_points" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) activity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("activities.id")) timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) power: Mapped[int | None] = mapped_column(Integer, nullable=True) heart_rate: Mapped[int | None] = mapped_column(Integer, nullable=True) cadence: Mapped[int | None] = mapped_column(Integer, nullable=True) speed: Mapped[float | None] = mapped_column(Float, nullable=True) latitude: Mapped[float | None] = mapped_column(Float, nullable=True) longitude: Mapped[float | None] = mapped_column(Float, nullable=True) altitude: Mapped[float | None] = mapped_column(Float, nullable=True) temperature: Mapped[int | None] = mapped_column(Integer, nullable=True) activity = relationship("Activity", back_populates="data_points") class Interval(Base): __tablename__ = "intervals" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) activity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("activities.id")) start_ts: Mapped[datetime] = mapped_column(DateTime(timezone=True)) end_ts: Mapped[datetime] = mapped_column(DateTime(timezone=True)) interval_type: Mapped[str] = mapped_column(String(50)) # work / rest / climb avg_power: Mapped[float | None] = mapped_column(Float, nullable=True) avg_hr: Mapped[int | None] = mapped_column(Integer, nullable=True) duration: Mapped[int | None] = mapped_column(Integer, nullable=True) # seconds activity = relationship("Activity", back_populates="intervals")