0% found this document useful (0 votes)
9 views85 pages

User Registration and Email Verification

The document outlines a user registration, login, and password reset system using Flask, including email verification and role-based access control. It includes functions for registering users, verifying emails, logging in, and handling password resets, with appropriate error handling and validation. The code also incorporates sending emails for verification and password reset requests using SMTP.

Uploaded by

Rafael's Film
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views85 pages

User Registration and Email Verification

The document outlines a user registration, login, and password reset system using Flask, including email verification and role-based access control. It includes functions for registering users, verifying emails, logging in, and handling password resets, with appropriate error handling and validation. The code also incorporates sending emails for verification and password reset requests using SMTP.

Uploaded by

Rafael's Film
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

APPENDIX J confirm_password =

[Link]("confirm_password")

Visual Studio Source Code (Website)


# Create username from email
//=========Backend==========// username = [Link]("@")[0] if email else
None
//======[Link]= ====//
from flask import request, jsonify, current_app,
session, url_for, render_template # Basic validation

from [Link] import get_db if not all([full_name, email, username,


password, confirm_password]):
from [Link] import User
return jsonify({"status": "error", "message":
from [Link] import "All fields are required"}), 400
generate_password_hash, check_password_hash
import re
if password != confirm_password:
import traceback
return jsonify({"status": "error", "message":
import datetime "Passwords do not match"}), 400
import uuid
import smtplib # Email validation regex
from [Link] import MIMEText email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-
zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
from [Link] import
MIMEMultipart if not [Link](email_pattern, email):
return jsonify({"status": "error", "message":
"Invalid email format"}), 400

def register_user():
try:
"""
db = next(get_db())
User registration with email verification
"""
# Check if email already exists
data = request.get_json()
existing_user =
[Link](User).filter([Link] == email).first()
# Extract and validate user data
if existing_user:
full_name = [Link]("full_name")
if existing_user.is_verified:
email = [Link]("email")
return jsonify({"status": "error",
password = [Link]("password") "message": "Email already registered"}), 409
else:
# Resend verification email for [Link](new_user)
unverified users
[Link]()
send_verification_email(existing_user,
db) [Link](new_user)

return jsonify({"status": "success",


"message": "Verification email sent"}), 200 # Send verification email
send_verification_email(new_user, db)
# Generate unique username if exists
existing_username = return jsonify({
[Link](User).filter([Link] ==
username).first() "status": "success",
if existing_username: "message": "Registration successful.
Please check your email to verify your
base_username = username account.",
counter = 1 "username": username,
while existing_username: }), 201
username = f"{base_username}
{counter}"
except Exception as e:
counter += 1
[Link]()
existing_username =
[Link](User).filter([Link] == current_app.[Link](f"Registration
username).first() failed: {str(e)}")
return jsonify({"status": "error", "message":
f"Registration failed: {str(e)}"}), 500
# Create new user
hashed_password =
generate_password_hash(password)
new_user = User( def send_verification_email(user, db):
full_name=full_name, """
email=email, Send email verification link to user
username=username, """
password=hashed_password, # Generate verification token with 24-hour
expiry
is_active=False, # User starts inactive
until email verified verification_token = str(uuid.uuid4())
is_verified=False, token_expiry = [Link]() +
[Link](hours=24)
)

# Save token to user


user.verification_token = verification_token </body>
user.verification_token_expiry = token_expiry </html>
[Link]() """

# Generate verification URL try:


base_url = request.host_url.rstrip("/") # Send email using STARTTLS
verify_url = f"{base_url}/verify-email? message = MIMEMultipart("alternative")
token={verification_token}"
message["Subject"] = "Verify Your Email -
Pre-board EXAMaker"
# Email configuration message["From"] = f"Verification
<{sender_email}>"
smtp_host = "[Link]"
message["To"] = [Link]
smtp_port = 587
smtp_username =
"forgotpassword@[Link]" part1 = MIMEText(html_content, "html")
smtp_password = ")dvOF=VdykQy" [Link](part1)
sender_email = "forgotpassword@preboard-
[Link]"
with [Link](smtp_host, smtp_port,
timeout=10) as server:
# Create email content (HTML and text [Link]()
versions)
[Link]()
html_content = f"""
[Link]()
<html>
[Link](smtp_username,
<head><style>body {{ font-family: Arial, smtp_password)
sans-serif; }}</style></head>
[Link](sender_email,
<body> [Link], message.as_string())
<h2>Verify Your Email - Pre-board
EXAMaker</h2>
current_app.[Link](f"Verification
<p>Dear {user.full_name},</p> email sent to {[Link]}")
<p>Click the button below to verify your return True
email:</p>
<a href="{verify_url}" style="background-
color: #800000; color: white; padding: 10px except Exception as e:
20px; text-decoration: none;">Verify Email</a> current_app.[Link](f"Failed to send
<p>Or copy this URL: {verify_url}</p> email: {str(e)}")

<p>This link expires in 24 hours.</p>


# Auto-verify in development mode if not user:
if return jsonify({"status": "error",
current_app.[Link]("AUTO_VERIFY_EMA "message": "Invalid verification token"}), 400
ILS", False):
user.is_verified = True
if user.is_verified:
user.is_active = True
return jsonify({"status": "success",
[Link]() "message": "Email already verified"})
current_app.[Link](f"Auto-verified
user {[Link]} in development mode")
# Check if token is expired
return True
if (not user.verification_token_expiry or
user.verification_token_expiry <
return False [Link]()):
return jsonify({"status": "error",
"message": "Verification token has expired"}),
400
def verify_email_with_token():
""" # Verify user
Handle email verification with token user.is_verified = True
""" user.is_active = True
token = [Link]("token") user.verification_token = None
db = None user.verification_token_expiry = None
[Link]()
try:
db = next(get_db()) return jsonify({"status": "success",
"message": "Email verified successfully!"})

if not token:
return jsonify({"status": "error", except Exception as e:
"message": "Verification token is missing"}), if db:
400
[Link]()
current_app.[Link](f"Email
# Find user by verification token verification error: {str(e)}")
user = return jsonify({"status": "error", "message":
[Link](User).filter(User.verification_token == "Verification failed"}), 500
token).first()
finally:
if db: return jsonify({"status": "error",
"message": "Invalid credentials"}), 401
[Link]()

# Role-based validation
if [Link] == "admin" and
def login_user(): is_teacher_login:
""" return jsonify({"status": "error",
User login with role-based validation "message": "Admin users must log in through
admin portal"}), 403
"""
data = request.get_json()
if [Link] == "program_chair" and not
is_teacher_login:
identifier = data["email"] # Can be email or return jsonify({"status": "error",
username "message": "Course selection required for
Program Chair"}), 400
password = data["password"]
course_id_str = [Link]("course") # For
program chair login # Check user status
if not user.is_verified:
is_teacher_login = bool(course_id_str) return jsonify({"status": "error",
"message": "Account not verified"}), 403

try:
if not user.is_active:
db = next(get_db())
return jsonify({"status": "error",
"message": "Account is inactive"}), 403
# Find user by email or username
if "@" in identifier:
# Validate course assignment for program
user = [Link](User).filter([Link] chairs
== identifier).first()
if [Link] == "program_chair":
else:
if not course_id_str:
user =
return jsonify({"status": "error",
[Link](User).filter([Link] ==
"message": "Course selection required"}), 400
identifier).first()

try:
if not user or not
check_password_hash([Link], course_id = int(course_id_str)
password):
except ValueError:
return jsonify({"status": "error",
"message": "Invalid course ID"}), 400
try:
db = next(get_db())
if user.category_id != course_id:
user = [Link](User).filter([Link] ==
return jsonify({"status": "error", email).first()
"message": "Access denied to selected
program"}), 403
if not user:

# Set session data # Return success even if email doesn't


exist (security)
[Link] = True
return jsonify({"status": "success",
session["logged_in"] = True "message": "If email registered, reset
instructions sent"})
session["user_id"] = [Link]
session["username"] = [Link]
# Generate reset token
session["role"] = [Link]
reset_token = str(uuid.uuid4())
session["category_id"] = user.category_id
token_expiry = [Link]() +
[Link](hours=24)
return jsonify({ user.reset_token = reset_token
"status": "success", user.reset_token_expiry = token_expiry
"message": "Login successful", [Link]()
"user": user.to_dict(),
}), 200 # Generate reset URL
base_url = request.host_url.rstrip("/")
except Exception as e: reset_url = f"{base_url}/reset-password?
return jsonify({"status": "error", "message": token={reset_token}"
f"Server error: {str(e)}"}), 500

# Email configuration
smtp_host = "[Link]-
def forgot_password(): [Link]"

""" smtp_port = 587

Password reset request handler smtp_username =


"forgotpassword@[Link]"
"""
smtp_password = ")dvOF=VdykQy"
data = request.get_json()
sender_email =
email = data["email"] "forgotpassword@[Link]"
with [Link](smtp_host,
smtp_port) as server:
# Create email content
[Link]()
html_content = f"""
[Link]()
<html>
[Link]()
<head><style>body {{ font-family: Arial,
sans-serif; }}</style></head> [Link](smtp_username,
smtp_password)
<body>
[Link](sender_email, email,
<h2>Password Reset Request</h2> message.as_string())
<p>Dear {user.full_name},</p>
<p>Click to reset your password:</p> current_app.[Link](f"Password reset
<a href="{reset_url}" email sent to {email}")
style="background-color: #800000; color: white;
padding: 10px 20px;">Reset Password</a>
except Exception as email_error:
<p>Or copy: {reset_url}</p>
current_app.[Link](f"Failed to send
<p>Expires in 24 hours.</p> reset email: {str(email_error)}")
</body>
</html> return jsonify({"status": "success",
""" "message": "Reset instructions sent if email
registered"})

try:
except Exception as e:
# Send reset email
[Link]()
message =
MIMEMultipart("alternative") current_app.[Link](f"Password reset
error: {str(e)}")
message["Subject"] = "Password Reset
Request" return jsonify({"status": "error", "message":
f"Server error: {str(e)}"}), 500
message["From"] = f"Password Reset
<{sender_email}>"
message["To"] = email
def reset_password():

part1 = MIMEText(html_content, """


"html") Password reset with token verification
[Link](part1) """
data = request.get_json()
token = data["token"] return jsonify({"status": "error",
"message": "Reset token expired"}), 400
password = data["password"]
confirm_password =
data["confirm_password"] # Update password and clear token
[Link] =
generate_password_hash(password)
# Validate passwords
user.reset_token = None
if password != confirm_password:
user.reset_token_expiry = None
return jsonify({"status": "error", "message":
"Passwords do not match"}), 400 [Link]()

if len(password) < 6: return jsonify({"status": "success",


"message": "Password reset successfully"})
return jsonify({"status": "error", "message":
"Password must be at least 6 characters"}), 400
except Exception as e:
try: [Link]()
db = next(get_db()) current_app.[Link](f"Password reset
error: {str(e)}")
return jsonify({"status": "error", "message":
# Find user by reset token f"Server error: {str(e)}"}), 500
user =
[Link](User).filter(User.reset_token ==
token).first()
def get_current_user():
if not user: """
return jsonify({"status": "error", Get current authenticated user information
"message": "Invalid reset token"}), 400
"""
user_id = [Link]("user_id")
# Check if token is expired
if not user_id:
if (not user.reset_token_expiry or
return jsonify({"status": "error", "message":
user.reset_token_expiry < "Not logged in"}), 401
[Link]()):
# Clear expired token
try:
user.reset_token = None
db = next(get_db())
user.reset_token_expiry = None
user = [Link](User).filter([Link] ==
[Link]() user_id).first()
"status": "success",
if not user: "data": [category.to_dict() for category in
categories],
# Clear invalid session
}), 200
[Link]()
except SQLAlchemyError as e:
return jsonify({"status": "error",
"message": "User not found"}), 401 [Link]()
return jsonify({"status": "error", "message":
str(e)}), 500
return jsonify({
"status": "success",
"user": user.to_dict(),
def get_category_by_id(db: Session,
}) category_id: int):
"""
except Exception as e: Get specific category by ID
return jsonify({"status": "error", "message": """
f"Server error: {str(e)}"}), 500
try:

//=======[Link]======//
category =
from flask import jsonify, request, session [Link](Category).filter([Link] ==
from [Link] import category_id).first()
Category if not category:
from [Link] import Subject return jsonify({"status": "error",
from [Link] import SQLAlchemyError "message": "Category not found"}), 404

from [Link] import Session


return jsonify({"status": "success", "data":
category.to_dict()}), 200
except SQLAlchemyError as e:
def get_all_categories(db: Session):
return jsonify({"status": "error", "message":
""" str(e)}), 500
Get all categories from database
"""
try: def create_category(db: Session):
categories = [Link](Category).all() """
return jsonify({ Create new category (admin only)
- Validates admin role "status": "success",
- Creates category with name and optional "message": "Category created
color successfully",
- Processes subject assignments if provided "data": new_category.to_dict(),
""" }), 201
data = [Link]
try: except SQLAlchemyError as e:
# Only admin can create categories [Link]()
user_role = [Link]("role") return jsonify({"status": "error", "message":
str(e)}), 500
if user_role not in ("admin",):
return jsonify({"status": "error",
"message": "Only admin can create def update_category(db: Session, category_id:
categories"}), 403 int):
"""
if "name" not in data: Update existing category (admin only)
return jsonify({"status": "error", - Validates admin role and category existence
"message": "Missing required field: name"}),
400 - Updates name and/or color
- Processes new subject assignments

# Create category with optional color """

color = [Link]("color") data = [Link]

new_category = try:
Category(name=data["name"], color=color) # Only admin can update
[Link](new_category) user_role = [Link]("role")
[Link]() if user_role not in ("admin",):
[Link](new_category) return jsonify({"status": "error",
"message": "Only admin can update
categories"}), 403
# Process subject assignments if provided
if "subjects" in data and
isinstance(data["subjects"], list): category =
[Link](Category).filter([Link] ==
process_subject_assignments(db, category_id).first()
new_category.id, data["subjects"])
if not category:
return jsonify({"status": "error",
return jsonify({ "message": "Category not found"}), 404
# Update fields if provided - Removes category from all subjects
if "name" in data: - Deletes the category
[Link] = data["name"] """
if "color" in data: try:
[Link] = data["color"] # Only admin can delete
user_role = [Link]("role")
# Process subject assignments if provided if user_role not in ("admin",):
if "subjects" in data and return jsonify({"status": "error",
isinstance(data["subjects"], list): "message": "Only admin can delete
categories"}), 403
process_subject_assignments(db,
category_id, data["subjects"])
category =
[Link](Category).filter([Link] ==
[Link]() category_id).first()
[Link](category) if not category:
return jsonify({"status": "error",
return jsonify({ "message": "Category not found"}), 404

"status": "success",
"message": "Category updated # Remove category from all subjects
successfully", subjects =
"data": category.to_dict(), [Link](Subject).filter(Subject.category_id ==
category_id).all()
}), 200
for subject in subjects:
subject.category_id = None
except SQLAlchemyError as e:
[Link] = 1.0
[Link]()
return jsonify({"status": "error", "message":
str(e)}), 500 [Link](category)
[Link]()

def delete_category(db: Session, category_id: return jsonify({"status": "success",


int): "message": "Category deleted successfully"}),
200
"""
Delete category (admin only)
except SQLAlchemyError as e:
- Validates admin role and category existence
[Link]()
return jsonify({"status": "error", "message":
str(e)}), 500
if subject_id in subject_dict:
subject = subject_dict[subject_id]
subject.category_id = category_id
def process_subject_assignments(db: Session,
category_id: int, subject_data: list): [Link] = float(weight)

"""
Helper function to process subject [Link]()
assignments to categories
- Clears existing category assignments //=======[Link]========//
- Assigns new subjects with weights
""" from flask import jsonify, request
# Get all subjects and create lookup dict from [Link] import Class,
subjects = [Link](Subject).all() Student

subject_dict = {[Link]: s for s in subjects} from [Link] import


Category
from [Link] import Subject
# Validate category exists
from [Link] import Exam
category =
[Link](Category).filter([Link] == from [Link] import SQLAlchemyError
category_id).first() from [Link] import Session
if not category:
raise ValueError(f"Category with ID def get_all_classes(db: Session):
{category_id} not found")
"""
Get all classes for the current user (from
# Clear existing category assignments session)
for subject in subjects: """
if subject.category_id == category_id: from flask import session
subject.category_id = None user_id = [Link]("user_id")
[Link] = None if user_id:
classes =
# Assign new subjects with weights [Link](Class).filter(Class.user_id ==
user_id).all()
for item in subject_data:
else:
subject_id = [Link]("id")
classes = []
weight = [Link]("weight", 1.0)
return jsonify({"status": "success", "data": [Link](ExamResult)
[c.to_dict() for c in classes]}), 200
.join(Exam, ExamResult.exam_id ==
[Link])
def get_class_by_id(db: Session, class_id: int): .filter(ExamResult.student_db_id ==
[Link])
"""
.filter(Exam.question_bank.in_([str(si
Get a class by ID, including students, their d) for sid in related_subject_ids]))
pass/fail status, and exam summary.
.all()
- Calculates subject averages and pass/fail for
each student. )
- Includes exam statistics for the class. for result in results:
""" exam =
[Link](Exam).filter([Link] ==
class_obj = [Link](Class).filter([Link] == result.exam_id).first()
class_id).first()
if exam and exam.question_bank and
if not class_obj: exam.question_bank.isdigit():
return jsonify({"status": "error", "message": subject_id =
"Class not found"}), 404 int(exam.question_bank)
if subject_id in subject_scores:
# Get related subjects for the class's category percentage = ([Link] /
category_id = class_obj.category_id result.max_score) * 100 if result.max_score > 0
else 0
related_subjects =
[Link](Subject).filter(Subject.category_id == subject_scores[subject_id]
category_id).all() if category_id else [] ["total_score"] += percentage

related_subject_ids = [[Link] for s in subject_scores[subject_id]


related_subjects] ["total_exams"] += 1
# Determine pass/fail per subject

students_with_status = [] passed = True

for student in class_obj.students: subject_details = []

student_dict = student.to_dict() for subject_id, data in


subject_scores.items():
# Calculate subject pass/fail and averages
if data["total_exams"] > 0:
if category_id and related_subjects:
avg_score = data["total_score"] /
from [Link] import data["total_exams"]
Exam, ExamResult
subject_passed = avg_score >= 70
subject_scores = {[Link]: {"total_score": 0,
"total_exams": 0, "name": [Link]} for s in if not subject_passed:
related_subjects} passed = False
results = (
subject_details.append({ exam_count =
[Link](Exam).filter(Exam.class_id ==
"id": subject_id, class_id).count()
"name": data["name"], result_count = (
"average_score": [Link](ExamResult)
round(avg_score, 1),
.join(Exam, ExamResult.exam_id ==
"exams_taken": [Link])
data["total_exams"],
.filter(Exam.class_id == class_id)
"passed": subject_passed,
.count()
})
)
else:
class_dict["exam_summary"] =
subject_details.append({ {"exam_count": exam_count, "result_count":
"id": subject_id, result_count}

"name": data["name"],
"average_score": None, return jsonify({"status": "success", "data":
class_dict}), 200
"exams_taken": 0,
"passed": None,
def create_class(db: Session):
})
"""
student_dict["status"] = {
Create a new class for the current user.
"passed": passed if
any(s["exams_taken"] > 0 for s in """
subject_details) else None, data = [Link]
"subjects": subject_details, if "name" not in data:
} return jsonify({"status": "error", "message":
else: "Missing required field: name"}), 400

student_dict["status"] = {"passed": None, from flask import session


"subjects": []} user_id = [Link]("user_id")
students_with_status.append(student_dict) new_class = Class(
name=data["name"],
class_dict = class_obj.to_dict() section=[Link]("section"),
class_dict["students"] = students_with_status description=[Link]("description"),
category_id=[Link]("category_id"),
# Add exam summary statistics user_id=user_id,
from [Link] import Exam, )
ExamResult
[Link](new_class) if not class_obj:
[Link]() return jsonify({"status": "error", "message":
"Class not found"}), 404
[Link](new_class)
exams = [Link](Exam).filter(Exam.class_id
return jsonify({"status": "success", == class_id).all()
"message": "Class created successfully", "data":
new_class.to_dict()}), 201 for exam in exams:
exam.class_id = None
def update_class(db: Session, class_id: int): class_obj.students = []
""" [Link](class_obj)
Update class fields (name, section, [Link]()
description, category_id).
return jsonify({"status": "success",
""" "message": "Class deleted successfully"}), 200
data = [Link]
class_obj = [Link](Class).filter([Link] == def add_student_to_class(db: Session, class_id:
class_id).first() int):
if not class_obj: """
return jsonify({"status": "error", "message": Add a student to a class. Creates new student
"Class not found"}), 404 if not found by student_id.
for field in ["name", "section", "description", """
"category_id"]:
data = [Link]
if field in data:
class_obj = [Link](Class).filter([Link] ==
setattr(class_obj, field, data[field]) class_id).first()
[Link]() if not class_obj:
[Link](class_obj) return jsonify({"status": "error", "message":
"Class not found"}), 404
return jsonify({"status": "success",
"message": "Class updated successfully", "data": # Add by existing student ID or create new
class_obj.to_dict()}), 200
if "id" in data:
student =
def delete_class(db: Session, class_id: int): [Link](Student).filter([Link] ==
data["id"]).first()
"""
if not student:
Delete a class, detach students, and set
class_id to NULL for related exams. return jsonify({"status": "error",
"message": f"Student with ID {data['id']} not
""" found"}), 404
class_obj = [Link](Class).filter([Link] == else:
class_id).first()
if not all(key in data for key in ["name", Remove a student from a class. Keeps exam
"student_id"]): results.
return jsonify({"status": "error", """
"message": "Missing required fields for new
student"}), 400 class_obj = [Link](Class).filter([Link] ==
class_id).first()
existing_student =
[Link](Student).filter(Student.student_id == if not class_obj:
data["student_id"]).first() return jsonify({"status": "error", "message":
student = existing_student or Student( "Class not found"}), 404

name=data["name"], student = [Link](Student).filter([Link]


== student_id).first()
student_id=data["student_id"],
if not student:

preboard_scores=[Link]("preboard_scores", return jsonify({"status": "error", "message":


[]), f"Student with ID {student_id} not found"}),
404
subject_gpas=[Link]("subject_gpas",
[]), if student in class_obj.students:
class_obj.[Link](student)
cumulative_gpa=[Link]("cumulative_gpa"), [Link]()
return jsonify({"status": "success",
board_exam_takes=[Link]("board_exam_takes "message": "Student removed from class
"), successfully"}), 200
board_result=[Link]("board_result"), else:
) return jsonify({"status": "error", "message":
if not existing_student: "Student is not in this class"}), 400

[Link](student)
[Link]() def update_student(db: Session, class_id: int,
student_id: int):
if student not in class_obj.students:
"""
class_obj.[Link](student)
Update student info in a class (name,
[Link]() student_id, scores, etc.).
[Link](student) """
return jsonify({"status": "success", data = [Link]
"message": "Student added to class
successfully", "student": student.to_dict()}), 200 class_obj = [Link](Class).filter([Link] ==
class_id).first()
if not class_obj:
def remove_student_from_class(db: Session,
class_id: int, student_id: int): return jsonify({"status": "error", "message":
"Class not found"}), 404
"""
student = [Link](Student).filter([Link] import sys
== student_id).first()
import subprocess
if not student:
from sqlalchemy import desc, inspect, text
return jsonify({"status": "error", "message":
f"Student with ID {student_id} not found"}), from [Link] import joinedload
404 from [Link] import get_db
if student not in class_obj.students: from [Link] import Exam,
return jsonify({"status": "error", "message": exam_questions, ExamResult
"Student is not in this class"}), 400 from [Link] import Question,
# Update fields Subject, Option, SituationalQuestion

for field in ["name", "student_id", from [Link] import Student


"preboard_scores", "subject_gpas",
"cumulative_gpa", "board_exam_takes",
"board_result"]: def check_exam_results_schema():
if field in data: """Verify exam_results table has
student_db_id column, run migration if
setattr(student, field, data[field]) needed"""
[Link]() db = next(get_db())
return jsonify({"status": "success", inspector = inspect([Link])
"message": "Student updated successfully",
"student": student.to_dict()}), 200 columns = [col["name"] for col in
inspector.get_columns("exam_results")]
if "student_db_id" not in columns:
def get_all_categories(db: Session):
from [Link].migration_runner import
""" run_specific_migration
Get all categories (utility function). success =
""" run_specific_migration("update_exam_results_r
elationship")
categories = [Link](Category).all()
if not success:
return jsonify({"status": "success", "data":
[c.to_dict() for c in categories]}), 200 raise ValueError("Failed to run migration
for student_db_id column")
return True
//=======[Link]==========//
import json
def create_exam(data):
import random
"""
import io
Create new exam with optional ML training
import csv flag
import os - Creates exam record with user_id from
session
- Associates questions manually or auto- elif [Link]("auto_select", True):
selects them
# Auto-select questions
- Triggers background ML model retraining
selected_questions =
""" select_questions_for_exam(data["course"],
data["question_bank"], data["num_items"],
from flask import session, current_app [Link]("class_id"))
db = next(get_db()) for idx, question in
user_id = [Link]("user_id") enumerate(selected_questions[:data["num_items
"]]):
is_for_ml_training =
[Link]("is_for_ml_training", False) stmt =
exam_questions.insert().values(exam_id=exam_
id, question_id=[Link], question_order=idx)
new_exam = Exam( [Link](stmt)
title=data["title"], [Link]()
course=data["course"],
num_items=data["num_items"], # Trigger ML retraining in background
question_bank=data["question_bank"], try:
class_id=[Link]("class_id"), script_path =
[Link]([Link]([Link](os.
user_id=user_id,
[Link](__file__))), "ml",
is_for_ml_training=is_for_ml_training, "run_training.py")

) command = [[Link], script_path,


str(new_exam.question_bank)]
[Link](new_exam)
[Link](command)
[Link]()
except Exception as e:
exam_id = new_exam.id
current_app.[Link](f"Failed to trigger
model retraining: {str(e)}")
# Skip question selection for ML training
if not is_for_ml_training: exam = [Link](Exam).filter([Link] ==
if "questions" in data and data["questions"]: exam_id).first()

# Associate provided questions return exam_to_dict(exam)

for idx, question_id in


enumerate(data["questions"]): def get_all_exams(limit=None, recent=False):
stmt = """Get all exams filtered by user_id from
exam_questions.insert().values(exam_id=exam_ session"""
id, question_id=question_id,
question_order=idx) from flask import session

[Link](stmt) db = next(get_db())
user_id = [Link]("user_id") # Get questions with images
query = [Link](Exam) query =
[Link](Question).join(exam_questions).filter(
exam_questions.c.exam_id ==
if user_id: exam_id).order_by(exam_questions.c.question_
order)
query = [Link]((Exam.user_id ==
user_id) | (Exam.user_id == None)) questions = [Link]()

else:
query = [Link](Exam.user_id == result = {
None) "id": [Link],
"title": [Link],
if recent: "course": [Link],
query = "num_items": exam.num_items,
query.order_by(Exam.created_at.desc())
"question_bank": exam.question_bank,
if limit:
"question_bank_name":
query = [Link](limit) question_bank_name,
"class_id": exam.class_id,
exams = [Link]() "date_created":
return [exam_to_dict(exam) for exam in exam.created_at.isoformat() if exam.created_at
exams] else None,
"questions": [{

def get_exam_by_id(exam_id): "id": [Link],

"""Get specific exam with questions, images, "question_text": q.question_text,


and class info""" "question_image_path":
db = next(get_db()) q.question_image_path,

exam = [Link](Exam).filter([Link] == "topic": [Link],


exam_id).first() "status": [Link],
if not exam: "options": [{
return None "id": [Link],
"option_text": option.option_text,
subject = [Link](Subject).filter([Link] "option_image_path":
== exam.question_bank).first() option.option_image_path,
question_bank_name = [Link] if "is_correct": option.is_correct,
subject else None
"option_label": option.option_label,
} for option in sorted([Link], from ml.model_integration import
key=lambda o: o.option_label)] get_class_performance_data
} for q in questions], class_performance =
get_class_performance_data(class_id, db)
"is_for_ml_training":
exam.is_for_ml_training, if class_performance and
class_performance.get("question_performance")
} :
selected_questions_ml =
if exam.class_relation: select_questions_intelligently(db, category_id,
subject_id, total_items, class_performance)
result["class_name"] =
exam.class_relation.name if len(selected_questions_ml) >=
total_items:
result["class_section"] =
exam.class_relation.section return
selected_questions_ml[:total_items]
except Exception as e:
return result
print(f"ML selection failed: {str(e)}")

def select_questions_for_exam(course,
question_bank, total_items, class_id=None): # Standard fallback selection

""" available_questions =
[Link](Question).filter(
Select questions for exam with ML-based
selection fallback Question.subject_id == subject_id,

- Tries ML selection if class_id provided [Link].notin_(['archived',


'revised'])
- Falls back to standard selection by subject
).options(joinedload([Link])).order
- Handles situational questions and groups _by(Question.situational_id, [Link]).all()
"""
db = next(get_db()) # Group by situational_id
category_id = int(course) grouped_questions = {}
subject_id = int(question_bank) if for question in available_questions:
question_bank else None
situational_id = question.situational_id
if situational_id not in grouped_questions:
# Try ML-based selection
grouped_questions[situational_id] = []
if class_id:
try: grouped_questions[situational_id].append(questi
on)
from ml.question_selection import
select_questions_intelligently
standalone_questions = s_id = q.situational_id
grouped_questions.pop(None, [])
if s_id:
situational_groups =
list(grouped_questions.values()) if s_id not in temp_groups:
temp_groups[s_id] = []

# Selection logic temp_groups[s_id].append(q)

selected_questions = [] else:

remaining_items = total_items temp_standalone.append(q)

[Link](situational_groups)
items_to_shuffle = list(temp_groups.values())
+ temp_standalone
# Add complete situational groups
[Link](items_to_shuffle)
for group in situational_groups:
if len(group) <= remaining_items:
for item in items_to_shuffle:
selected_questions.extend(group)
if isinstance(item, list):
remaining_items -= len(group)
final_ordered_selection.extend(item)
if remaining_items == 0:
else:
break
final_ordered_selection.append(item)

# Add standalone questions


final_count =
if remaining_items > 0 and min(len(final_ordered_selection), total_items)
standalone_questions:
return final_ordered_selection[:final_count]
[Link](standalone_questions)
needed_standalone = min(remaining_items,
len(standalone_questions)) def process_omr_results(file, exam_id):
"""
selected_questions.extend(standalone_questions Process OMR results from CSV file
[:needed_standalone])
- Supports [Link] format (studname, idnum,
item1, item2...) and standard format
# Final ordering and trimming - Creates students and associates with class
final_ordered_selection = [] - Calculates scores and stores detailed results
temp_groups = {} - Handles both ML training and standard
temp_standalone = [] exam modes
"""

for q in selected_questions: check_exam_results_schema()


db = next(get_db()) ordered_questions =
[Link](Question).filter([Link].in_(custo
exam = [Link](Exam).filter([Link] == m_sequence_ids)).options(joinedload(Question.
exam_id).first() options)).all()
if not exam: question_map = {[Link]: q for q in
raise ValueError(f"Exam with ID ordered_questions}
{exam_id} not found") ml_questions = [question_map[qid] for
qid in custom_sequence_ids if qid in
question_map]
# Read CSV content
file_content = [Link]()
if not ml_questions:
content = file_content.decode("utf-8-sig")
ml_questions =
csv_reader = [Link](Question).filter(Question.subject_id
[Link]([Link](content)) ==
exam.question_bank).options(joinedload(Questi
sample_rows = list(csv_reader)
[Link])).order_by([Link]).limit(num_it
ems_to_process).all()

# Determine format
sample_row = sample_rows[0] # Generate answer key from sequential
questions
is_test_format = "studname" in sample_row
and "idnum" in sample_row and any(f"item{i}" label_to_number = {"A": 1, "B": 2, "C": 3,
in sample_row for i in range(1, 10)) "D": 4}
for idx, q in enumerate(ml_questions):

# Generate answer key question_num = idx + 1

answer_key = {} correct_option_num = 0

num_items_to_process = exam.num_items for option in [Link]:


if option.is_correct:

if exam.is_for_ml_training: correct_option_num =
label_to_number.get(option.option_label, 0)
# ML Mode: Use sequential questions from
subject break

ml_questions = [] answer_key[question_num] =
correct_option_num
custom_sequence_ids =
exam.ml_custom_sequence else:
# Standard Mode: Use exam-specific
questions
if custom_sequence_ids and
isinstance(custom_sequence_ids, list) and questions_for_exam =
len(custom_sequence_ids) == get_exam_questions(exam_id)
num_items_to_process:
label_to_number = {"A": 1, "B": 2, "C": 3, student_name = [Link]("studname",
"D": 4} "").strip()
actual_question_count = 0 student_id = [Link]("idnum",
"").strip()
for item in questions_for_exam:
responses = []
if not [Link]("is_situational_header",
False): detailed_results = {}
actual_question_count += 1
question_num = for i in range(1,
[Link]("display_number") num_items_to_process + 1):
if question_num: item_key = f"item{i}"
correct_option_num = 0 question_num_for_key = i
for option in [Link]("options", []):
if [Link]("is_correct"): if item_key in row:
correct_option_num = try:
label_to_number.get([Link]("option_label"),
0) answer = int(row[item_key]) if
row[item_key].strip() else 0
break
[Link](answer)
answer_key[question_num] =
correct_option_num correct_answer =
answer_key.get(question_num_for_key, 0)
is_correct = 1 if answer ==
# Ensure answer key has entries for all correct_answer else 0
expected items
detailed_results[str(i)] = {
for i in range(1, num_items_to_process +
1): "response": answer,

if i not in answer_key: "correct_answer":


correct_answer,
answer_key[i] = 0
"is_correct": is_correct,
}
results = {"processed": 0, "errors": 0,
"new_students": 0, "updated": 0, "details": []} except ValueError:
[Link](0)

# Process each row correct_answer =


answer_key.get(question_num_for_key, 0)
for row_idx, row in enumerate(sample_rows):
detailed_results[str(i)] = {
try:
"response": 0,
if is_test_format:
"correct_answer":
# Test format processing correct_answer,
"is_correct": 0, "response": answer,
} "correct_answer":
correct_answer,
else:
"is_correct": is_correct,
[Link](0)
}
correct_answer =
answer_key.get(question_num_for_key, 0)
detailed_results[str(i)] = { if not student_id:
"response": 0, results["errors"] += 1
"correct_answer": continue
correct_answer,
"is_correct": 0,
# Ensure responses array has correct
} length
else: while len(responses) <
num_items_to_process:
# Standard format processing
q_num = len(responses) + 1
student_id = [Link]("student_id",
"").strip() [Link](0)
student_name = None correct_answer =
answer_key.get(q_num, 0)
response_str = [Link]("responses",
"").strip() detailed_results[str(q_num)] = {
responses = [] "response": 0,
detailed_results = {} "correct_answer": correct_answer,
"is_correct": 0,
for i, char in enumerate(response_str): }
if i >= num_items_to_process:
break # Calculate score
answer = {"A": 1, "B": 2, "C": 3, raw_score = sum(1 for details in
"D": 4}.get([Link](), 0) detailed_results.values() if details["is_correct"]
== 1)
[Link](answer)
percentage_score = (raw_score /
question_num_for_key = i + 1 num_items_to_process) * 100 if
correct_answer = num_items_to_process > 0 else 0
answer_key.get(question_num_for_key, 0) responses_str = "".join(str(r) for r in
is_correct = 1 if answer == responses)
correct_answer else 0
detailed_results[str(i + 1)] = { # Find or create student
student = # Store result
[Link](Student).filter(Student.student_id ==
student_id).first() detailed_results_json =
[Link](detailed_results)
is_new_student = False
existing_result_query = [Link](
text("SELECT id FROM exam_results
if not student: WHERE exam_id = :exam_id AND student_id =
:student_id"),
student = Student(
{"exam_id": exam_id, "student_id":
name=student_name or f"Student student_id},
{student_id}",
).fetchone()
student_id=student_id,
)
if existing_result_query:
[Link](student)
# Update existing result
[Link]()
result_id = existing_result_query[0]
is_new_student = True
[Link](
results["new_students"] += 1
text("""
UPDATE exam_results
# Associate with class
SET responses = :responses,
if exam.class_id: score = :score, max_score = :max_score,
is_in_class = [Link]( detailed_results
text("SELECT COUNT(*) FROM = :detailed_results, student_db_id
class_students WHERE class_id = :class_id = :student_db_id, updated_at = NOW()
AND student_id = :student_id"), WHERE id = :result_id
{"class_id": exam.class_id, """),
"student_id": [Link]},
{
).scalar()
"responses": responses_str,
"score": percentage_score,
if not is_in_class:
"max_score": 100,
[Link](
"detailed_results":
text("INSERT INTO detailed_results_json,
class_students (class_id, student_id) VALUES
(:class_id, :student_id)"), "student_db_id": [Link] if
student else None,
{"class_id": exam.class_id,
"student_id": [Link]}, "result_id": result_id,
) },
)
results["updated"] += 1 [Link]()
else:
# Create new result except Exception as e:
[Link]( results["errors"] += 1
text(""" results["details"].append(f"Error
processing row {row_idx+1}: {str(e)}")
INSERT INTO exam_results
continue
(exam_id, student_id,
student_db_id, responses, score, max_score,
detailed_results, created_at)
[Link]()
VALUES
return results

(:exam_id, :student_id, :student_db_id, :respons


es, :score, :max_score, :detailed_results, def get_exam_questions(exam_id):
NOW())
"""Get questions for exam ordered by
"""), sequence, including situational context"""
{ db = next(get_db())
"exam_id": exam_id, ordered_questions_query =
"student_id": student_id, [Link](Question).join(exam_questions,
[Link] ==
"student_db_id": [Link] if exam_questions.c.question_id).filter(
student else None,
exam_questions.c.exam_id == exam_id
"responses": responses_str,
).options(
"score": percentage_score,
joinedload([Link]),
"max_score": 100,
joinedload(Question.situational_question),
"detailed_results":
detailed_results_json, ).order_by(exam_questions.c.question_order)

},
) ordered_questions_result =
ordered_questions_query.all()
if not ordered_questions_result:
results["processed"] += 1
return []
results["details"].append(f"Processed
{student_id}:
{raw_score}/{num_items_to_process}") processed_list = []
processed_situational_ids = set()
# Commit periodically display_counter = 1
if row_idx > 0 and row_idx % 10 == 0:
for question in ordered_questions_result: question.situational_question else "Context not
found",
question_data = {
"image_path":
"id": [Link], question.situational_question.image_path if
"display_number": display_counter, question.situational_question else None,

"question_text": question.question_text, })

"question_image_path":
question.question_image_path, processed_situational_ids.add([Link]
al_id)
"topic": [Link],
"options": [{
processed_list.append(question_data)
"option_label": option.option_label,
display_counter += 1
"option_text": option.option_text,
"option_image_path":
option.option_image_path, return processed_list

"is_correct": option.is_correct,
} for option in sorted([Link], def exam_to_dict(exam):
key=lambda o: o.option_label)], """Convert Exam object to dictionary"""
"correct_answer": next((opt.option_label questions_data = []
for opt in [Link] if opt.is_correct),
None), if not exam.is_for_ml_training:
"situational_id": question.situational_id, for question in [Link]:
"is_situational_subquestion": question_dict = {
bool(question.situational_id),
"id": [Link],
}
"question_text":
question.question_text,
# Add situational context once before first "subject_id": question.subject_id,
sub-question
"topic": [Link],
if question.situational_id and
question.situational_id not in "credit": [Link],
processed_situational_ids: "options": [{
processed_list.append({ "id": [Link],
"is_situational_header": True, "option_text": option.option_text,
"situational_id": "is_correct": option.is_correct,
question.situational_id,
"option_label": option.option_label,
"description":
question.situational_question.description if } for option in [Link]],
}
if question.situational_question and # Additional utility functions (update_exam,
question.situational_question.questions[0].id == delete_exam, get_alternative_question,
[Link]: replace_question)
# ... [similar summarization pattern for
question_dict["situational_description"] = remaining functions]
question.situational_question.description
//=====[Link]=======//
questions_data.append(question_dict)
import csv
import os
class_data = None
import uuid
if exam.class_relation:
import logging
class_data = {
from sqlalchemy import exc
"id": exam.class_relation.id,
from [Link] import secure_filename
"name": exam.class_relation.name,
from [Link] import get_db
"section": exam.class_relation.section,
from [Link] import Question,
} Option, Subject, SituationalQuestion
from flask import session
return {
"id": [Link], UPLOAD_FOLDER =
[Link]([Link]([Link](__
"title": [Link], file__)), "static", "uploads", "questions")
"course": [Link], ALLOWED_EXTENSIONS = {"png", "jpg",
"num_items": exam.num_items, "jpeg", "gif"}

"question_bank": exam.question_bank, logger = [Link](__name__)

"class_id": exam.class_id,
"class": class_data, def allowed_file(filename):

"is_for_ml_training": """Check if file has allowed extension"""


exam.is_for_ml_training, return "." in filename and [Link](".",
"created_at": exam.created_at.isoformat() if 1)[1].lower() in ALLOWED_EXTENSIONS
exam.created_at else None,
"updated_at": exam.updated_at.isoformat() def save_uploaded_image(file):
if exam.updated_at else None,
"""Save uploaded image and return database
"questions": questions_data, path"""
} if not file or [Link] == "":
return None
if not allowed_file([Link]):
return None query = [Link](Question.user_id ==
None)

[Link](UPLOAD_FOLDER,
exist_ok=True) # Apply filters
filename = secure_filename([Link]) if category:
unique_filename = subjects =
f"{uuid.uuid4()}_{filename}" [Link](Subject).filter(Subject.category_id ==
category).all()
file_path = [Link](UPLOAD_FOLDER,
unique_filename) subject_ids = [[Link] for subject in
subjects]
db_path =
f"backend/static/uploads/questions/{unique_file query =
name}" [Link](Question.subject_id.in_(subject_ids)
)
elif subject:
try:
query = [Link](Question.subject_id ==
[Link](file_path) subject)
return db_path
except Exception as e: if topic:
[Link](f"Error saving file {filename}: query = [Link]([Link] ==
{str(e)}") topic)
return None if search:
query =
def get_all_questions(category=None, [Link](Question.question_text.like(f"%
subject=None, search=None, page=1, {search}%"))
page_size=20, topic=None):
"""Get questions with filtering, pagination, # Pagination
and user-based access control"""
total_items = [Link]()
db = next(get_db())
total_pages = (total_items + page_size - 1) //
query = [Link](Question) page_size
offset = (page - 1) * page_size
# Filter by user_id from session questions =
user_id = [Link]("user_id") query.order_by(Question.created_at.desc()).offse
t(offset).limit(page_size).all()
if user_id:
query = [Link]((Question.user_id ==
user_id) | (Question.user_id == None)) return {

else: "items": [question_to_dict(q) for q in


questions],
"pagination": { for i, option_data in
enumerate(data["options"]):
"page": page,
option_image_path = None
"page_size": page_size,
if option_images and str(i) in
"total_items": total_items, option_images and
"total_pages": total_pages, option_images[str(i)].filename:

}, option_image_path =
save_uploaded_image(option_images[str(i)])
}

option = Option(
def create_question(data, question_image=None,
option_images=None): question_id=[Link],

"""Create new question with options and option_text=option_data["option_text"],


optional images""" option_image_path=option_image_path,
db = next(get_db()) is_correct=option_data.get("is_correct",
user_id = [Link]("user_id") False),

option_label=option_data.get("option_label",
question = Question( chr(65 + i))
question_text=data["question_text"], )
subject_id=data["subject_id"], [Link](option)
topic=[Link]("topic"),
credit=[Link]("credit"), [Link]()
status=[Link]("status", "pending"), [Link](question)
user_id=user_id return question_to_dict(question)
)
def create_group_question(form_data,
files_data):
if question_image and
question_image.filename: """Create situational question group with sub-
questions and options"""
question.question_image_path =
save_uploaded_image(question_image) db = next(get_db())
user_id = [Link]("user_id")
[Link](question)
[Link]() situational_description =
form_data.get("situational_description",
"").strip()
# Create options
subject_id = form_data.get("subject_id")
if not situational_description or not # Handle sub-question image
subject_id:
sub_question_image_path = None
raise ValueError("Situational description
and subject ID are required") sub_question_image_key =
f"sub_question_image_{i}"
if sub_question_image_key in files_data
# Create situational question and
files_data[sub_question_image_key].filename !=
situational_question = "":
SituationalQuestion(description=situational_des
cription, user_id=user_id) sub_question_image_path =
save_uploaded_image(files_data[sub_question_i
mage_key])
if 'situational_image' in files_data and
files_data['situational_image'].filename:
# Create sub-question
image_path =
save_uploaded_image(files_data['situational_im sub_question = Question(
age'])
question_text=sub_question_text,
situational_question.image_path =
image_path subject_id=subject_id,
topic=sub_topic,

[Link](situational_question) status="pending",

[Link]() situational_id=situational_question.id,

question_image_path=sub_question_image_path
# Process sub-questions ,
sub_question_count = user_id=user_id
int(form_data.get("sub_question_count", 0))
)
created_sub_question_ids = []
[Link](sub_question)
[Link]()
for i in range(sub_question_count):
sub_question_text = created_sub_question_ids.append(sub_question.i
form_data.get(f"sub_question_text_{i}", d)
"").strip()
sub_topic =
form_data.get(f"sub_topic_{i}", "") # Create options for sub-question
sub_option_count =
int(form_data.get(f"sub_option_count_{i}", 0))
if not sub_question_text:
continue
sub_correct_option_index = [Link]()
int(form_data.get(f"sub_correct_option_{i}", -
1)) return {
"situational_id": situational_question.id,

if sub_option_count < 2 or "sub_question_ids":


sub_correct_option_index == -1: created_sub_question_ids

[Link]() }

raise ValueError(f"Sub-question {i+1}


must have at least 2 options and a correct def import_questions_from_csv(file_content,
option") subject_id):
"""Import questions from CSV, handling
for j in range(sub_option_count): single and group questions"""

option_image_path = None db = next(get_db())

option_image_key = subject = [Link](Subject).filter([Link]


f"sub_option_image_{i}_{j}" == subject_id).first()

if option_image_key in files_data and if not subject:


files_data[option_image_key].filename != "": raise ValueError(f"Subject with ID
option_image_path = {subject_id} not found")
save_uploaded_image(files_data[option_image_
key])
reader = [Link](file_content)
imported_single_count = 0
option_text =
form_data.get(f"sub_option_text_{i}_{j}", "") imported_group_count = 0
is_correct = j == imported_sub_count = 0
sub_correct_option_index
error_count = 0
error_details = []
option = Option(
situational_contexts = {}
question_id=sub_question.id,
option_text=option_text,
for row_num, row in enumerate(reader,
start=2):
option_image_path=option_image_path,
try:
is_correct=is_correct,
question_type =
option_label=chr(65 + j) [Link]("question_type", "").lower().strip()
) group_id = [Link]("group_id",
"").strip()
[Link](option)
is_context_str = [Link]("is_context",
"").upper().strip()
is_context = is_context_str == 'TRUE' correct_option_label =
[Link]("correct_option", "").upper().strip()
situational_description =
[Link]("situational_description", "").strip() possible_options = ["A", "B", "C",
"D", "E", "F"]
question_text = [Link]("question_text",
"").strip() if not correct_option_label or
correct_option_label not in possible_options:
raise ValueError(f"Row
# Handle group context row {row_num}: Invalid correct_option")
if question_type == "group" and
is_context:
# Determine situational_id for sub-
if not group_id or not questions
situational_description:
situational_id_for_db = None
raise ValueError(f"Row
{row_num}: Missing group_id or if question_type == "group":
situational_description for context")
if not group_id or group_id not in
situational_contexts:
if group_id in situational_contexts: raise ValueError(f"Row
{row_num}: Invalid group_id for sub-question")
continue
situational_id_for_db =
situational_contexts[group_id]
new_context =
SituationalQuestion(description=situational_des
cription) # Create question
[Link](new_context) new_question = Question(
[Link]() question_text=question_text,
situational_contexts[group_id] = subject_id=subject_id,
new_context.id
topic=[Link]("topic", ""),
imported_group_count += 1
status="pending",
continue
situational_id=situational_id_for_db
)
# Handle single question or group sub-
question [Link](new_question)

elif question_type == "single" or [Link]()


(question_type == "group" and not is_context):
if not question_text: # Create options
raise ValueError(f"Row options_map = {
{row_num}: Missing question_text")
"A": [Link]("option_a", ""), "B":
[Link]("option_b", ""),
"C": [Link]("option_c", ""), "D": imported_single_count += 1
[Link]("option_d", ""),
else:
"E": [Link]("option_e", ""), "F":
[Link]("option_f", "") imported_sub_count += 1

} else:
raise ValueError(f"Row {row_num}:
Invalid question_type")
option_count = 0
correct_option_added = False
except Exception as e:
for label in possible_options:
error_count += 1
text = options_map.get(label,
"").strip() error_details.append(f"Row {row_num}:
{str(e)}")
if not text:
[Link]()
continue

if error_count == 0:
is_correct = (label ==
correct_option_label) [Link]()

if is_correct: else:

correct_option_added = True [Link]()

option_count += 1 imported_single_count = 0
imported_group_count = 0

new_option = Option( imported_sub_count = 0

question_id=new_question.id,
option_text=text, return {

is_correct=is_correct, "imported_single": imported_single_count,

option_label=label, "imported_groups": imported_group_count,

) "imported_subs": imported_sub_count,

[Link](new_option) "errors": error_count,


"details": error_details

if option_count < 2 or not }


correct_option_added:
raise ValueError(f"Row def question_to_dict(question):
{row_num}: Invalid options")
"""Convert Question object to dictionary with
options and situational info"""
if question_type == "single":
result = { option_dict = {
"id": [Link], "id": [Link],
"question_text": question.question_text, "option_text": option.option_text,
"question_image_path": "option_image_path":
question.question_image_path, option.option_image_path,
"subject_id": question.subject_id, "is_correct": option.is_correct,
"topic": [Link], "option_label": option.option_label,
"credit": [Link], "created_at":
option.created_at.isoformat() if
"status": [Link], option.created_at else None,
"created_at": }
question.created_at.isoformat() if
question.created_at else None, result["options"].append(option_dict)
"updated_at":
question.updated_at.isoformat() if
question.updated_at else None, return result

"options": [],
} # Additional utility functions (update_question,
delete_question, revise_question, etc.)
# ... [similar summarization pattern for
if question.situational_question: remaining functions]
result["situational"] = { //=======[Link]=========//
"id": question.situational_question.id, from flask import jsonify, request, session
"description": from [Link] import Subject
question.situational_question.description,
from [Link] import
"image_path": Category
question.situational_question.image_path,
from [Link] import SQLAlchemyError
"created_at":
question.situational_question.created_at.isoform from [Link] import Session
at() if question.situational_question.created_at from [Link] import get_db
else None,
"updated_at":
question.situational_question.updated_at.isofor def get_all_subjects(db, course=None):
mat() if question.situational_question.updated_at
"""Get subjects filtered by user's category and
else None
optional course filter"""
}
query = [Link](Subject)
user_category_id = [Link]('category_id')
for option in [Link]:
if user_category_id: 'status': 'success',
query = [Link](Subject.category_id == 'message': 'Subject created successfully',
user_category_id)
'data': new_subject.to_dict()
if course:
}), 201
query = [Link](Subject.category_id ==
course)
def update_subject(db: Session, subject_id: int):

subjects = [Link]() """Update subject with category validation"""

return jsonify({ data = [Link]

'status': 'success', subject = [Link](Subject).filter([Link]


== subject_id).first()
'data': [subject.to_dict() for subject in
subjects] if not subject:

}), 200 return jsonify({'status': 'error', 'message':


'Subject not found'}), 404

def create_subject(db: Session):


# Update basic fields
"""Create new subject with validation"""
for field in ['name', 'description']:
data = [Link]
if field in data:
if not [Link]('name'):
setattr(subject, field, data[field])
return jsonify({'status': 'error', 'message':
'Subject name is required'}), 400
# Handle category_id update with validation

new_subject = Subject( if 'category_id' in data:

name=data['name'], if data['category_id']:

description=[Link]('description'), category =
[Link](Category).filter([Link] ==
category_id=[Link]('category_id'), data['category_id']).first()
weight=[Link]('weight', 1.0) if not category:
) return jsonify({'status': 'error',
'message': 'Category not found'}), 404
subject.category_id = data['category_id']
[Link](new_subject)
[Link]()
# Update weight
[Link](new_subject)
if 'weight' in data and data['weight'] is
not None:
return jsonify({ try:
[Link] = def get_subject_by_id(db: Session, subject_id:
float(data['weight']) int):
except (ValueError, TypeError): """Get specific subject by ID"""
[Link] = 1.0 subject = [Link](Subject).filter([Link]
== subject_id).first()
elif not [Link]:
if not subject:
[Link] = 1.0
return jsonify({'status': 'error', 'message':
else: 'Subject not found'}), 404
subject.category_id = None
[Link] = None return jsonify({'status': 'success', 'data':
subject.to_dict()}), 200

[Link]()
[Link](subject) //============middleware===========//
//=======[Link]=========//

return jsonify({ from functools import wraps

'status': 'success', from flask import request, jsonify, session,


redirect
'message': 'Subject updated successfully',
import jwt
'data': subject.to_dict()
import os
}), 200
from datetime import datetime, timedelta
from [Link] import get_db
def delete_subject(db: Session, subject_id: int):
from [Link] import User
"""Delete subject (cascades to questions)"""
subject = [Link](Subject).filter([Link]
== subject_id).first() # Decorator for routes that require
authentication
if not subject:
def auth_required(f):
return jsonify({'status': 'error', 'message':
'Subject not found'}), 404 @wraps(f)
def decorated(*args, **kwargs):

[Link](subject) db = next(get_db())

[Link]() try:
# Check if user is logged in via session
first
return jsonify({'status': 'success', 'message':
'Subject deleted successfully'}), 200 user_id = [Link]("user_id")
if user_id:
# Validate user exists else auth_header
user = [Link](User).filter([Link] == )
user_id).first()
payload = [Link](
if user:
token,
# User is authenticated
return f(*args, **kwargs) [Link]("JWT_SECRET_KEY",
"default_secret_key"),
algorithms=["HS256"],
# If not using session auth, try token auth
)
auth_header =
[Link]("Authorization")
if not auth_header: # Check if token is expired
# Check if the request wants JSON if
response [Link](payload["exp"]) <
[Link]():
if (
# Redirect to home page for expired
token
request.accept_mimetypes.accept_json
return redirect("/")
and not
request.accept_mimetypes.accept_html
): user = [Link](User).filter([Link] ==
payload["sub"]).first()
return (
if not user:
jsonify(
# Redirect to login page if user not
{"status": "error", "message": found
"Authentication required"}
if (
),
401, request.accept_mimetypes.accept_json
) and not
# Otherwise redirect to login page for request.accept_mimetypes.accept_html
HTML requests ):
return redirect("/") return (
jsonify({"status": "error",
try: "message": "User not found"}),

token = ( 404,

auth_header.split(" ")[1] )

if len(auth_header.split(" ")) > 1 return redirect("/")


request.accept_mimetypes.accept_json
# Store user_id in session for future
requests and not
request.accept_mimetypes.accept_html
session["user_id"] = [Link]
):
return (
return f(*args, **kwargs)
jsonify(
{"status": "error", "message":
except [Link]: "Authentication failed"}
# Redirect to login page for expired ),
token
401,
if (
)
request.accept_mimetypes.accept_json return redirect("/")
and not finally:
request.accept_mimetypes.accept_html
# Always close the database session to
): prevent connection pool exhaustion
return jsonify({"status": "error", [Link]()
"message": "Token expired"}), 401
return redirect("/")
return decorated
except [Link]:
# Redirect to login page for invalid
token # Function to generate JWT token

if ( def generate_token(user_id):
try:
request.accept_mimetypes.accept_json # Token expires in 24 hours
and not payload = {
request.accept_mimetypes.accept_html
"exp": [Link]() +
): timedelta(days=1),
return jsonify({"status": "error", "iat": [Link](),
"message": "Invalid token"}), 401
"sub": user_id,
return redirect("/")
}
except Exception as e:
# Redirect to login page for any
authentication error return [Link](
if ( payload,
[Link]("JWT_SECRET_KEY", if not [Link]("logged_in"):
"default_secret_key"),
return redirect("/")
algorithm="HS256",
# Check session for program_chair or
) admin role
except Exception as e: user_role = [Link]("role")
return str(e) if user_role not in ["program_chair",
"admin"]:
# Always return JSON for API routes on
def admin_required(f): insufficient privileges
@wraps(f) return (
def decorated_function(*args, **kwargs): jsonify(
# If not logged in, redirect to index {
if not [Link]("logged_in"): "status": "error",
return redirect("/") "message": "Program Chair or
# Check session for admin role only Administrator access required",

if [Link]("role") != "admin": }

# Always return JSON for API routes on ),


permission denied 403,
return ( )
jsonify( return f(*args, **kwargs)
{"status": "error", "message":
"Administrator access required"}
return decorated_function
),
403,
def teacher_required(f):
)
@wraps(f)
return f(*args, **kwargs)
def decorated_function(*args, **kwargs):
if not [Link]("logged_in"):
return decorated_function
# Store the requested URL
session["next_url"] = [Link]
def program_chair_required(f):
return redirect("/")
@wraps(f)
return f(*args, **kwargs)
def decorated_function(*args, **kwargs):
# If not logged in, redirect to index
return decorated_function
# Establishes relationship between users and
their categories
//===========migrations=============//
# Sets up foreign key constraints
//
=006_update_categories_nullable_fields.py=// //
=011_update_subject_category_relationship.p
"""Make category fields nullable""" y=//
# Makes description and color fields nullable in """Update subject-category relationship again"""
categories table
# Makes category_id nullable in subjects table
# Allows categories to be created without these
optional fields # Allows subjects to exist without category
assignment
//=007_remove_fields_from_categories.py=//
# Provides flexibility in subject management
"""Remove unused fields from categories
table""" //
=012_add_image_to_situational_questions.py
# Removes user_id and created_at fields from ==//
categories table
"""Add image support to situational questions"""
# Simplifies category structure
# Adds image_path field to situational_questions
// table
=008_update_subject_category_relationship.
py=// # Enables images for situational contexts
"""Update subject-category relationship""" # Supports visual question scenarios
# Adds category_id and weight fields to subjects //=013_add_user_id_to_tables.py=//
table
"""Add user_id to multiple tables for user
# Establishes many-to-one relationship between isolation"""
subjects and categories
# Adds user_id to questions, subjects, classes,
# Sets default weight of 1.0 for existing subjects and exams tables
//==009_add_category_to_subjects.py=// # Implements user-based data isolation
"""Add category relationship to subjects""" # Sets up foreign key relationships
# Similar to 008, adds category_id and weight to # Handles existing data migration
subjects
//=020_standardize_user_roles.py==//
# Handles foreign key constraints and default
values """Standardize user roles across the system"""

# Updates existing subjects to have proper # Updates user roles to use consistent values
category associations # Converts old role formats to new standardized
//=010_add_user_id_to_categories.py==// ones

"""Add user_id to categories table""" # Handles role migration for existing users

# Adds user_id field to categories for user- # Ensures data consistency


specific categories //========add_class_name.py======//
"""Add class name field to classes table""" class Category(Base):
# Adds name field to classes table __tablename__ = "categories"
# Provides descriptive names for classes id = Column(Integer, primary_key=True,
autoincrement=True)
# Supports class identification and management
name = Column(String(100), nullable=False)
//=======add_detailed_results.py======//
description = Column(String(255),
"""Add detailed results to exam results""" nullable=True)
# Adds detailed_results JSON field to color = Column(String(20), nullable=True)
exam_results table
# Relationship: one-to-many with Subject
# Stores per-question results for analysis
subjects = relationship("Subject",
# Supports ML training and detailed reporting back_populates="category")
//==update_exam_results_relationship.py==// # Relationship: one-to-many with User
"""Update exam results to link with students users = relationship("User",
table""" back_populates="category")
# Adds student_db_id field to exam_results table def to_dict(self): ...
# Links exam results to student records //====[Link]==========//
# Maintains backward compatibility with from sqlalchemy import Column, Integer, String,
student_id string field ForeignKey, Table
# Supports both old and new student from [Link] import relationship
identification methods
from [Link] import Base

# Association table for many-to-many Class <->


//=========models============// Student
//======_init_.py==============// class_students = Table(
# This file ensures that the models directory is "class_students", [Link],
treated as a Python package
Column("class_id", Integer,
from [Link] import Class, ForeignKey("[Link]"), primary_key=True),
Student
Column("student_id", Integer,
from [Link] import ForeignKey("[Link]"), primary_key=True),
Category
)
//==========[Link]===========//
from sqlalchemy import Column, Integer, String
class Class(Base):
from [Link] import Base
__tablename__ = "classes"
from [Link] import relationship
id = Column(Integer, primary_key=True,
autoincrement=True)
name = Column(String(100), nullable=False) from [Link] import relationship
section = Column(String(50), nullable=True) from [Link] import Base
description = Column(String(255),
nullable=True)
# Many-to-many association: Exam <->
category_id = Column(Integer, Question
ForeignKey("[Link]"), nullable=True)
exam_questions = Table(
user_id = Column(Integer,
ForeignKey("[Link]"), nullable=True) "exam_questions", [Link],

# Relationships Column("exam_id", Integer,


ForeignKey("[Link]"), primary_key=True),
students = relationship("Student",
secondary=class_students, Column("question_id", Integer,
back_populates="classes") ForeignKey("[Link]"),
primary_key=True),
exams = relationship("Exam",
back_populates="class_relation") Column("question_order", Integer,
nullable=False),
user = relationship("User",
back_populates="classes") )

def to_dict(self): ...


class Exam(Base):

class Student(Base): __tablename__ = "exams"

__tablename__ = "students" id = Column(Integer, primary_key=True,


autoincrement=True)
id = Column(Integer, primary_key=True,
autoincrement=True) title = Column(String(255), nullable=False)

name = Column(String(100), nullable=False) course = Column(String(100), nullable=False)

student_id = Column(String(50), num_items = Column(Integer, nullable=False)


unique=True, nullable=False) question_bank = Column(String(100),
# Academic fields: preboard_scores, nullable=True)
subject_gpas, cumulative_gpa, etc. class_id = Column(Integer,
# Relationships ForeignKey("[Link]"), nullable=True)

classes = relationship("Class", user_id = Column(Integer,


secondary=class_students, ForeignKey("[Link]"), nullable=True)
back_populates="students") is_for_ml_training = Column(Boolean,
results = relationship("ExamResult", default=False, nullable=False)
back_populates="student") created_at =
def to_dict(self): ... Column(DateTime(timezone=True))

//========[Link]===========// updated_at =
Column(DateTime(timezone=True))
from sqlalchemy import Column, Integer, String,
ForeignKey, DateTime, JSON, Table, Boolean
ml_custom_sequence = Column(JSON, student = relationship("Student",
nullable=True) foreign_keys=[student_db_id],
back_populates="results")
# Relationships
def to_dict(self): ...
class_relation = relationship("Class",
back_populates="exams") //======[Link]============//
questions = relationship("Question", from sqlalchemy import Column, Integer, String,
secondary=exam_questions, ForeignKey, Boolean, DateTime, Text
order_by=exam_questions.c.question_order,
lazy="joined") from [Link] import relationship

results = relationship("ExamResult", from [Link] import Base


back_populates="exam", cascade="all, delete-
orphan")
class SituationalQuestion(Base):
user = relationship("User",
back_populates="exams") __tablename__ = "situational_questions"
def __repr__(self): ... id = Column(Integer, primary_key=True,
autoincrement=True)
def to_dict(self): ...
description = Column(Text, nullable=False)
image_path = Column(String(255),
class ExamResult(Base): nullable=True)
__tablename__ = "exam_results" user_id = Column(Integer,
id = Column(Integer, primary_key=True) ForeignKey("[Link]"), nullable=True)

exam_id = Column(Integer, created_at =


ForeignKey("[Link]", Column(DateTime(timezone=True))
ondelete="CASCADE"), nullable=False) updated_at =
student_id = Column(String(50), Column(DateTime(timezone=True))
nullable=False) # Relationships
student_db_id = Column(Integer, questions = relationship("Question",
ForeignKey("[Link]"), nullable=True) back_populates="situational_question")
responses = Column(String(255), user = relationship("User",
nullable=False) back_populates="situational_questions")
score = Column(Integer, nullable=False)
max_score = Column(Integer, nullable=False) class Question(Base):
created_at = Column(DateTime) __tablename__ = "questions"
detailed_results = Column(JSON, id = Column(Integer, primary_key=True,
nullable=True) autoincrement=True)
# Relationships question_text = Column(Text, nullable=False)
exam = relationship("Exam", question_image_path = Column(String(255),
back_populates="results") nullable=True)
subject_id = Column(Integer, is_correct = Column(Boolean, default=False)
ForeignKey("[Link]"), nullable=False)
option_label = Column(String(10),
topic = Column(String(100), nullable=True) nullable=True)
credit = Column(Integer, nullable=True) created_at =
Column(DateTime(timezone=True))
status = Column(String(20),
default="pending") # Relationship
user_id = Column(Integer, question = relationship("Question",
ForeignKey("[Link]"), nullable=True) back_populates="options")
created_at = def __repr__(self): ...
Column(DateTime(timezone=True))
//===seed_data.py=========//
updated_at =
Column(DateTime(timezone=True)) def seed_subjects():

situational_id = Column(Integer, """


ForeignKey("situational_questions.id"), - Checks if subjects already exist.
nullable=True)
- If not, adds all SUBJECTS to the Subject
# Relationships table.
subject = relationship("Subject", - Handles table creation if missing.
back_populates="questions")
"""
options = relationship("Option",
back_populates="question", cascade="all, # db = next(get_db())
delete-orphan")
# for subject_data in SUBJECTS:
situational_question =
# [Link](Subject(**subject_data))
relationship("SituationalQuestion",
back_populates="questions") # [Link]()
user = relationship("User", if __name__ == "__main__":
back_populates="questions")
# Runs both seed_subjects() and
def __repr__(self): ... seed_questions()
# Prints progress and error messages
class Option(Base):
__tablename__ = "options" //==========[Link]===========//
id = Column(Integer, primary_key=True, from sqlalchemy import Column, Integer, String,
autoincrement=True) ForeignKey, Float
question_id = Column(Integer, from [Link] import relationship
ForeignKey("[Link]"), nullable=False)
from [Link] import Base
option_text = Column(Text, nullable=False)
option_image_path = Column(String(255),
nullable=True) class Subject(Base):
__tablename__ = "subjects"
id = Column(Integer, primary_key=True, verification_token = Column(String(100),
autoincrement=True) nullable=True)
name = Column(String(100), nullable=False) verification_token_expiry =
Column(DateTime(timezone=True),
description = Column(String(255), nullable=True)
nullable=True)
reset_token = Column(String(100),
category_id = Column(Integer, nullable=True)
ForeignKey("[Link]"), nullable=True)
reset_token_expiry =
weight = Column(Float, default=1.0) Column(DateTime(timezone=True),
# Relationships nullable=True)

category = relationship("Category", created_at =


back_populates="subjects") Column(DateTime(timezone=True))

questions = relationship("Question", updated_at =


back_populates="subject") Column(DateTime(timezone=True))

user = relationship("User", role = Column(String(30), nullable=False,


back_populates="subjects") default='program_chair')

def to_dict(self): ... category_id = Column(Integer,


ForeignKey('[Link]'), nullable=True)
//=======[Link]=========//
# Relationships
from sqlalchemy import Column, Integer, String,
DateTime, Boolean, ForeignKey category = relationship("Category",
back_populates="users")
from [Link] import relationship
classes = relationship("Class",
from [Link] import Base back_populates="user")
questions = relationship("Question",
back_populates="user")
class User(Base):
situational_questions =
__tablename__ = "users"
relationship("SituationalQuestion",
id = Column(Integer, primary_key=True, back_populates="user")
autoincrement=True)
subjects = relationship("Subject",
full_name = Column(String(100), back_populates="user")
nullable=False)
exams = relationship("Exam",
email = Column(String(100), unique=True, back_populates="user")
index=True, nullable=False)
def to_dict(self): ...
username = Column(String(50), unique=True,
index=True, nullable=False)
//========routes=============//
password = Column(String(255),
nullable=False) //====[Link]=============//
is_active = Column(Boolean, default=True) # Admin-only endpoints for managing users,
classes, categories, and analytics
is_verified = Column(Boolean, default=False)
@[Link]("/api/algorithm/predict",
methods=["POST"])
# User management: create, list, update, delete,
activate, reset password # Runs prediction using selected algorithm
@[Link]("/api/admin/users", //==========[Link]===========//
methods=["GET", "POST"])
# User authentication endpoints
@[Link]("/api/admin/users/<int:user_id>",
methods=["GET", "PUT", "DELETE"])
@[Link]("/api/auth/register",
methods=["POST"])
# Class management: create, list, update, delete
# Register new user
@[Link]("/api/admin/classes",
methods=["GET", "POST"])
@[Link]("/api/admin/classes/<int:class_id>", @[Link]("/api/auth/login",
methods=["GET", "PUT", "DELETE"]) methods=["POST"])
# Login

# Category management: create, list, update,


delete @[Link]("/api/forgot-password",
@[Link]("/api/admin/categories", methods=["POST"])
methods=["GET", "POST"]) # Request password reset
@[Link]("/api/admin/categories/
<int:category_id>", methods=["GET", "PUT",
"DELETE"]) @[Link]("/api/reset-password",
methods=["POST"])
# Reset password
# Analytics and reports
@[Link]("/api/admin/analytics",
methods=["GET"]) @[Link]("/verify-email", methods=["GET"])
# All endpoints require admin role # Email verification
(admin_required)
//======algorithm_routes.py========//
@[Link]("/api/auth/current-user",
# Endpoints for ML algorithm comparison and methods=["GET"])
prediction
# Get current user info

@[Link]("/api/algorithm/compare",
@[Link]("/api/auth/logout",
methods=["POST"])
methods=["POST"])
# Compares different ML algorithms on exam
# Logout (requires authentication)
data
//=======[Link]=======//
# CRUD endpoints for categories (admin only)
# Endpoints for exam CRUD, question selection,
OMR result upload, analytics, ML integration
@[Link]("/api/categories", methods=["GET",
"POST"])
# List all categories, create new category @[Link]("/api/exams", methods=["GET",
"POST"])
# List, create exams
@[Link]("/api/categories/<int:category_id>",
methods=["GET", "PUT", "DELETE"])
# Get, update, or delete a specific category @[Link]("/api/exams/<int:exam_id>",
methods=["GET", "PUT", "DELETE"])
# Get, update, delete specific exam
# Assign subjects to categories, manage
color/weight
//========[Link]============// @[Link]("/api/exams/<int:exam_id>/print",
methods=["GET", "POST"])
# CRUD endpoints for classes and student
management # Print exam

@[Link]("/api/classes", methods=["GET", @[Link]("/api/exams/<int:exam_id>/results",


"POST"]) methods=["GET"])
# List all classes, create new class # Get exam results

@[Link]("/api/classes/<int:class_id>", @[Link]("/api/exams/import-omr",
methods=["GET", "PUT", "DELETE"]) methods=["POST"])
# Get, update, or delete a specific class # Upload OMR results

@[Link]("/api/classes/<int:class_id>/ @[Link]("/api/exams/<int:exam_id>/ml-
students", methods=["POST"]) question-sequence", methods=["GET",
"POST"])
# Add student to class
# ML question sequence endpoints

@[Link]("/api/classes/<int:class_id>/
students/<int:student_id>", # Handles exam creation, question assignment,
methods=["DELETE", "PUT"]) result processing, reporting, ML analytics
# Remove or update student in class //======[Link]=========//
# Utility endpoints for fixing and validating
image paths
# Returns class details, student status, and exam
summary
//=======[Link]============// @[Link]("/api/image-fix/fix-paths",
methods=["POST"])
# Fixes image paths for questions/options @[Link]("/api/questions/import",
methods=["POST"])
# Import questions from CSV
@[Link]("/api/image-fix/check-paths",
methods=["GET"])
# Checks for missing/broken image paths @[Link]("/api/questions/group",
methods=["POST"])
//======[Link]==========//
# Create group (situational) questions
# Endpoints for ML model training, prediction,
and analysis
@[Link]("/api/questions/<int:question_id>/
revise", methods=["POST"])
@[Link]("/api/ml/train", methods=["POST"])
# Revise question
# Train ML model

# Handles image upload, group questions,


@[Link]("/api/ml/predict", revision, and search
methods=["POST"])
# Predict using ML model
//====render_template_code.html=======//
<!DOCTYPE html>
@[Link]("/api/ml/analysis",
methods=["GET"]) <html lang="en">
# Get ML analysis and feature importance <head>
<meta charset="UTF-8">
# Exposes ML features to the frontend <title>Print Exam: {{ [Link] }}</title>
//======[Link]======// <style>
# (Empty or placeholder file) /* Base styles */
//======[Link]========// body { font-family: 'Times New Roman',
Times, serif; margin: 20px; font-size: 10pt; line-
# CRUD endpoints for questions and options height: 1.2; }
.exam-container { width: 100%; margin:
@[Link]("/api/questions", methods=["GET", auto; }
"POST"]) .header { text-align: center; margin-
# List, create questions bottom: 20px; position: relative; border-bottom:
1px solid #ccc; padding-bottom: 10px; }
.logo { position: absolute; left: 0; top: 0;
@[Link]("/api/questions/<int:question_id>", max-height: 60px; }
methods=["GET", "PUT", "DELETE"])
.school-info { margin-left: 70px; text-
# Get, update, delete specific question align: left; }
.school-info h1 { font-size: 16pt; margin: margin-bottom: 4px;
0; color: #800000; }
}
.school-info p { margin: 2px 0; font-size:
9pt; } .question-display-number {

.exam-title { margin-top: 15px; } font-weight: bold;

.exam-title h2 { font-size: 14pt; margin: min-width: 25px; /* Ensure space for


5px 0; } number */

.exam-title p { font-size: 10pt; margin: display: inline-block;


2px 0; } vertical-align: top;
.student-info { display: flex; justify- }
content: space-between; margin-bottom: 15px;
font-size: 10pt; } .question-text-content {
.student-info div { width: 48%; } display: inline-block;
.student-info span { display: inline- width: calc(100% - 30px);
block; min-width: 50px; }
vertical-align: top;
.student-info .line { border-bottom: 1px
}
solid black; display: inline-block; width:
calc(100% - 60px); margin-left: 5px; } .question-text p, .situational-description
p { margin: 0 0 5px 0; }
.instructions { margin-bottom: 20px;
padding: 10px; border: 1px solid #eee; border- .question-image-container, .option-
radius: 5px; background-color: #f9f9f9; font- image-container {
size: 9pt; }
text-align: left; /* Align images with
.instructions h4 { margin-top: 0; font- text */
size: 10pt; }
margin: 5px 0 5px 25px; /* Indent
.instructions ul { margin: 5px 0 0 20px; images */
padding: 0; }
}
.questions-section { margin-top: 15px; }
.question-image, .option-image {
max-width: 80%; /* Adjust image size
/* Question Styles */ */
.question-item { max-height: 150px;
margin-bottom: 12px; display: block; /* Ensure images don't
disrupt flow */
padding-left: 5px; /* Indent questions
slightly */ margin-top: 5px;
page-break-inside: avoid; }
} .options {
.question-header { margin-left: 25px; /* Indent options */
display: flex; }
.option { .registration-marks { position:
absolute; width: 10px; height: 10px;
display: flex; background: black; }
margin-bottom: 3px; .mark-1 { top: 5px; left: 5px; } .mark-
page-break-inside: avoid; 2 { top: 5px; right: 5px; } .mark-3 { bottom:
5px; left: 5px; } .mark-4 { bottom: 5px; right:
} 5px; }
.option span:first-child { .omr-header { display: flex; justify-
content: space-between; align-items: flex-start;
min-width: 20px; /* Space for A. B.
border-bottom: 1px solid #eee; padding-bottom:
*/
10px; margin-bottom: 10px; }
display: inline-block;
.omr-title h2 { font-size: 14pt; margin:
} 0; } .omr-title p { font-size: 10pt; margin: 2px 0;
}
.omr-instructions { font-size: 8pt;
/* Situational Question Styles */ max-width: 40%; } .omr-instructions ul
.situational-group { { margin: 2px 0 0 15px; padding: 0; }

margin-bottom: 15px; .student-row { display: flex; gap:


20px; margin-bottom: 15px; }
border-left: 2px solid #eee;
.student-section { border: 1px solid
padding-left: 10px; #ddd; padding: 10px; flex: 1; }
page-break-inside: avoid; .student-section h4 { margin: 0 0 10px
0; font-size: 10pt; text-align: center;
} background: #f0f0f0; padding: 5px; border-
.situational-description { radius: 3px; }

font-style: italic; .student-input-row { display: flex;


align-items: center; margin-bottom: 10px; }
margin-bottom: 10px;
.student-label { min-width: 40px; font-
padding: 8px; weight: bold; } .student-line { flex-grow: 1;
border-bottom: 1px solid #999; height: 10px; }
background-color: #f9f9f9;
.bubble-grid { display: flex; gap: 5px;
border-radius: 4px; overflow-x: auto; padding-bottom: 5px; }
} .name-grid { max-height: 100px; /*
.situational-description strong { font- Limit height for screen view */ overflow-y: auto;
weight: bold; } }
.bubble-column { display: flex; flex-
direction: column; align-items: center; }
/* OMR Styles */
.bubble-label { font-size: 7pt; margin-
.omr-sheet { border: 1px solid #ccc; bottom: 3px; }
padding: 15px; margin-top: 20px; position:
relative; background: #fff; page-break-before: .bubble-values { display: flex; flex-
always; } direction: column; gap: 2px; }
.student-bubble { width: 12px; height: body { margin: 15mm; font-size:
12px; border: 1px solid #ccc; border-radius: 10pt; }
50%; display: flex; justify-content: center; align-
items: center; font-size: 6pt; } .pagebreak { page-break-before:
always; }
.answer-grid-container { border: 1px
solid #ddd; padding: 10px; } .omr-sheet { border: none; margin-
top: 0; }
.answer-grid-title { font-size: 11pt;
font-weight: bold; text-align: center; margin- .name-grid { max-height: none;
bottom: 10px; } overflow: visible; } /* Ensure full name grid
prints */
.answer-grid { display: flex; flex-
wrap: wrap; justify-content: space-between; gap: /* Add other print adjustments as
10px 1%; } needed */

.grid-column { display: flex; flex- }


direction: column; gap: 4px; width: calc(100% / </style>
7 - 10px); /* Adjust for 7 columns roughly */
min-width: 90px; } </head>
.question-row { display: flex; align- <body>
items: center; gap: 5px; }
<div class="exam-container">
.question-number { font-size: 8pt;
{% if not omr_only %}
min-width: 20px; text-align: right; }
<!-- Exam Header -->
.options-row { display: flex; gap:
3px; } <div class="header">
.option-bubble { width: 14px; height: <img
14px; border: 1px solid #aaa; border-radius: src="/frontend/images/school_logo.png"
50%; display: flex; justify-content: center; align- alt="School Logo" class="logo"
items: center; font-size: 7pt; } onerror="[Link]='none'">
<div class="school-info">
/* Answer Key Styles */ <h1>{{ school_name or 'Your
School Name' }}</h1>
.answer-key { padding: 20px; border:
1px solid #ccc; margin-top: 20px; } <p>{{ school_address or 'School
Address' }}</p>
.answer-key h2 { text-align: center;
margin-bottom: 15px; } </div>
.answer-key .answer-grid { display: <div class="exam-title">
grid; grid-template-columns: repeat(auto-fill,
minmax(50px, 1fr)); gap: 10px; } <h2>{{ [Link] }}</h2>

.answer-key .answer-grid div { font- <p>Course: {{ [Link] }} |


size: 9pt; } Total Items: {{ exam.num_items }}</p>
</div>

/* Print-specific styles */ </div>

@media print {
{% if not answer_key_only %} <h2>Answer Key -
{{ [Link] }}</h2>
<!-- Student Info Section -->
<div class="answer-grid">
<div class="student-info">
{% for item in
<div>Name: <span processed_items %}
class="line"></span></div>
{% if [Link] is
<div>Date: <span defined %}
class="line"></span></div>
{% for sub_q in
</div> item.sub_questions %}
<div class="student-info">
<div>Section: <span <div>{{ sub_q.display_number }}.
class="line"></span></div> {% for option in
<div>Score: <span sub_q.options %}
class="line"></span></div> {% if
</div> option.is_correct %}{{ option.option_label }}
{% endif %}
{% endfor %}
<!-- Instructions -->
</div>
<div class="instructions">
{% endfor %}
<h4>Instructions:</h4>
{% else %}
<ul>
<li>Read each question <div>{{ item.display_number }}.
carefully.</li>
{% for option in
<li>Choose the best answer for [Link] %}
multiple-choice questions.</li>
{% if
<li>Write your answers clearly option.is_correct %}{{ option.option_label }}
on the provided space or answer sheet.</li> {% endif %}
<li>Ensure your name and {% endfor %}
section are written correctly.</li>
</div>
</ul>
{% endif %}
</div>
{% endfor %}
</div>
<!-- Questions Section -->
</div>
<div class="questions-section">
{% else %}
{% if answer_key_only %}
{% for item in processed_items %}
<div class="answer-key">
{% if [Link] is defined {% for option in
%} sub_q.options %}
<div class="situational-group"> <div class="option">
<div class="situational-
description"> <span>{{ option.option_label }}.</span>
<div>
<p><strong>{{ [Link]
}}</strong></p> {{ option.option_text
}}
</div>
{% if
{% for sub_q in option.option_image_path %}
item.sub_questions %}
<div class="option-
<div class="question-item"> image-container">
<div class="question- <img
header"> src="/{{ option.option_image_path }}"
alt="Option Image" class="option-image"
<span class="question-
display-
number">{{ sub_q.display_number }}.</span> onerror="[Link]=null;
[Link]='/frontend/images/image-not-
<div class="question-text- [Link]';">
content">
</div>
<p>{{ sub_q.question_text }}</p> {% endif %}
{% if </div>
sub_q.question_image_path %}
</div>
<div class="question-
image-container"> {% endfor %}

<img </div>
src="/{{ sub_q.question_image_path }}" </div>
alt="Question Image" class="question-image"
{% endfor %}
onerror="[Link]=null; </div>
[Link]='/frontend/images/image-not-
{% else %}
[Link]';">
<div class="question-item">
</div>
<div class="question-header">
{% endif %}
<span class="question-
</div>
display-number">{{ item.display_number
</div> }}.</span>
<div class="options"> <div class="question-text-
content">
{% endif %}
<p>{{ item.question_text }}</p>
</div>
{% if
item.question_image_path %} </div>

<div class="question- {% endfor %}


image-container"> </div>
<img </div>
src="/{{ item.question_image_path }}"
alt="Question Image" class="question-image" {% endif %}
{% endfor %}
onerror="[Link]=null;
{% endif %}
[Link]='/frontend/images/image-not-
[Link]';"> </div>
</div> {% endif %}
{% endif %}
</div> {% if omr_only or not
answer_key_only %}
</div>
<!-- OMR Sheet -->
<div class="options">
<div class="omr-sheet {% if not
{% for option in
omr_only %}pagebreak{% endif %}">
[Link] %}
<!-- Registration marks -->
<div class="option">
<div class="registration-marks
mark-1"></div>
<span>{{ option.option_label }}.</span>
<div class="registration-marks
<div>
mark-2"></div>
<div class="registration-marks
{{ option.option_text }}
mark-3"></div>
{% if
<div class="registration-marks
option.option_image_path %}
mark-4"></div>
<div class="option-
image-container">
<div class="omr-content">
<img
src="/{{ option.option_image_path }}" <!-- Header for OMR sheet -->
alt="Option Image" class="option-image"
<div class="omr-header">

onerror="[Link]=null; <div class="omr-title">


[Link]='/frontend/images/image-not- <h2>{{ [Link] }}</h2>
[Link]';">
<p>{{ [Link] }} |
</div> {{ exam.num_items }} items</p>
</div> </div>

<div class="omr-instructions"> <!-- Answer grid -->


<h4>Instructions:</h4> <div class="answer-grid-
container">
<ul>
<div class="answer-grid-
<li>Fill ONE bubble per title">Answer Sheet</div>
question with blue/black pen</li>
<div class="answer-grid">
<li>Mark clearly and
completely within the bubble</li> {% for i in range(0,
exam.num_items, 15) %}
<li>Erase completely any
mark you wish to change</li> <div class="grid-column">
</ul> {% for j in range(i, i + 15)
%}
</div>
{% if j <
</div> exam.num_items %}
<div class="question-
<!-- Student information section row">
--> <span
<div class="student-row"> class="question-number">{{ j + 1 }}.</span>

<div class="student-section"> <div class="options-


row">
<h4>Student
Information</h4> {% for letter in
['A', 'B', 'C', 'D'] %}
<div class="student-input-
row"> <div
class="option-bubble">{{ letter }}</div>
<span class="student-
label">Name:</span> {% endfor %}

<div class="student- </div>


line"></div> </div>
</div> {% endif %}
<div class="student-input- {% endfor %}
row">
</div>
<span class="student-
label">Section:</span> </div>
<div class="student- </div>
line"></div>
</div>
</div>
</div>
</div>
{% endif %} # CRUD endpoints for subjects
</div>
@[Link]("/api/subjects", methods=["GET",
"POST"])
<script>
# List, create subjects
// When page loads, log all image
paths for debugging
@[Link]("/api/subjects/<int:subject_id>",
[Link]('DOMContentLoad methods=["GET", "PUT", "DELETE"])
ed', function() {
# Get, update, delete specific subject
// Log all question images
[Link]("Checking all images
on page:"); # Assigns subjects to categories, updates
weights, manages subject info

[Link]('img').forEach(funct //========[Link]===========//
ion(img) { # (Empty or placeholder file)
[Link]("Image src:",
[Link]);
//=========scripts============//

// Add error handling for images


//====debug_exam_images.py=======//
[Link] = function() {
import sys
[Link]('Failed to load
image:', [Link]); import os
if (![Link]('image- import logging
not-found')) {
[Link] =
# Add the parent directory to the path so we can
'/frontend/images/[Link]';
import modules
}
[Link](0,
}; [Link]([Link]([Link](__f
ile__), "../..")))
});
});
from [Link] import get_db
</script>
from [Link] import Exam
</body>
from [Link] import Question
</html>
from [Link] import
get_exam_by_id
//=========[Link]=======//
# Setup logging if img_path.startswith("/"):
[Link]( full_path = img_path
level=[Link], format="%(asctime)s - else:
%(levelname)s - %(message)s"
full_path = [Link](
)
logger = [Link](__name__) [Link]([Link]([Link]
e(__file__))),
img_path,
def debug_exam_images(exam_id):
)
"""Debug image paths for a specific exam"""
[Link](f"Debugging image paths for
exam ID: {exam_id}") # Check if the file exists
exists = [Link](full_path)
# Get the exam with controller function [Link](f" - File exists: {exists}")
exam_data = get_exam_by_id(exam_id) [Link](f" - Full path: {full_path}")

if not exam_data: # If file doesn't exist, try alternate paths


[Link](f"Exam ID {exam_id} not if not exists:
found")
# Try with backend/ prefix
return
if not img_path.startswith("backend/"):
alt_path = [Link](
[Link](f"Exam title:
{exam_data.get('title')}")
[Link]([Link]([Link]
[Link](f"Questions count: e(__file__))),
{len(exam_data.get('questions', []))}")
"backend",
img_path,
# Check image paths for each question
)
for i, q in
enumerate(exam_data.get("questions", [])): alt_exists = [Link](alt_path)

img_path = [Link]("question_image_path") [Link](f" - Alternate path:


{alt_path}")
[Link](f"Question {i+1}:
ID={[Link]('id')}, Image path: {img_path}") [Link](f" - Alternate path
exists: {alt_exists}")

if img_path:
# Check image paths for each option
# Check if path is absolute or relative
for j, opt in enumerate([Link]("options", [])):
opt_img_path = except ValueError:
[Link]("option_image_path")
print("Please provide a valid exam ID
[Link]( (integer)")
f" Option {[Link]('option_label')}: [Link](1)
Image path: {opt_img_path}"
except Exception as e:
)
print(f"Error: {str(e)}")
[Link](1)
if opt_img_path:
# Check if path is absolute or relative
if opt_img_path.startswith("/"):
full_path = opt_img_path
//==========util===================//
else:
//========[Link]========//
full_path = [Link](
# Database connection utilities for SQLAlchemy

[Link]([Link]([Link]
e(__file__))), from sqlalchemy import create_engine
opt_img_path, from [Link] import declarative_base,
) sessionmaker, scoped_session
from dotenv import load_dotenv

# Check if the file exists


exists = [Link](full_path) # Load DB credentials from environment

[Link](f" - File exists: {exists}") # Create SQLAlchemy engine with connection


pooling
[Link](f" - Full path:
{full_path}") engine = create_engine(DATABASE_URL,
pool_size=10, max_overflow=20, ...)

if __name__ == "__main__":
# Thread-safe session factory
if len([Link]) < 2:
SessionLocal =
print("Usage: python scoped_session(sessionmaker(autocommit=Fals
debug_exam_images.py <exam_id>") e, autoflush=False, bind=engine))
[Link](1)
# Declarative base for models
try: Base = declarative_base()
exam_id = int([Link][1])
debug_exam_images(exam_id) def get_db():
# Yields a database session (for use in
routes/controllers)
//=====[Link]========//
...
# Session management utilities for Flask

@contextmanager
from flask import session
def db_session():
# Context manager for DB sessions
(commit/rollback/close) def set_session_user(user):

//=======logging_config.py=========// # Stores user info in session (user_id,


username, role, etc.)
# Logging configuration for the backend
...

import logging
def clear_session():
# Clears all session data (logout)
def setup_logging():
...
# Sets up root logger and ML logger
# Configures file and console handlers, log
format, and log level def get_current_user():

# Returns (root_logger, ml_logger) # Retrieves current user info from session

... ...

//=====migration_runner.py========// //========.env===========//

# Utility for running database migrations DB_HOST=localhost


programmatically DB_NAME=examgen_db
DB_USER=root
def run_migrations(): DB_PASSWORD=
# Runs all pending migrations using Alembic ADMIN_USERNAME=admin
or custom logic
ADMIN_PASSWORD=admin123
# Returns number of migrations run
ADMIN_EMAIL=admin@[Link]
...
ADMIN_FULLNAME=Your Admin Name

def run_specific_migration(migration_name):
# Runs a specific migration script by name
//======run_migrations.py=========//
# Returns True if successful, False otherwise
"""
...
Script to run database migrations manually
""" # If no args provided, run the
detailed_results migration
run_migration("add_detailed_results")
import importlib
import os
//======[Link]==============//
import sys
import sys
import os
def run_migration(migration_name):
import logging
"""Run a specific migration by name"""
import secrets
try:
import re
# Import the migration module
from datetime import timedelta
migration =
importlib.import_module(f"migrations.
{migration_name}")
# Add dotenv support
from dotenv import load_dotenv
# Run the upgrade function
print(f"Running migration:
{migration_name}") load_dotenv()

[Link]()
print(f"Migration {migration_name} # Add the parent directory to the path so we can
completed successfully") import modules properly

return True [Link](0,


[Link]([Link]([Link](__f
except Exception as e: ile__), "..")))
print(f"Error running migration
{migration_name}: {str(e)}")
from flask import (
return False
Flask,
send_from_directory,
if __name__ == "__main__":
jsonify,
# Check command line args for migration
name request,

if len([Link]) > 1: session,

migration_name = [Link][1] redirect,

run_migration(migration_name) make_response,

else: render_template,
)
from [Link] import get_db, ) # Import the new image fix routes
Base, engine
from [Link].logging_config import
setup_logging from [Link]
import (
auth_required,
# Import models in the correct order to ensure
proper registration with SQLAlchemy admin_required,

from [Link] import Subject program_chair_required,

from [Link] import )


Category
from [Link] import Question, # Import migration runner
Option
from [Link].migration_runner import
from [Link] import Class, run_migrations
Student
from [Link] import User
from [Link] import
generate_password_hash
# Then import routes from jinja2 import ChoiceLoader,
from [Link] import FileSystemLoader
auth_routes
from [Link] import app = Flask(
question_routes
__name__,
from [Link] import
subject_routes
static_folder=[Link]([Link]([Link]
from [Link] import [Link](__file__)), "frontend"),
exam_routes
template_folder=[Link](
from [Link] import
categories_routes
[Link]([Link](__file__)),
from [Link] import "frontend", "pages"
class_routes
),
from [Link] import
admin_bp )
from [Link] import ml_routes
# Add the new ML routes
# Add the frontend email-templates directory to
from [Link].algorithm_routes import the Jinja2 loader
algorithm_routes
frontend_email_templates = [Link](
from [Link] import (
[Link]([Link](__file__)),
image_fix_routes, "frontend", "pages", "email-templates"
) [Link]["DEBUG"] = True # Enable debug
mode
[Link]["AUTO_VERIFY_EMAILS"] =
# If not already a ChoiceLoader, wrap it True # Auto-verify emails in development
if not isinstance(app.jinja_loader,
ChoiceLoader):
# Initialize logging
app.jinja_loader = ChoiceLoader(
root_logger, ml_logger = setup_logging()
[
[Link] = root_logger
app.jinja_loader,

FileSystemLoader(frontend_email_templates), # Create database tables before first request


] with app.app_context():
) [Link].create_all(bind=engine)
else:
# If already a ChoiceLoader, append # Run database migrations
try:
app.jinja_loader.[Link](FileSystemLoad
er(frontend_email_templates)) [Link]("Running database
migrations...")
num_migrations = run_migrations()
# Simple session config - avoiding Flask-Session
for now to fix the error [Link](f"Successfully ran
{num_migrations} migrations")
[Link]["SECRET_KEY"] =
[Link]("SECRET_KEY", except Exception as e:
secrets.token_hex(16)) [Link](f"Error running
[Link]["SESSION_COOKIE_NAME"] = migrations: {str(e)}")
"preboard_session"
[Link]["SESSION_COOKIE_SECURE"] = # --- Ensure default admin user exists ---
False # Set to True in production with HTTPS
db = next(get_db())
[Link]["SESSION_COOKIE_HTTPONLY"
] = True admin_username =
[Link]("ADMIN_USERNAME")
[Link]["SESSION_COOKIE_SAMESITE"]
= "Lax" admin_password =
[Link]("ADMIN_PASSWORD")
[Link]["PERMANENT_SESSION_LIFETI
ME"] = timedelta(days=1) admin_email =
[Link]("ADMIN_EMAIL")
admin_fullname =
# Development configs [Link]("ADMIN_FULLNAME",
"Admin User")
if admin_username and admin_password and # Initialize route blueprints
admin_email:
# Note: Some routes are functions that need to
# Check for any user with the admin be called with the app
username, regardless of role
# while others are Blueprint instances that need
existing_admin = to be registered
[Link](User).filter([Link] ==
admin_username).first()
if not existing_admin: # Initialize route functions that modify the app

[Link](f"Creating default admin auth_routes(app)


user: {admin_username}") question_routes(app)
admin_user = User( subject_routes(app)
full_name=admin_fullname, exam_routes(app)
email=admin_email, categories_routes(app)
username=admin_username, class_routes(app)
ml_routes(app) # Initialize ML routes
password=generate_password_hash(admin_pass
word), image_fix_routes(app) # Initialize image fix
routes
role="admin",
is_active=True,
# Register blueprint instances
is_verified=True,
app.register_blueprint(admin_bp,
) url_prefix="/api")
[Link](admin_user) app.register_blueprint(algorithm_routes)
[Link]()
else: # Admin login page (unprotected)
[Link]( @[Link]("/admin", methods=["GET"])
f"Admin user '{admin_username}' @[Link]("/admin/", methods=["GET"])
already exists. Skipping creation."
def admin_login_page():
)
return send_from_directory(
else:
[Link](app.static_folder, "pages"),
[Link]( "[Link]"
"ADMIN_USERNAME, )
ADMIN_PASSWORD, or ADMIN_EMAIL not
set in .env. Skipping admin creation."
) # Admin panel route with admin_required
middleware
@[Link]("/admin/panel", methods=["GET"])
@admin_required return send_from_directory(
def admin_panel(): [Link](app.static_folder, "pages"),
"[Link]"
return send_from_directory(
)
[Link](app.static_folder, "pages"),
"[Link]"
) @[Link]("/admin/question-bank")
@admin_required
# Admin dashboard route with admin_required def admin_question_bank():
middleware
return send_from_directory(
@[Link]("/admin/dashboard",
methods=["GET"]) [Link](app.static_folder, "pages"),
"[Link]"
@admin_required
)
def admin_dashboard():
return send_from_directory(
# Request logging middleware
[Link](app.static_folder, "pages"),
"[Link]" @app.before_request

) def log_request_info():
[Link]("Headers: %s",
[Link])
# Add routes for admin user and program
management pages [Link]("Body: %s",
request.get_data())
@[Link]("/admin/users", methods=["GET"])
[Link]("Session: %s",
@admin_required dict(session))
def user_management_page():
return send_from_directory( @[Link]("/")
[Link](app.static_folder, "pages"), def home():
"[Link]"
if [Link]("logged_in"):
)
# Redirect to stored next_url if it exists,
otherwise to dashboard
@[Link]("/admin/programs", next_url = [Link]("next_url",
methods=["GET"]) "/dashboard")
@admin_required return redirect(next_url)
def categories_management_page(): return send_from_directory(app.static_folder,
"[Link]")
# Serve from the static folder (frontend) with
the relative path
@[Link]("/dashboard") @program_chair_required
@program_chair_required def class_record():
def dashboard(): return send_from_directory(
return send_from_directory( [Link](app.static_folder, "pages"),
"[Link]"
[Link](app.static_folder, "pages"),
"[Link]" )
)
@[Link]("/dashboard/question-bank/items")
@[Link]("/dashboard/question-bank") @program_chair_required
@program_chair_required def question_bank_items():
def question_bank(): return send_from_directory(
return send_from_directory( [Link](app.static_folder, "pages"),
"[Link]"
[Link](app.static_folder, "pages"),
"[Link]" )
)
# Replace [Link] route with a
cleaner URL pattern
@[Link]("/dashboard/item-analysis")
@[Link]("/dashboard/question")
@program_chair_required
@program_chair_required
def item_analysis():
def question_detail():
return send_from_directory(
"""Serve the question details page with query
[Link](app.static_folder, "pages"), parameters"""
"[Link]"
[Link]("Serving question detail page
) with params: %s", [Link])
return send_from_directory(
@[Link]("/dashboard/prediction-tool") [Link](app.static_folder, "pages"),
@program_chair_required "[Link]"

def prediction_tool(): )

return send_from_directory(
[Link](app.static_folder, "pages"), # Keep the old route temporarily for backwards
"[Link]" compatibility

) @[Link]("/dashboard/
[Link]")
@program_chair_required
@[Link]("/dashboard/class-record")
def question_bank_items_direct(): return send_from_directory(
# Redirect to the new URL format [Link](app.static_folder, "pages"),
"[Link]"
exam = [Link]("exam", "")
)
subject_id = [Link]("subject_id", "")
return redirect(f"/dashboard/question?
exam={exam}&subject_id={subject_id}") @[Link]("/dashboard/classes")
@program_chair_required
# Keep both old and new routes temporarily for def classes_management():
backward compatibility
return send_from_directory(
@[Link]("/question-bank")
[Link](app.static_folder, "pages"),
@program_chair_required "[Link]"
def question_bank_redirect(): )
return redirect("/dashboard/question-bank")
@[Link]("/dashboard/class-management")
@[Link]("/item-analysis") @program_chair_required
@program_chair_required def class_management_redirect():
def item_analysis_redirect(): return redirect("/dashboard/classes")
return redirect("/dashboard/item-analysis")
# Add logout route
@[Link]("/prediction-tool") @[Link]("/api/auth/logout")
@program_chair_required def logout():
def prediction_tool_redirect(): [Link]()
return redirect("/dashboard/prediction-tool") return jsonify({"status": "success",
"message": "Logged out successfully"}), 200

@[Link]("/class-record")
# Session testing route
@program_chair_required
@[Link]("/test-session")
def class_record_redirect():
def test_session():
return redirect("/dashboard/class-record")
if "visit_count" in session:
session["visit_count"] =
@[Link]("/dashboard/categories") [Link]("visit_count") + 1
@program_chair_required else:
def categories_management(): session["visit_count"] = 1
@[Link]("/frontend/assets/css/
<path:filename>")
return jsonify(
def serve_css(filename):
{
return send_from_directory(
"status": "success",
[Link](app.static_folder, "assets",
"message": f"You've visited this page "css"), filename
{session['visit_count']} time(s)",
)
"session_data": {k: str(v) for k, v in
[Link]()},
} @[Link]("/frontend/images/
<path:filename>")
)
def serve_images(filename):
return
# Special route to handle thesis-preboard-exam- send_from_directory([Link](app.static_fol
gen paths der, "images"), filename)
@[Link]("/thesis-preboard-exam-gen/
frontend/<path:filename>")
# Add specific route for print CSS
def serve_thesis_files(filename):
@[Link]("/api/assets/css/[Link]")
return send_from_directory(app.static_folder,
filename) def serve_print_css():
response = send_from_directory(
# Add specific routes for components to make [Link](app.static_folder, "assets",
debugging easier "css"), "[Link]"
@[Link]("/frontend/components/ )
<path:filename>")
# Set cache control for CSS
def serve_components(filename):
[Link]["Cache-Control"] = "public,
return max-age=86400"
send_from_directory([Link](app.static_fol
der, "components"), filename) return response

@[Link]("/frontend/assets/js/ # Add a debug route to check image existence


<path:filename>") and improve the image serving route

def serve_js(filename): @[Link]("/api/debug/question-images")

return send_from_directory( @program_chair_required

[Link](app.static_folder, "assets", def debug_question_images():


"js"), filename """Debug endpoint to list all image files in the
) questions folder"""
# Get the uploads directory path - using return jsonify(
backend/static
{
upload_dir = [Link](
"status": "error",
[Link](__file__), "static",
"uploads", "questions" "message": f"Error listing files:
{str(e)}",
)
"directory": upload_dir,
"absolute_path":
# Check if directory exists [Link](upload_dir),
if not [Link](upload_dir): }
return jsonify( )
{
"status": "error", # Improved route to serve question images with
better error handling
"message": f"Upload directory does
not exist: {upload_dir}", @[Link]("/backend/static/uploads/
questions/<path:filename>")
"absolute_path":
[Link](upload_dir), def serve_question_images(filename):
} """Serve uploaded question images with
proper error handling"""
)
try:
# The directory structure relative to the
# List all files in the directory Flask app
try: upload_dir = [Link](
files = [Link](upload_dir) [Link](__file__), "static",
return jsonify( "uploads", "questions"

{ )

"status": "success",
"directory": upload_dir, # Ensure the directory exists

"absolute_path": [Link](upload_dir, exist_ok=True)


[Link](upload_dir),
"file_count": len(files), # Log detailed debugging information
"files": files, [Link](f"Requested image:
} {filename}")

) [Link](f"Looking in directory:
{upload_dir}")
except Exception as e:
[Link](f"Absolute path: """Serve frontend images including the
{[Link](upload_dir)}") fallback image"""
try:
# Check if the file exists images_dir = [Link](app.static_folder,
"images")
file_path = [Link](upload_dir,
filename) return send_from_directory(images_dir,
filename)
if not [Link](file_path):
except Exception as e:
[Link](f"Image file not
found: {file_path}") [Link](f"Error serving frontend
image {filename}: {str(e)}")
# Return the default image-not-found
image return jsonify({"error": "Error serving
image"}), 500
return send_from_directory(
[Link](app.static_folder,
"images"), "[Link]" @[Link]("/static/<path:filepath>")
) def serve_static_files(filepath):
[Link](f"Serving static file:
{filepath}")
[Link](f"Image found, serving
from: {file_path}") # Try to first serve from the backend/static
directory
response =
send_from_directory(upload_dir, filename) backend_static =
[Link]([Link](__file__), "static")
[Link]["Cache-Control"] = "no-
cache, no-store, must-revalidate" if [Link]([Link](backend_static,
filepath)):
[Link]["Pragma"] = "no-cache"
return send_from_directory(backend_static,
[Link]["Expires"] = "0" filepath)
return response
except Exception as e: # Fallback to the project root static directory
[Link](f"Error serving image static_dir =
{filename}: {str(e)}") [Link]([Link]([Link](__
return jsonify({"error": "Error serving file__)))
image"}), 500 return send_from_directory(static_dir,
f"static/{filepath}")

# Add a route for the fallback image


@[Link]("/frontend/images/ @[Link]("/<path:path>")
<path:filename>") def static_files(path):
def serve_frontend_images(filename): return send_from_directory(app.static_folder,
path)
return jsonify({"status": "error",
"message": "Not logged in"}), 401
@[Link]("/test-db")
def test_db():
# Add a test endpoint to verify API connectivity
"""Test database connection and return
status""" @[Link]("/api/test", methods=["GET"])
try: def test_api():
# Get a database session """Simple API test endpoint"""
db = next(get_db()) return jsonify({"status": "success",
"message": "API is working"})
[Link]("SELECT 1")
return jsonify(
if __name__ == "__main__":
{"status": "success", "message":
"Database connection successful"} # Setup logging configuration
) [Link](
except Exception as e: level=[Link],
return jsonify({"status": "error", format="%(asctime)s - %(name)s - %
"message": f"Database error: {str(e)}"}) (levelname)s - %(message)s",
)
# Add an endpoint to get current user session
data
# Run database migrations
@[Link]("/api/auth/current-user")
[Link]("Running database
def get_current_user(): migrations...")
if [Link]("logged_in"): run_migrations()
return jsonify(
{ # Start the server
"status": "success", [Link](port=8080, debug=True,
threaded=True)
"user": {
"id": [Link]("user_id"),
"username":
[Link]("username"),
}, //========frontend=============//
} //===========api============//
) //========[Link]========//
else: // API utility functions for authentication
status_code: 500
// Base API URL - adjust if needed based on };
your deployment
}
const API_BASE_URL = '/api';
}

// Generic function to make API requests


// Authentication functions - make them global
async function apiRequest(endpoint, method = so [Link] can access them
'GET', data = null) {
[Link] = async function(userData)
const options = { {
method, return apiRequest('/auth/register', 'POST',
userData);
headers: {
};
'Content-Type': 'application/json',
},
[Link] = async function(credentials)
}; {
return apiRequest('/auth/login', 'POST',
if (data) { credentials);

[Link] = [Link](data); };

}
[Link] = async function(email)
{
try {
return apiRequest('/auth/forgot-password',
const response = await fetch(`$ 'POST', { email });
{API_BASE_URL}${endpoint}`, options);
};
const result = await [Link]();

// Add response status to the result


//==========assets============//
result.status_code = [Link];
//===========css===========//
return result;
//======[Link]====//
} catch (error) {
/* Styles for admin dashboard layout and
[Link]('API request error:', error); widgets */
return { .header { ... }
status: 'error', .sidebar { ... }
message: 'Network or server error .dashboard-card { ... }
occurred',
.stats-panel { ... } .class-record-table { ... }
.table { ... } .[Link] { ... }
.button, .btn-primary, .btn-danger { ... } .[Link] { ... }
/* Responsive adjustments for mobile/tablet */ .attendance-mark { ... }
@media (max-width: 768px) { ... } //==[Link]=======///
//====[Link]===// * Styles for exam management interface */
/* Styles for ML/algorithm comparison page */ .exam-list { ... }
.algorithm-table { ... } .exam-card { ... }
.result-chart { ... } .exam-form { ... }
.highlight-best { ... } .[Link] { ... }
.highlight-worst { ... } .[Link] { ... }
/* Responsive chart/table design */ //========[Link]========//
@media (max-width: 600px) { ... } /* Styles for folder/file management UI */
//===[Link]====/// .folder-list { ... }
* Styles for category management UI */ .[Link] { ... }
.category-list { ... } .file-upload-btn { ... }
.category-color-picker { ... } //=======[Link]========//
.category-edit-form { ... } /* Styles for item analysis and statistics */
.[Link] { ... } .analysis-table { ... }
.drag-handle { ... } .analysis-chart { ... }
/* Visual feedback for drag-and-drop */ .item-difficulty-bar { ... }
//=====[Link]===// //========[Link]=======//
* Styles for class management and student lists /* Styles for loading spinners and overlays */
*/
.loading-spinner { ... }
.class-card { ... }
.loading-overlay { ... }
.student-table { ... }
//==========[Link]========//
.[Link] { ... }
/* Styles for login, register, and password reset
.class-modal { ... } forms */
.add-student-btn { ... } .login-form { ... }
/* Responsive layouts for class/student views */ .input-group { ... }
//====[Link]========// .login-btn, .register-btn { ... }
/* Styles for class record and gradebook */ .form-error { ... }
// .question-filter { ... }
====[Link]========//
/* Styles for math equations and image display
in questions */ //======[Link]=======//

.math-equation { ... } /* Styles for question bank items and details */

.question-image { ... } .item-list { ... }

//========[Link]===========// .[Link] { ... }

/* Styles for modal dialogs and overlays */ .item-details { ... }

.modal { ... } //=====[Link]========//

.modal-header, .modal-footer { ... } /* Styles for sidebar navigation */

.close-btn { ... } .sidebar { ... }

//=======[Link]========// .[Link] { ... }

/* Styles for navigation bar and links */ //======[Link]=======//

.navbar { ... } /* General/global styles for the app */

.[Link] { ... } body, html { ... }

.navbar-brand { ... } h1, h2, h3 { ... }

//=====[Link]=============// a, button { ... }

/* Styles for prediction tool UI */ input, select, textarea { ... }

.prediction-form { ... } //======[Link]========//

.prediction-result { ... } /* Styles for tooltips */

//=====[Link]=========// .tooltip { ... }

/* Print-friendly styles for exam PDFs and .tooltip-arrow { ... }


reports */
@media print {
.no-print { display: none; } //========js=========//
.print-header { ... } //=====[Link]======//
.print-table { ... } // Handles admin dashboard interactivity
} // - Loads dashboard stats, charts, and recent
//=======[Link]============// activity

/* Styles for question bank management */ // - Manages event listeners for dashboard
widgets
.question-bank-list { ... }
// - Handles AJAX calls to fetch dashboard data
.[Link] { ... }
// - Updates DOM with user, class, and exam <li><a href="...">Dashboard</a></li>
summaries
<li><a href="...">User Management</a></li>
<li><a href="...">Class
Management</a></li>
//======components=================// <li><a href="...">Category
Management</a></li>
//=======[Link]==========//
<li><a href="...">Exam
<!-- Admin navigation bar --> Management</a></li>
<nav class="admin-navbar"> <li><a href="...">Analytics</a></li>
<!-- Brand/logo section --> <!-- ... -->
<div class="navbar-brand">...</div> </ul>
<!-- Navigation links: Dashboard, Users, <!-- Collapsible/expandable sections for sub-
Classes, Categories, Exams, Analytics --> menus (if any) -->
<ul class="navbar-links"> </aside>
<li><a href="...">Dashboard</a></li> //=====[Link]=================//
<li><a href="...">Users</a></li> <!-- General header for non-admin pages -->
<li><a href="...">Classes</a></li> <header class="main-header">
<!-- ... --> <!-- Brand/logo -->
</ul> <div class="header-logo">...</div>
<!-- User profile dropdown/logout --> <!-- Page title or breadcrumbs -->
<div class="navbar-user"> <div class="header-title">Page Title</div>
<span>Admin Name</span> <!-- User info/profile/logout -->
<a href="...">Logout</a> <div class="header-user">
</div> <span>User Name</span>
</nav> <a href="...">Logout</a>
//=======[Link]=======// </div>
<!-- Admin sidebar menu --> </header>
<aside class="admin-sidebar"> //
<!-- User info/profile (optional) --> =========[Link]===============//

<div class="sidebar-profile">...</div> <!-- Main navigation bar for non-admin users --


>
<!-- Navigation links: Dashboard, User
Management, Class Management, etc. --> <nav class="main-navbar">

<ul class="sidebar-menu"> <!-- Brand/logo -->


<div class="navbar-brand">...</div>
<!-- Navigation links: Home, Exams, Question </aside>
Bank, Analytics, etc. -->
<ul class="navbar-links">
//=======ml==========//
<li><a href="...">Home</a></li>
//=======test===========//
<li><a href="...">Exams</a></li>
//======test_data_preparation.py=====//
<li><a href="...">Question Bank</a></li>
# Tests for ML data preparation utilities
<li><a href="...">Analytics</a></li>
# - Validates data loading and preprocessing
<!-- ... --> functions
</ul> # - Checks feature extraction, label encoding,
and missing value handling
<!-- User profile/logout -->
# - Asserts correct output shapes and types for
<div class="navbar-user"> ML input
<span>User Name</span> def test_load_data(): ...
<a href="...">Logout</a> def test_prepare_features(): ...
</div> def test_handle_missing_values(): ...
</nav> //=========test_end_to_end.py=======//
//=======[Link]==============// # End-to-end tests for the ML pipeline
<!-- Sidebar for non-admin users --> # - Runs the full pipeline: data loading →
<aside class="main-sidebar"> training → prediction → evaluation

<!-- User info/profile --> # - Asserts that the pipeline completes without
errors
<div class="sidebar-profile">...</div>
# - Checks that model accuracy and output meet
<!-- Navigation links: Dashboard, Exams, expected thresholds
Question Bank, Item Analysis, etc. -->
def test_full_pipeline(): ...
<ul class="sidebar-menu">
def test_pipeline_with_real_data(): ...
<li><a href="...">Dashboard</a></li>
//========test_integration.py=========//
<li><a href="...">Exams</a></li>
# Integration tests for ML components
<li><a href="...">Question Bank</a></li>
# - Tests interaction between data preparation,
<li><a href="...">Item Analysis</a></li> model training, and prediction modules
<li><a href="...">Prediction Tool</a></li> # - Ensures models can be trained and used for
inference with real or mock data
<li><a href="...">Class Record</a></li>
def test_model_training_and_prediction(): ...
<!-- ... -->
def test_feature_importance_output(): ...
</ul>
//===========test_models.py=======//
<!-- Collapsible for mobile/tablet (if any) -->
# Unit tests for individual ML models # Adds item analysis metrics (difficulty,
discrimination, distractor strength, etc.)
# - Tests model initialization, training, and
prediction for each supported algorithm
# - Checks model serialization/deserialization def enhance_features(df):
(joblib)
# Adds advanced text features (readability,
def test_decision_tree(): ... word count, technical terms, etc.)
def test_random_forest(): ...
def test_xgboost(): ... def prepare_data(question_bank_id=None,
use_examinee_data=True,
def test_model_save_and_load(): ... use_item_analysis=True):
//=========test_prediction.py=======// # Full pipeline: get data, add empirical/item
# Tests for prediction logic and API analysis features, define target, clean

# - Validates prediction endpoints or functions # Returns DataFrame ready for ML model


training
# - Checks input validation, output format, and
error handling
def test_predict_single(): ... def split_data(df, target_column="difficulty",
test_size=0.3, random_state=42):
def test_predict_batch(): ...
# Splits data into train/test, normalizes
def test_invalid_input(): ... features, saves scaler

//======data_preparation.py==========// def
prepare_features_for_prediction(question_data):
# Data extraction, feature engineering, and
dataset preparation for ML # Prepares features for a single question for
prediction
//===db_model_evaluation.py=========//
def
get_data_from_db(question_bank_id=None): # Loads student data from DB, preprocesses,
and evaluates XGBoost classifier
# Fetches questions (and options, subjects)
from DB, returns DataFrame with features
# 1. Fetches students with board results,
preboard scores, subject GPAs
def enrich_with_examinee_data(df=None):
# 2. Extracts/averages scores and GPAs from
# Adds empirical difficulty, discrimination,
JSON fields
and response stats from exam results
# 3. Balances classes (upsampling)
# Handles missing data with text-based
difficulty estimation # 4. Trains XGBoost with GridSearchCV,
evaluates with cross-validation
# 5. Prints confusion matrix, accuracy, precision,
def enrich_with_item_analysis_data(df,
recall, F1
question_bank_id=None):
//====difficulty_model.py========// # Checks for missing mappings, prints sample
detailed_results
# Class-based ML model for predicting question
difficulty (with NLP features)
if __name__ == "__main__":
class DifficultyModel: diagnose_exams()
def __init__(self): //======ml_prediction.py======//
# Loads model, vectorizer, scaler, metadata # Unified interface for predicting question
if available difficulty

def train(self, questions_data): def predict_difficulty(question_data,


confidence_needed=0.7):
# Trains XGBoost regressor on question
features + text vectorization # Uses DifficultyModel or fallback to legacy
model
# Returns difficulty, confidence, source,
def predict(self, question): message, subject, topic
# Predicts difficulty using ML model or
heuristics if not trained
def get_model_info():
# Returns info about the current ML model
def _extract_features(self, question): (type, version, accuracy, etc.)
# Extracts features for prediction (text
length, word count, options, etc.)
def prepare_features(question_data):
# Prepares features for prediction (handles
def _calculate_heuristic_difficulty(self, missing/categorical data, scaling)
features):
//======model_evaluation.py==========//
# Simple rules for fallback difficulty
estimation # Utility for evaluating classification models

# Singleton instance: difficulty_model = def evaluate_classification_model(y_true,


DifficultyModel() y_pred, model_name, metrics_csv_path,
confusion_matrix_png_path):
//=====exam_diagnosis.py==========//
# Calculates accuracy, precision, recall, F1
# Script to diagnose exam/question mapping (macro/weighted)
issues in the DB
# Saves metrics to CSV, confusion matrix to
PNG
def diagnose_exams(): # Prints summary to console
# Prints counts of exams, results, mappings

if __name__ == "__main__":
# Runs sample evaluation with dummy data # Trains XGBoost model for question difficulty
prediction
//======model_integration.py========//
# Integrates ML models with the web app,
logging, and question selection def
train_xgboost_model(question_bank_id=None,
use_existing_models=False, timestamp=None):
def predict_question_difficulty(question): # Prepares data, splits train/test, tunes
# Predicts difficulty for a question, logs hyperparameters with GridSearchCV
prediction # Trains, evaluates, saves model and feature
importances

def get_ml_system_status():
# Returns ML system status, accuracy, feature if __name__ == "__main__":
importance # Runs training for all or specific question
bank

def select_questions_for_new_exam(course_id, //======[Link]========//


question_bank_id, num_items, class_id=None): # Model training, hyperparameter tuning,
# Selects questions for an exam using ML or evaluation, and ensemble logic
fallback to random selection

def train_model(X_train, y_train,


def get_class_performance_data(class_id, model_type="random_forest",
db=None): tune_hyperparams=True, **kwargs):

# Aggregates class performance for intelligent # Trains model (XGBoost, DecisionTree, etc.)
question selection with optional tuning

def update_model_with_exam_results(exam_id, def evaluate_model(model, X_test, y_test):


db=None): # Evaluates model (MSE, RMSE, MAE, R²),
# Updates/retrains model with new exam saves plots
results if enough new data

def train_ensemble_model(X_train, y_train,


def should_model_be_retrained(db): tune_hyperparams=True, **kwargs):

# Decides if retraining is needed based on # Trains ensemble (VotingRegressor) of


new data since last training multiple models

def save_training_statistics(...): def save_model_version(model, model_type):

# Saves model training stats for monitoring # Saves model version info for tracking

//======model_training.py==========//
def get_prediction_explanation(model, X, # Selects questions maximizing topic
feature_names): diversity
# Returns top feature importances for a
prediction
def select_questions_randomly(questions,
//=======prediction_logger.py=======// num_items):
# Logs predictions for analysis and continuous # Fallback: random selection
learning

def
class PredictionLogger: load_question_selection_model(question_bank_i
d=None):
def log_prediction(self, question_id,
prediction_result, model_version="current"): # Loads latest XGBoost model and feature
names for a question bank
# Appends prediction to daily JSONL log
//========run_full_tests.py==========//
# Test runners for ML module
def get_recent_predictions(self, limit=100):
# Loads recent predictions from logs
def run_full_tests():
# Runs all tests, including end-to-end
def generate_prediction_report(self):
# Aggregates stats (avg difficulty, counts by
subject/topic) def run_tests():
//========question_selection.py========// # Runs standard ML tests
# Intelligent question selection for exams using
ML predictions
# run_single_end_to_end.py runs just the end-
to-end test
def select_questions_intelligently(db, course_id, //========run_tests.py=========//
question_bank_id, num_items,
class_performance=None): # Script to run model training with correct
import order
# Loads model, predicts difficulty, ranks and
selects diverse questions
if __name__ == "__main__":

def rank_questions_by_score(questions, # Imports all models, runs


question_scores): train_xgboost_model(), prints summary

# Sorts questions by predicted difficulty //========run_training.py======//


# Script to run model training with correct
import order
def select_diverse_questions(ranked_questions,
num_items):
if __name__ == "__main__":
# Imports all models, runs # Periodic check for model update
train_xgboost_model(), prints summary
//===student_performance_schema.py====//
# SQLAlchemy schema for tracking student
exam sessions, responses, and performance
//==student_performance_integration.py==//
class ExamSession(Base):
# Integrates student performance data for # Exam session metadata, links to Exam and
continuous learning and model retraining StudentExam

def class StudentExam(Base):


extract_performance_metrics(db_session=None)
: # Student's attempt at an exam, links to
Student, ExamSession, StudentResponse
# Aggregates attempts, correctness, response
times per question
class StudentResponse(Base):

def # Student's response to a question,


update_question_difficulties(performance_metri correctness, time spent, etc.
cs, db_session=None):
# Updates question difficulty in DB based on # Example usage: calculate empirical difficulty,
empirical data discrimination, response times
//====test_accuracy_explanation.py====//
def should_retrain_model(db_session=None): # Test scripts for accuracy explanation and
# Decides if retraining is needed (time, new algorithm comparison
data, accuracy)

def demonstrate_high_accuracy():
def schedule_model_update(): # Shows why high accuracy is legitimate
# Schedules model update if needed (plots MAE vs accuracy)

def train_new_model(): def test_mae_to_accuracy():

# Trains new model with latest data # Tests accuracy scaling for various MAE
values

def generate_question_analytics(question_id):
def test_synthetic_model_evaluation():
# Returns analytics for a specific question
# Tests accuracy scaling with synthetic
predictions
def automatic_model_update_check(): //===test_algorithm_comparison.py=====//
# Test scripts for accuracy explanation and # Debugging utility for class and student
algorithm comparison relationships

def demonstrate_high_accuracy(): def debug_class_relationships():


# Shows why high accuracy is legitimate # Analyzes class-student relationships, prints
(plots MAE vs accuracy) counts and issues
# Checks for orphaned students, missing
relationships, etc.
def test_mae_to_accuracy():
# Tests accuracy scaling for various MAE
values def fix_class_student_relationships():
# Fixes broken class-student relationships
def test_synthetic_model_evaluation(): # Reassigns orphaned students to default class
# Tests accuracy scaling with synthetic
predictions
def analyze_student_distribution():
//======validate_model.py=====//
# Shows distribution of students across
# Validates all trained models on real test data classes
# Identifies classes with too many/few
students
def _validate_single_model(model_name,
X_test, y_test, question_bank_id=None):
# Loads model, aligns features, evaluates, if __name__ == "__main__":
saves metrics and confusion matrix
# Runs debugging and fixing functions
//======fix_class_students.py======//
def
validate_all_models(question_bank_id=None): # Utility to fix class and student data integrity
issues
# Prepares data, splits test set, validates all
models (XGBoost, RF, DT)
def fix_orphaned_students():

if __name__ == "__main__": # Finds students without class assignments

# Runs validation for all or specific question # Assigns them to a default class or creates
bank new class

def fix_duplicate_students():

//=========tools=================// # Identifies and merges duplicate student


records
//======debug_class.py=====//
# Preserves exam results and other data
DB_PASSWORD =
[Link]("DB_PASSWORD", "")
def validate_class_data():
DB_HOST = [Link]("DB_HOST",
# Validates class-student relationships "localhost")
# Reports data integrity issues DB_PORT = int([Link]("DB_PORT",
"3306"))

def cleanup_empty_classes(): DB_NAME = [Link]("DB_NAME",


"examgen_db")
# Removes classes with no students
# Handles cleanup of related data
def create_database():
"""Create the database if it doesn't exist"""
if __name__ == "__main__":
try:
# Runs all fixing functions with confirmation
prompts # Connect to MySQL without specifying a
database
connection = [Link](
//=========create_db.py======//
host=DB_HOST,
"""
port=DB_PORT,
Script to create the database if it doesn't exist.
user=DB_USER,
Run this script before initializing tables to
ensure the database exists. password=DB_PASSWORD,

""" charset="utf8mb4"
)

import os
import sys try:

import pymysql with [Link]() as cursor:

from dotenv import load_dotenv # Check if database exists


[Link](f"SHOW
DATABASES LIKE '{DB_NAME}'")
# Load environment variables
result = [Link]()
load_dotenv()

if result:
# Database connection settings (without
database name) print(f"Database '{DB_NAME}'
already exists.")
DB_USER = [Link]("DB_USER",
"root") else:
# Create database
[Link](f"CREATE import subprocess
DATABASE {DB_NAME} CHARACTER SET
utf8mb4 COLLATE utf8mb4_unicode_ci") import sys

[Link]() import os

print(f"Database '{DB_NAME}'
created successfully.") def create_lock_file():
finally: print("Creating requirements lock file...")
[Link]()

# Path to the lock file


return True lock_file_path = "[Link]"
except Exception as e:
print(f"Error creating database: {str(e)}") try:
return False # Run pip freeze to get all installed
packages with versions

if __name__ == "__main__": result = [Link](

print("Checking database...") [[Link], "-m", "pip", "freeze"],

if create_database(): capture_output=True,

print("Database setup complete.") text=True,

print("Next steps:") check=True

print("1. Run 'python init_db.py' to create )


the tables")
print("2. Run 'python run_seed_data.py' to # Get the output
populate with sample data")
packages = [Link]
else:
print("Database setup failed.")
# Write to the lock file
[Link](1)
with open(lock_file_path, "w") as lock_file:
lock_file.write("# Locked dependencies -
Do not modify this file directly\n")
//========create_lock_file.py======// lock_file.write("# Generated from pip
""" freeze\n")

Script to create a requirements lock file. lock_file.write(packages)

This script generates a [Link] file


with exact versions of all installed dependencies. print(f"Lock file created successfully at
""" {lock_file_path}")
print("Use this file for production print("Installing spaCy model...")
deployments with: pip install -r requirements-
[Link]") subprocess.check_call([[Link], "-m",
"spacy", "download", "en_core_web_sm"])

except [Link] as e:
print("Installing NLTK packages...")
print(f"Error running pip freeze: {e}")
import nltk
return False
except IOError as e:
for package in ["punkt", "stopwords",
print(f"Error writing lock file: {e}") "wordnet"]:
return False print(f"Downloading {package}...")
[Link](package)
return True
print("Environment setup complete!")
if __name__ == "__main__":
create_lock_file() if __name__ == "__main__":
install_requirements()

//=====setup_environment.py===//
import subprocess
import sys

def install_requirements():
print("Installing main requirements...")
subprocess.check_call(
[[Link], "-m", "pip", "install", "-r",
"[Link]"]
)

You might also like