from datetime import date, datetime from sqlalchemy import JSON, Boolean, Date, DateTime, Numeric, String, Text, func from sqlalchemy.orm import Mapped, mapped_column from app.db.session import Base class Tenant(Base): __tablename__ = "tenants" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(128), nullable=False) type: Mapped[str] = mapped_column(String(32), nullable=False, default="organization") status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") retention_days: Mapped[int] = mapped_column(default=1095) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) tenant_id: Mapped[int | None] = mapped_column(nullable=True) phone: Mapped[str | None] = mapped_column(String(32), nullable=True) phone_hash: Mapped[str | None] = mapped_column(String(128), nullable=True, unique=True) wechat_openid: Mapped[str | None] = mapped_column(String(128), nullable=True, unique=True) role: Mapped[str] = mapped_column(String(32), nullable=False, default="parent") status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") consent_signed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) privacy_version: Mapped[str | None] = mapped_column(String(16), default="v1") last_login_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) class Child(Base): __tablename__ = "children" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) tenant_id: Mapped[int | None] = mapped_column(nullable=True) parent_user_id: Mapped[int] = mapped_column(nullable=False, index=True) coach_user_id: Mapped[int | None] = mapped_column(nullable=True) name: Mapped[str] = mapped_column(String(64), nullable=False) gender: Mapped[str] = mapped_column(String(16), nullable=False, default="unknown") birthday: Mapped[date] = mapped_column(Date, nullable=False) height: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True) weight: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True) contraindications: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) class Video(Base): __tablename__ = "videos" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) child_id: Mapped[int] = mapped_column(nullable=False, index=True) uploaded_by: Mapped[int] = mapped_column(nullable=False) scene: Mapped[str] = mapped_column(String(64), nullable=False) object_key: Mapped[str] = mapped_column(String(512), nullable=False, unique=True) duration_seconds: Mapped[int | None] = mapped_column(nullable=True) size_bytes: Mapped[int | None] = mapped_column(nullable=True) status: Mapped[str] = mapped_column(String(32), nullable=False, default="uploaded") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class AnalysisTask(Base): __tablename__ = "analysis_tasks" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) child_id: Mapped[int] = mapped_column(nullable=False, index=True) video_id: Mapped[int] = mapped_column(nullable=False, index=True) task_type: Mapped[str] = mapped_column(String(64), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="CREATED") progress: Mapped[int] = mapped_column(default=0) model_version: Mapped[str | None] = mapped_column(String(64), nullable=True) error_code: Mapped[str | None] = mapped_column(String(64), nullable=True) error_message: Mapped[str | None] = mapped_column(String(512), nullable=True) result: Mapped[dict | None] = mapped_column(JSON, nullable=True) idempotency_key: Mapped[str | None] = mapped_column(String(128), nullable=True) retry_count: Mapped[int] = mapped_column(default=0) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) class Report(Base): __tablename__ = "reports" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) child_id: Mapped[int] = mapped_column(nullable=False, index=True) task_id: Mapped[int] = mapped_column(nullable=False, unique=True) report_type: Mapped[str] = mapped_column(String(64), nullable=False, default="posture_screening") risk_level: Mapped[str] = mapped_column(String(32), nullable=False) summary: Mapped[str] = mapped_column(Text, nullable=False) metrics: Mapped[list] = mapped_column(JSON, nullable=False) recommendations: Mapped[list] = mapped_column(JSON, nullable=False) disclaimer: Mapped[str] = mapped_column(Text, nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="published") reviewed_by: Mapped[int | None] = mapped_column(nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) class Exercise(Base): __tablename__ = "exercises" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(128), nullable=False) category: Mapped[str] = mapped_column(String(64), nullable=False, default="posture") target_issue: Mapped[str | None] = mapped_column(String(128), nullable=True) difficulty: Mapped[str] = mapped_column(String(32), nullable=False, default="basic") duration_seconds: Mapped[int] = mapped_column(default=300) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class TrainingPlan(Base): __tablename__ = "training_plans" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) child_id: Mapped[int] = mapped_column(nullable=False, index=True) report_id: Mapped[int | None] = mapped_column(nullable=True) coach_user_id: Mapped[int | None] = mapped_column(nullable=True) goal: Mapped[str] = mapped_column(String(256), nullable=False) cycle_days: Mapped[int] = mapped_column(default=28) status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") plan_detail: Mapped[dict] = mapped_column(JSON, nullable=False) started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now()) class TrainingRecord(Base): __tablename__ = "training_records" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) plan_id: Mapped[int] = mapped_column(nullable=False, index=True) child_id: Mapped[int] = mapped_column(nullable=False, index=True) exercise_id: Mapped[int] = mapped_column(nullable=False) completed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) score: Mapped[int | None] = mapped_column(nullable=True) duration_seconds: Mapped[int | None] = mapped_column(nullable=True) feedback: Mapped[dict | None] = mapped_column(JSON, nullable=True) note: Mapped[str | None] = mapped_column(String(512), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())