|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import ast |
|
from typing import Dict, List |
|
from pymongo import MongoClient |
|
from datetime import datetime |
|
import openai |
|
import google.generativeai as genai |
|
from google.generativeai import GenerativeModel |
|
from dotenv import load_dotenv |
|
import os |
|
from file_upload_vectorize import resources_collection, vectors_collection, courses_collection2, faculty_collection |
|
|
|
|
|
load_dotenv() |
|
MONGO_URI = os.getenv('MONGO_URI') |
|
OPENAI_KEY = os.getenv('OPENAI_KEY') |
|
GEMINI_KEY = os.getenv('GEMINI_KEY') |
|
|
|
|
|
openai.api_key = OPENAI_KEY |
|
genai.configure(api_key=GEMINI_KEY) |
|
model = genai.GenerativeModel('gemini-1.5-flash') |
|
|
|
|
|
client = MongoClient(MONGO_URI) |
|
db = client['novascholar_db'] |
|
quizzes_collection = db["quizzes"] |
|
surprise_quizzes_collection = db["surprise_quizzes"] |
|
|
|
def strip_code_markers(response_text): |
|
"""Strip off the markers ``` and python from a LLM model's response""" |
|
if response_text.startswith("```python"): |
|
response_text = response_text[len("```python"):].strip() |
|
if response_text.startswith("```"): |
|
response_text = response_text[len("```"):].strip() |
|
if response_text.endswith("```"): |
|
response_text = response_text[:-len("```")].strip() |
|
return response_text |
|
|
|
|
|
|
|
def generate_mcqs(context, num_questions, session_title, session_description): |
|
"""Generate MCQs either from context or session details""" |
|
try: |
|
|
|
if context: |
|
prompt = f""" |
|
Based on the following content, generate {num_questions} multiple choice questions. |
|
Format each question as a Python dictionary with the following structure: |
|
{{ |
|
"question": "Question text here", |
|
"options": ["A) option1", "B) option2", "C) option3", "D) option4"], |
|
"correct_option": "A) option1" or "B) option2" or "C) option3" or "D) option4" |
|
}} |
|
|
|
Content: |
|
{context} |
|
|
|
Generate challenging but clear questions that test understanding of key concepts. |
|
Return only the Python list of dictionaries. |
|
""" |
|
else: |
|
prompt = f""" |
|
Generate {num_questions} multiple choice questions about the topic: |
|
Title: {session_title} |
|
Description: {session_description} |
|
|
|
Format each question as a Python dictionary with the following structure: |
|
{{ |
|
"question": "Question text here", |
|
"options": ["A) option1", "B) option2", "C) option3", "D) option4"], |
|
"correct_option": "A" or "B" or "C" or "D" |
|
}} |
|
|
|
Generate challenging but clear questions. |
|
Return only the Python list of dictionaries without any additional formatting or markers |
|
Do not write any other text, do not start the response with (```python), do not end the response with backticks(```) |
|
A Sample response should look like this: Response Text: [ |
|
{ |
|
"question": "Which of the following is NOT a valid data type in C++?", |
|
"options": ["int", "double", "boolean", "char"], |
|
"correct_option": "C" |
|
} |
|
] (Notice that there are no backticks(```) around the response and no (```python)) |
|
. |
|
""" |
|
|
|
response = model.generate_content(prompt) |
|
response_text = response.text.strip() |
|
print("Response Text:", response_text) |
|
modified_response_text = strip_code_markers(response_text) |
|
print("Response Text Modified to:", modified_response_text) |
|
|
|
mcqs = ast.literal_eval(modified_response_text) |
|
print(mcqs) |
|
if not mcqs: |
|
raise ValueError("No questions generated") |
|
return mcqs |
|
except Exception as e: |
|
print(f"Error generating MCQs: , error: {e}") |
|
return None |
|
|
|
def generate_pre_class_question_bank(context: str, session_title: str, session_description: str) -> List[Dict]: |
|
"""Generate a bank of 40 MCQ questions for pre-class quiz""" |
|
try: |
|
prompt = f""" |
|
Based on the following content, generate 50 multiple choice questions for a question bank. |
|
Format each question as a Python dictionary with this exact structure: |
|
{{ |
|
"question": "Question text here", |
|
"options": ["A) option1", "B) option2", "C) option3", "D) option4"], |
|
"correct_option": "A) option1" or "B) option2" or "C) option3" or "D) option4", |
|
"difficulty": "easy|medium|hard", |
|
"topic": "specific topic from content" |
|
}} |
|
|
|
Content: |
|
{context} |
|
|
|
Session Title: {session_title} |
|
Description: {session_description} |
|
|
|
Requirements: |
|
1. Questions should cover all important concepts |
|
2. Mix of easy (20%), medium (50%), and hard (30%) questions |
|
3. Clear and unambiguous questions |
|
4. Options should be plausible but only one correct answer |
|
5. Include topic tags for categorization |
|
6. **NUMBER OF QUESTIONS SHOULD BE GREATER THAN OR EQUAL TO 60** |
|
|
|
Return ONLY the JSON array of questions. |
|
""" |
|
|
|
response = model.generate_content( |
|
prompt, |
|
generation_config=genai.GenerationConfig( |
|
|
|
response_mime_type="application/json" |
|
) |
|
) |
|
response_text = response.text.strip() |
|
modified_response_text = strip_code_markers(response_text) |
|
question_bank = ast.literal_eval(modified_response_text) |
|
print(question_bank) |
|
|
|
if not isinstance(question_bank, list): |
|
raise ValueError("Invalid question bank format or count") |
|
|
|
return question_bank |
|
|
|
except Exception as e: |
|
print(f"Error generating question bank: {e}") |
|
return None |
|
|
|
|
|
def save_pre_class_quiz_with_bank( |
|
course_id: str, |
|
session_id: str, |
|
title: str, |
|
question_bank: List[Dict], |
|
num_questions: int, |
|
duration: int, |
|
user_id: str |
|
) -> str: |
|
"""Save pre-class quiz with question bank to database""" |
|
try: |
|
quiz_data = { |
|
"user_id": user_id, |
|
"course_id": course_id, |
|
"session_id": session_id, |
|
"title": title, |
|
"question_bank": question_bank, |
|
"num_questions": num_questions, |
|
"duration_minutes": duration, |
|
"created_at": datetime.now(), |
|
"status": "active", |
|
"submissions": [], |
|
"quiz_type": "pre_class" |
|
} |
|
result = quizzes_collection.insert_one(quiz_data) |
|
return result.inserted_id |
|
except Exception as e: |
|
print(f"Error saving quiz with question bank: {e}") |
|
return None |
|
|
|
def get_randomized_questions(quiz_id) -> List[Dict]: |
|
"""Get randomly selected questions from question bank based on quiz settings""" |
|
try: |
|
quiz = quizzes_collection.find_one({"_id": quiz_id}) |
|
if not quiz: |
|
return None |
|
|
|
num_questions = quiz.get("num_questions", 0) |
|
question_bank = quiz.get("question_bank", []) |
|
|
|
if not question_bank or num_questions <= 0: |
|
return None |
|
|
|
|
|
import random |
|
selected_questions = random.sample(question_bank, num_questions) |
|
|
|
return selected_questions |
|
|
|
except Exception as e: |
|
print(f"Error getting randomized questions: {e}") |
|
return None |
|
|
|
|
|
def save_pre_class_quiz(course_id: str, session_id: str, title: str, questions: List[Dict], user_id: str, duration: int): |
|
"""Save pre-class quiz to database""" |
|
try: |
|
quiz_data = { |
|
"user_id": user_id, |
|
"course_id": course_id, |
|
"session_id": session_id, |
|
"title": title, |
|
"questions": questions, |
|
"created_at": datetime.now(), |
|
"status": "active", |
|
"submissions": [], |
|
"duration_minutes": duration, |
|
"quiz_type": "pre_class" |
|
} |
|
result = quizzes_collection.insert_one(quiz_data) |
|
return result.inserted_id |
|
except Exception as e: |
|
print(f"Error saving quiz: {e}") |
|
return None |
|
|
|
|
|
def save_quiz(course_id, session_id, title, questions, user_id): |
|
"""Save quiz to database""" |
|
try: |
|
quiz_data = { |
|
"user_id": user_id, |
|
"course_id": course_id, |
|
"session_id": session_id, |
|
"title": title, |
|
"questions": questions, |
|
"duration_minutes": 15, |
|
"created_at": datetime.utcnow(), |
|
"status": "active", |
|
"submissions": [] |
|
} |
|
result = quizzes_collection.insert_one(quiz_data) |
|
return result.inserted_id |
|
except Exception as e: |
|
print(f"Error saving quiz: {e}") |
|
return None |
|
|
|
def save_surprise_quiz(course_id, session_id, title, questions, user_id, no_minutes): |
|
"""Save quiz to database""" |
|
try: |
|
quiz_data = { |
|
"user_id": user_id, |
|
"course_id": course_id, |
|
"session_id": session_id, |
|
"title": title, |
|
"questions": questions, |
|
"created_at": datetime.now(), |
|
"status": "active", |
|
"submissions": [], |
|
"no_minutes": no_minutes |
|
} |
|
result = surprise_quizzes_collection.insert_one(quiz_data) |
|
return result.inserted_id |
|
except Exception as e: |
|
print(f"Error saving quiz: {e}") |
|
return None |
|
|
|
|
|
def get_student_quiz_score(quiz_id, student_id): |
|
"""Get student's score for a specific quiz""" |
|
quiz = quizzes_collection.find_one( |
|
{ |
|
"_id": quiz_id, |
|
"submissions.student_id": student_id |
|
}, |
|
{"submissions.$": 1} |
|
) |
|
if quiz and quiz.get('submissions'): |
|
return quiz['submissions'][0].get('score') |
|
return None |
|
|
|
def get_student_surprise_quiz_score(quiz_id, student_id): |
|
"""Get student's score for a specific quiz""" |
|
quiz = surprise_quizzes_collection.find_one( |
|
{ |
|
"_id": quiz_id, |
|
"submissions.student_id": student_id |
|
}, |
|
{"submissions.$": 1} |
|
) |
|
if quiz and quiz.get('submissions'): |
|
return quiz['submissions'][0].get('score') |
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def submit_quiz_answers(quiz_id, student_id, student_answers): |
|
"""Submit and score student's quiz answers""" |
|
try: |
|
quiz = quizzes_collection.find_one({"_id": quiz_id}) |
|
if not quiz: |
|
return None |
|
|
|
total_questions = len(quiz['questions']) |
|
|
|
print("\nScoring Debug:") |
|
print(f"Total questions: {total_questions}") |
|
|
|
|
|
correct_answers = 0 |
|
|
|
for q_idx, question in enumerate(quiz['questions']): |
|
student_answer = student_answers.get(str(q_idx)) |
|
correct_option = question['correct_option'] |
|
|
|
|
|
print(f"\nQuestion {q_idx + 1}:") |
|
print(f"Student answer: {student_answer}") |
|
print(f"Correct option: {correct_option}") |
|
|
|
if student_answer: |
|
|
|
|
|
|
|
|
|
if student_answer == correct_option: |
|
correct_answers += 1 |
|
print(f"✓ Correct! Total correct: {correct_answers}") |
|
else: |
|
print("✗ Incorrect") |
|
|
|
score = (correct_answers / total_questions) * 100 |
|
print(f"\nFinal Score: {score}% ({correct_answers}/{total_questions} correct)") |
|
|
|
|
|
submission_data = { |
|
"student_id": student_id, |
|
"answers": student_answers, |
|
"score": score, |
|
"submitted_at": datetime.utcnow(), |
|
"correct_answers_count": correct_answers, |
|
"total_questions": total_questions |
|
} |
|
|
|
|
|
result = quizzes_collection.update_one( |
|
{"_id": quiz_id}, |
|
{"$push": {"submissions": submission_data}} |
|
) |
|
|
|
return score if result.modified_count > 0 else None |
|
|
|
except Exception as e: |
|
print(f"Error submitting quiz: {e}") |
|
return None |
|
|
|
def submit_surprise_quiz_answers(quiz_id, student_id, student_answers): |
|
"""Submit and score student's quiz answers""" |
|
try: |
|
quiz = surprise_quizzes_collection.find_one({"_id": quiz_id}) |
|
if not quiz: |
|
return None |
|
|
|
|
|
correct_answers = 0 |
|
total_questions = len(quiz['questions']) |
|
|
|
for q_idx, question in enumerate(quiz['questions']): |
|
student_answer = student_answers.get(str(q_idx)) |
|
if student_answer: |
|
|
|
answer_letter = student_answer.split(')')[0].strip() |
|
if answer_letter == question['correct_option']: |
|
correct_answers += 1 |
|
|
|
score = (correct_answers / total_questions) * 100 |
|
|
|
|
|
submission_data = { |
|
"student_id": student_id, |
|
"answers": student_answers, |
|
"score": score, |
|
"submitted_at": datetime.utcnow() |
|
} |
|
|
|
|
|
result = surprise_quizzes_collection.update_one( |
|
{"_id": quiz_id}, |
|
{"$push": {"submissions": submission_data}} |
|
) |
|
|
|
return score if result.modified_count > 0 else None |
|
|
|
except Exception as e: |
|
print(f"Error submitting quiz: {e}") |
|
return None |