Files
codetutor/backend/src/models/question.py
T

154 lines
5.2 KiB
Python

import enum
import uuid
from typing import TYPE_CHECKING, Any
from sqlalchemy import Boolean, Enum, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, TimestampMixin, UUIDMixin
if TYPE_CHECKING:
from src.models.category import Category
from src.models.pattern import Pattern
class Difficulty(str, enum.Enum):
"""Question difficulty levels."""
EASY = "easy"
MEDIUM = "medium"
HARD = "hard"
# Association tables
class QuestionCategory(Base):
"""Junction table for questions and categories."""
__tablename__ = "question_categories"
question_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("questions.id", ondelete="CASCADE"),
primary_key=True,
)
category_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("categories.id", ondelete="CASCADE"),
primary_key=True,
)
class QuestionPattern(Base):
"""Junction table for questions and patterns."""
__tablename__ = "question_patterns"
question_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("questions.id", ondelete="CASCADE"),
primary_key=True,
)
pattern_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("patterns.id", ondelete="CASCADE"),
primary_key=True,
)
is_optimal: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Relationships
question: Mapped["Question"] = relationship(back_populates="question_patterns")
pattern: Mapped["Pattern"] = relationship()
class Question(Base, UUIDMixin, TimestampMixin):
"""Coding interview question."""
__tablename__ = "questions"
title: Mapped[str] = mapped_column(String(255), nullable=False)
slug: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
difficulty: Mapped[Difficulty] = mapped_column(
Enum(
Difficulty,
name="difficulty_enum",
create_type=False,
values_callable=lambda x: [e.value for e in x],
),
nullable=False,
)
description: Mapped[str] = mapped_column(Text, nullable=False)
constraints: Mapped[str | None] = mapped_column(Text, nullable=True)
examples: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True)
leetcode_id: Mapped[int | None] = mapped_column(Integer, unique=True, nullable=True)
leetcode_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
function_signature: Mapped[str | None] = mapped_column(Text, nullable=True)
test_cases: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True)
# Relationships
explanation: Mapped["Explanation | None"] = relationship(
back_populates="question",
uselist=False,
cascade="all, delete-orphan",
)
solutions: Mapped[list["Solution"]] = relationship(
back_populates="question",
cascade="all, delete-orphan",
)
categories: Mapped[list["Category"]] = relationship(
secondary="question_categories",
back_populates="questions",
)
patterns: Mapped[list["Pattern"]] = relationship(
secondary="question_patterns",
back_populates="questions",
)
question_patterns: Mapped[list["QuestionPattern"]] = relationship(
back_populates="question",
cascade="all, delete-orphan",
)
class Explanation(Base, UUIDMixin):
"""Educational explanation for a question."""
__tablename__ = "explanations"
question_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("questions.id", ondelete="CASCADE"),
unique=True,
nullable=False,
)
approach: Mapped[str] = mapped_column(Text, nullable=False)
intuition: Mapped[str] = mapped_column(Text, nullable=False)
common_pitfalls: Mapped[list[dict[str, Any]] | None] = mapped_column(JSONB, nullable=True)
key_takeaways: Mapped[list[str] | None] = mapped_column(JSONB, nullable=True)
time_complexity: Mapped[str] = mapped_column(Text, nullable=False)
space_complexity: Mapped[str] = mapped_column(Text, nullable=False)
complexity_explanation: Mapped[str | None] = mapped_column(Text, nullable=True)
pattern_comparison: Mapped[str | None] = mapped_column(Text, nullable=True)
# Relationships
question: Mapped["Question"] = relationship(back_populates="explanation")
class Solution(Base, UUIDMixin):
"""Code solution for a question."""
__tablename__ = "solutions"
question_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("questions.id", ondelete="CASCADE"),
nullable=False,
)
approach_name: Mapped[str] = mapped_column(String(100), nullable=False)
code: Mapped[str] = mapped_column(Text, nullable=False)
language: Mapped[str] = mapped_column(String(20), default="python", nullable=False)
is_optimal: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
explanation: Mapped[str | None] = mapped_column(Text, nullable=True)
# Relationships
question: Mapped["Question"] = relationship(back_populates="solutions")