feat: complete pragativeer v2 frontend and backend, docker setup, and cbt engine

This commit is contained in:
2026-07-06 17:08:13 +05:30
parent ce01795aa9
commit 426b98ddab
66 changed files with 7949 additions and 363 deletions

168
backend/models.py Normal file
View File

@@ -0,0 +1,168 @@
from datetime import datetime, date
from enum import Enum
from typing import List, Optional, Dict, Any
from uuid import UUID, uuid4
from sqlmodel import Field, SQLModel, Relationship, JSON
# --- ENUMS ---
class ClassEnum(str, Enum):
ELEVENTH = "11"
TWELFTH = "12"
DROPPER = "Dropper"
class ExamEnum(str, Enum):
JEE_MAIN = "JEE_Main"
JEE_ADVANCED = "JEE_Advanced"
NEET = "NEET"
class MediumEnum(str, Enum):
ENGLISH = "English"
HINDI = "Hindi"
BILINGUAL = "Bilingual"
class TierEnum(str, Enum):
FREE = "Free"
PRO = "Pro"
AI_COACH = "Al_Coach"
class QuestionTypeEnum(str, Enum):
MCQ_SINGLE = "MCQ_Single"
MCQ_MULTIPLE = "MCQ_Multiple"
NUMERICAL = "Numerical"
class SourceTypeEnum(str, Enum):
DPS = "DPS"
TEST = "Test"
MICROTEST = "MicroTest"
class ErrorTypeEnum(str, Enum):
CONCEPTUAL = "Conceptual"
CALCULATION = "Calculation"
READING = "Reading"
TIME_PRESSURE = "Time_Pressure"
# --- CORE DB MODELS ---
class User(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
email: Optional[str] = Field(default=None, unique=True)
phone: Optional[str] = Field(default=None, unique=True)
password_hash: str
name: str
student_class: ClassEnum = Field(alias="class")
target_exam: ExamEnum
medium: MediumEnum
school: Optional[str] = None
city: str
state: str
tier: TierEnum = Field(default=TierEnum.FREE)
trial_end: Optional[datetime] = None
subscription_end: Optional[datetime] = None
streak_count: int = Field(default=0)
longest_streak: int = Field(default=0)
created_at: datetime = Field(default_factory=datetime.utcnow)
last_active: datetime = Field(default_factory=datetime.utcnow)
class Parent(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
email: str
phone: str
password_hash: str
name: str
language: str = Field(default="English") # e.g., Hindi, English
linked_students: List[str] = Field(default=[], sa_type=JSON)
created_at: datetime = Field(default_factory=datetime.utcnow)
class Question(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
subject: str # Physics, Chemistry, Maths, Biology
topic: str
subtopic: str
difficulty: str # Easy, Medium, Hard
type: QuestionTypeEnum
question_text: str
question_text_hi: Optional[str] = None
options: Optional[Dict[str, Any]] = Field(default=None, sa_type=JSON)
options_hi: Optional[Dict[str, Any]] = Field(default=None, sa_type=JSON)
correct_answer: Dict[str, Any] = Field(sa_type=JSON)
solution_text: str
solution_text_hi: Optional[str] = None
language: MediumEnum
year: Optional[int] = None
exam: Optional[ExamEnum] = None
tags: Optional[str] = None
is_active: bool = Field(default=True)
created_by: UUID
verified_by: Optional[UUID] = None
class DPS(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
date: date
title: str
questions: List[str] = Field(default=[], sa_type=JSON) # Array of question UUID strings
difficulty: str
time_limit: int
is_active: bool = Field(default=True)
created_by: UUID
class Test(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
title: str
type: str # Weekly, Mock, PYQ
exam_pattern: ExamEnum
sections: Dict[str, Any] = Field(sa_type=JSON) # Array of {name, questions[], time_limit}
total_time: int
difficulty: str
scheduled_date: Optional[datetime] = None
is_active: bool = Field(default=True)
created_by: UUID
class Attempt(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
user_id: UUID = Field(foreign_key="user.id")
source_type: SourceTypeEnum
source_id: UUID
started_at: datetime
submitted_at: Optional[datetime] = None
time_taken: Optional[int] = None # in seconds
answers: Optional[Dict[str, Any]] = Field(default=None, sa_type=JSON) # Array of {question_id, selected_answer, is_correct, time_spent}
score: int = Field(default=0)
total_questions: int
accuracy: float = Field(default=0.0)
status: str = Field(default="In_Progress") # In_Progress, Submitted, Timed_Out
class Mistake(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
user_id: UUID = Field(foreign_key="user.id")
question_id: UUID = Field(foreign_key="question.id")
attempt_id: UUID = Field(foreign_key="attempt.id")
error_type: ErrorTypeEnum
topic: str
subtopic: str
next_review: datetime
review_count: int = Field(default=0)
consecutive_correct: int = Field(default=0)
is_mastered: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
class StudyPlan(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
user_id: UUID = Field(foreign_key="user.id")
date: date
tasks: Dict[str, Any] = Field(sa_type=JSON) # Array of {type, description, duration_minutes, status}
is_completed: bool = Field(default=False)
generated_at: datetime = Field(default_factory=datetime.utcnow)
feedback: Optional[str] = None
class Subscription(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True)
user_id: UUID = Field(foreign_key="user.id")
tier: TierEnum
billing_cycle: str # Monthly, Quarterly, Annual
start_date: datetime
end_date: datetime
amount: int # Stored in Paise (INR)
payment_id: str # Razorpay order ID / payment ID
status: str # Active, Expired, Cancelled, Paused
auto_renew: bool = Field(default=True)
coupon_code: Optional[str] = None