--- phase: 01-foundation plan: 02 type: execute --- Create SQLAlchemy models for Project, Discussion, Round, Message, and Consensus. Purpose: Define the data model that powers all discussion features. Output: Complete models.py with all relationships, ready for database creation. ~/.claude/get-shit-done/workflows/execute-phase.md ~/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @SPEC.md @CLAUDE.md **Data model from SPEC.md:** ``` Project (has many) -> Discussion (has many) -> Round (has many) -> Message \-> Discussion (has one) -> Consensus ``` **Field specifications:** - Project: id (uuid), name, created_at, updated_at, models (JSON array), settings (JSON) - Discussion: id, project_id (FK), question, type (open|discuss), status (active|completed), created_at - Round: id, discussion_id (FK), round_number, type (parallel|sequential) - Message: id, round_id (FK), model, content, timestamp, is_direct - Consensus: id, discussion_id (FK), agreements (JSON array), disagreements (JSON array), generated_at, generated_by **Constraints:** - Use SQLAlchemy 2.0 style (mapped_column, DeclarativeBase) - Type hints required - SQLite compatible (use JSON type, not ARRAY) Task 1: Create base model and enums src/moai/core/models.py Create src/moai/core/models.py with: **Imports:** SQLAlchemy 2.0 style (DeclarativeBase, Mapped, mapped_column, relationship), uuid, datetime, enum **Base class:** - class Base(DeclarativeBase): pass **Enums (use Python Enum, store as string):** - DiscussionType: OPEN = "open", DISCUSS = "discuss" - DiscussionStatus: ACTIVE = "active", COMPLETED = "completed" - RoundType: PARALLEL = "parallel", SEQUENTIAL = "sequential" **UUID helper:** - Use uuid4() for default IDs - Store as String(36) for SQLite compatibility (NOT native UUID type) Add module docstring explaining the data model. python -c "from moai.core.models import Base, DiscussionType, DiscussionStatus, RoundType" Base class and enums importable Task 2: Create Project and Discussion models src/moai/core/models.py Add to models.py: **Project model:** - id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) - name: Mapped[str] = mapped_column(String(255)) - created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - models: Mapped[list] = mapped_column(JSON, default=list) - stores ["claude", "gpt", "gemini"] - settings: Mapped[dict] = mapped_column(JSON, default=dict) - stores {default_rounds, consensus_threshold, system_prompt_override} - discussions: Mapped[list["Discussion"]] = relationship(back_populates="project", cascade="all, delete-orphan") **Discussion model:** - id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) - project_id: Mapped[str] = mapped_column(ForeignKey("project.id")) - question: Mapped[str] = mapped_column(Text) - type: Mapped[DiscussionType] = mapped_column(Enum(DiscussionType)) - status: Mapped[DiscussionStatus] = mapped_column(Enum(DiscussionStatus), default=DiscussionStatus.ACTIVE) - created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - project: Mapped["Project"] = relationship(back_populates="discussions") - rounds: Mapped[list["Round"]] = relationship(back_populates="discussion", cascade="all, delete-orphan") - consensus: Mapped["Consensus"] = relationship(back_populates="discussion", uselist=False, cascade="all, delete-orphan") Use __tablename__ = "project" and "discussion" (singular, lowercase). python -c "from moai.core.models import Project, Discussion; print(Project.__tablename__, Discussion.__tablename__)" Project and Discussion models defined with bidirectional relationships Task 3: Create Round, Message, and Consensus models src/moai/core/models.py Add to models.py: **Round model:** - id: Mapped[str] (uuid, primary key) - discussion_id: Mapped[str] = mapped_column(ForeignKey("discussion.id")) - round_number: Mapped[int] - type: Mapped[RoundType] = mapped_column(Enum(RoundType)) - discussion: Mapped["Discussion"] = relationship(back_populates="rounds") - messages: Mapped[list["Message"]] = relationship(back_populates="round", cascade="all, delete-orphan") **Message model:** - id: Mapped[str] (uuid, primary key) - round_id: Mapped[str] = mapped_column(ForeignKey("round.id")) - model: Mapped[str] = mapped_column(String(50)) - e.g., "claude", "gpt", "gemini" - content: Mapped[str] = mapped_column(Text) - timestamp: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - is_direct: Mapped[bool] = mapped_column(Boolean, default=False) - true if @mentioned - round: Mapped["Round"] = relationship(back_populates="messages") **Consensus model:** - id: Mapped[str] (uuid, primary key) - discussion_id: Mapped[str] = mapped_column(ForeignKey("discussion.id"), unique=True) - agreements: Mapped[list] = mapped_column(JSON, default=list) - bullet point strings - disagreements: Mapped[list] = mapped_column(JSON, default=list) - [{topic, positions: {model: position}}] - generated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - generated_by: Mapped[str] = mapped_column(String(50)) - which model summarized - discussion: Mapped["Discussion"] = relationship(back_populates="consensus") Use __tablename__ = "round", "message", "consensus" (singular, lowercase). python -c "from moai.core.models import Round, Message, Consensus; print('All models imported')" All 5 models defined with complete relationships Before declaring plan complete: - [ ] `python -c "from moai.core.models import Base, Project, Discussion, Round, Message, Consensus"` succeeds - [ ] `ruff check src/moai/core/models.py` passes - [ ] All foreign keys reference correct tables - [ ] All relationships are bidirectional - [ ] All fields have type hints - All tasks completed - All 5 models importable - No linting errors - Relationships match SPEC.md data model diagram After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`