Spaces:
Sleeping
Sleeping
Upload 22 files
Browse files- Dockerfile +21 -0
- __pycache__/auth.cpython-312.pyc +0 -0
- __pycache__/crud.cpython-312.pyc +0 -0
- __pycache__/database.cpython-312.pyc +0 -0
- __pycache__/main.cpython-312.pyc +0 -0
- __pycache__/models.cpython-312.pyc +0 -0
- auth.py +23 -0
- crud.py +71 -0
- database.py +30 -0
- main.py +190 -0
- models.py +20 -0
- rf_cgpa_model.pkl +3 -0
- scaler.pkl +3 -0
- static/js/hours_check.js +7 -0
- templates/admin_dashboard.html +47 -0
- templates/admin_user_detail.html +46 -0
- templates/base.html +31 -0
- templates/dashboard.html +46 -0
- templates/login.html +16 -0
- templates/predict.html +173 -0
- templates/signup.html +15 -0
- users.db +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use the official Python image as the base image
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# Set the working directory in the container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy the requirements file to the container (if you have one)
|
| 8 |
+
# COPY requirements.txt .
|
| 9 |
+
|
| 10 |
+
# Install the necessary dependencies
|
| 11 |
+
RUN pip install --upgrade pip && \
|
| 12 |
+
pip install fastapi uvicorn jinja2 python-multipart pandas scikit-learn
|
| 13 |
+
|
| 14 |
+
# Copy the FastAPI app files to the container
|
| 15 |
+
COPY . /app
|
| 16 |
+
|
| 17 |
+
# Expose port 7860
|
| 18 |
+
EXPOSE 7860
|
| 19 |
+
|
| 20 |
+
# Command to run the FastAPI app using Uvicorn
|
| 21 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
__pycache__/auth.cpython-312.pyc
ADDED
|
Binary file (1.62 kB). View file
|
|
|
__pycache__/crud.cpython-312.pyc
ADDED
|
Binary file (5.77 kB). View file
|
|
|
__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (1.22 kB). View file
|
|
|
__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (13.1 kB). View file
|
|
|
__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (1.07 kB). View file
|
|
|
auth.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import Depends, HTTPException, Request, status
|
| 2 |
+
from werkzeug.security import generate_password_hash, check_password_hash
|
| 3 |
+
import crud, database
|
| 4 |
+
|
| 5 |
+
# Password utils
|
| 6 |
+
def get_password_hash(pw): return generate_password_hash(pw)
|
| 7 |
+
def verify_password(pw, h): return check_password_hash(h, pw)
|
| 8 |
+
|
| 9 |
+
# Dependency to get current user
|
| 10 |
+
async def get_current_user(request: Request):
|
| 11 |
+
uid = request.session.get('user_id')
|
| 12 |
+
if not uid:
|
| 13 |
+
raise HTTPException(status_code=status.HTTP_302_FOUND, headers={'Location':'/user/login'})
|
| 14 |
+
user = crud.get_user_by_id(uid)
|
| 15 |
+
if not user:
|
| 16 |
+
raise HTTPException(status_code=status.HTTP_302_FOUND, headers={'Location':'/user/login'})
|
| 17 |
+
return user
|
| 18 |
+
|
| 19 |
+
# Admin dependency
|
| 20 |
+
async def get_current_admin(request: Request):
|
| 21 |
+
if not request.session.get('is_admin'):
|
| 22 |
+
raise HTTPException(status_code=status.HTTP_302_FOUND, headers={'Location':'/admin/login'})
|
| 23 |
+
return True
|
crud.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import database, hashlib, json
|
| 2 |
+
from auth import get_password_hash, verify_password
|
| 3 |
+
import models
|
| 4 |
+
|
| 5 |
+
# Users
|
| 6 |
+
|
| 7 |
+
def get_user_by_id(user_id):
|
| 8 |
+
with database.get_conn() as c:
|
| 9 |
+
r = c.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
|
| 10 |
+
return r and models.User(id=r['id'], email=r['email'], name=r['name'])
|
| 11 |
+
|
| 12 |
+
def create_user(email, name, password):
|
| 13 |
+
hashed = get_password_hash(password)
|
| 14 |
+
with database.get_conn() as c:
|
| 15 |
+
c.execute("INSERT INTO users (email,name,hashed_password) VALUES (?,?,?)",
|
| 16 |
+
(email,name,hashed))
|
| 17 |
+
|
| 18 |
+
def get_user_by_email(email):
|
| 19 |
+
with database.get_conn() as c:
|
| 20 |
+
r = c.execute("SELECT * FROM users WHERE email=?", (email,)).fetchone()
|
| 21 |
+
return r and models.User(id=r['id'], email=r['email'], name=r['name'])
|
| 22 |
+
|
| 23 |
+
def get_user_by_id(user_id):
|
| 24 |
+
with database.get_conn() as c:
|
| 25 |
+
r = c.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone()
|
| 26 |
+
return r and models.User(id=r['id'], email=r['email'], name=r['name'])
|
| 27 |
+
|
| 28 |
+
def authenticate_user(email, password):
|
| 29 |
+
user = get_user_by_email(email)
|
| 30 |
+
if not user: return None
|
| 31 |
+
with database.get_conn() as c:
|
| 32 |
+
r = c.execute("SELECT hashed_password FROM users WHERE email=?", (email,)).fetchone()
|
| 33 |
+
if not verify_password(password, r['hashed_password']): return None
|
| 34 |
+
return user
|
| 35 |
+
|
| 36 |
+
def list_users():
|
| 37 |
+
with database.get_conn() as c:
|
| 38 |
+
rows = c.execute("SELECT * FROM users").fetchall()
|
| 39 |
+
return [models.User(id=r['id'], email=r['email'], name=r['name']) for r in rows]
|
| 40 |
+
|
| 41 |
+
def update_user(user_id, name, email):
|
| 42 |
+
with database.get_conn() as c:
|
| 43 |
+
c.execute("UPDATE users SET name=?,email=? WHERE id=?", (name,email,user_id))
|
| 44 |
+
|
| 45 |
+
def delete_user(user_id):
|
| 46 |
+
with database.get_conn() as c:
|
| 47 |
+
c.execute("DELETE FROM users WHERE id=?", (user_id,))
|
| 48 |
+
|
| 49 |
+
# Predictions
|
| 50 |
+
|
| 51 |
+
def save_prediction(user_id, pred, acc, algo, input_dict):
|
| 52 |
+
j = json.dumps(input_dict)
|
| 53 |
+
with database.get_conn() as c:
|
| 54 |
+
cur = c.execute(
|
| 55 |
+
"INSERT INTO predictions (user_id,predicted,accuracy,algorithm,input_json) VALUES (?,?,?,?,?)",
|
| 56 |
+
(user_id,pred,acc,algo,j)
|
| 57 |
+
)
|
| 58 |
+
return cur.lastrowid
|
| 59 |
+
|
| 60 |
+
def get_predictions(user_id):
|
| 61 |
+
with database.get_conn() as c:
|
| 62 |
+
rows = c.execute("SELECT * FROM predictions WHERE user_id=? ORDER BY id", (user_id,)).fetchall()
|
| 63 |
+
return [models.Prediction(id=r['id'], predicted=r['predicted'], accuracy=r['accuracy'], algorithm=r['algorithm'], input_json=json.loads(r['input_json']), timestamp=r['timestamp']) for r in rows]
|
| 64 |
+
|
| 65 |
+
def delete_prediction(pred_id, user_id):
|
| 66 |
+
with database.get_conn() as c:
|
| 67 |
+
c.execute("DELETE FROM predictions WHERE id=? AND user_id=?", (pred_id,user_id))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
|
database.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
DB_PATH = Path(__file__).parent / "users.db"
|
| 5 |
+
|
| 6 |
+
def get_conn():
|
| 7 |
+
conn = sqlite3.connect(DB_PATH)
|
| 8 |
+
conn.row_factory = sqlite3.Row
|
| 9 |
+
return conn
|
| 10 |
+
|
| 11 |
+
# on import, ensure tables exist
|
| 12 |
+
with get_conn() as c:
|
| 13 |
+
c.execute("""
|
| 14 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 15 |
+
id INTEGER PRIMARY KEY,
|
| 16 |
+
email TEXT UNIQUE,
|
| 17 |
+
name TEXT,
|
| 18 |
+
hashed_password TEXT
|
| 19 |
+
)""")
|
| 20 |
+
c.execute("""
|
| 21 |
+
CREATE TABLE IF NOT EXISTS predictions (
|
| 22 |
+
id INTEGER PRIMARY KEY,
|
| 23 |
+
user_id INTEGER,
|
| 24 |
+
predicted REAL,
|
| 25 |
+
accuracy REAL,
|
| 26 |
+
algorithm TEXT,
|
| 27 |
+
input_json TEXT,
|
| 28 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
| 29 |
+
FOREIGN KEY(user_id) REFERENCES users(id)
|
| 30 |
+
)""")
|
main.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, Depends, Form, HTTPException, status
|
| 2 |
+
from fastapi.responses import HTMLResponse, RedirectResponse
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from starlette.middleware.sessions import SessionMiddleware
|
| 6 |
+
import pickle, os
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
import database, crud, models, auth
|
| 10 |
+
|
| 11 |
+
# Config
|
| 12 |
+
SECRET_KEY = "your-very-secret-key"
|
| 13 |
+
MODEL_FILE = 'rf_cgpa_model.pkl'
|
| 14 |
+
SCALER_FILE = 'scaler.pkl'
|
| 15 |
+
|
| 16 |
+
# App init
|
| 17 |
+
app = FastAPI()
|
| 18 |
+
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
|
| 19 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 20 |
+
templates = Jinja2Templates(directory="templates")
|
| 21 |
+
|
| 22 |
+
# Load ML artifacts
|
| 23 |
+
if not os.path.exists(MODEL_FILE) or not os.path.exists(SCALER_FILE):
|
| 24 |
+
raise RuntimeError("Model or scaler pickle missing.")
|
| 25 |
+
with open(MODEL_FILE,'rb') as f: model = pickle.load(f)
|
| 26 |
+
with open(SCALER_FILE,'rb') as f: scaler = pickle.load(f)
|
| 27 |
+
|
| 28 |
+
# Home -> redirect to login
|
| 29 |
+
@app.get("/", response_class=HTMLResponse)
|
| 30 |
+
async def root():
|
| 31 |
+
return RedirectResponse("/user/login")
|
| 32 |
+
|
| 33 |
+
# -------- User routes --------
|
| 34 |
+
@app.get("/user/signup", response_class=HTMLResponse)
|
| 35 |
+
async def user_signup_form(request: Request):
|
| 36 |
+
return templates.TemplateResponse("signup.html", {"request":request})
|
| 37 |
+
|
| 38 |
+
# -------- User routes --------
|
| 39 |
+
@app.get("/user/signup", response_class=HTMLResponse)
|
| 40 |
+
async def user_signup_form(request: Request):
|
| 41 |
+
return templates.TemplateResponse("signup.html", {"request":request})
|
| 42 |
+
|
| 43 |
+
@app.post("/user/signup")
|
| 44 |
+
async def user_signup(request: Request,
|
| 45 |
+
email: str = Form(...), name: str = Form(...), password: str = Form(...)):
|
| 46 |
+
exists = crud.get_user_by_email(email)
|
| 47 |
+
if exists:
|
| 48 |
+
return templates.TemplateResponse("signup.html", {"request":request, "error":"Email already registered."})
|
| 49 |
+
crud.create_user(email, name, password)
|
| 50 |
+
return RedirectResponse("/user/login", status_code=302)
|
| 51 |
+
|
| 52 |
+
@app.get("/user/login", response_class=HTMLResponse)
|
| 53 |
+
async def user_login_form(request: Request):
|
| 54 |
+
return templates.TemplateResponse("login.html", {"request":request, "role":"user"})
|
| 55 |
+
|
| 56 |
+
@app.post("/user/login")
|
| 57 |
+
async def user_login(request: Request,
|
| 58 |
+
email: str = Form(...), password: str = Form(...)):
|
| 59 |
+
user = crud.authenticate_user(email, password)
|
| 60 |
+
if not user:
|
| 61 |
+
return templates.TemplateResponse("login.html", {"request":request, "error":"Invalid credentials.", "role":"user"})
|
| 62 |
+
request.session.update({"user_id":user.id, "email":user.email, "is_admin":False})
|
| 63 |
+
return RedirectResponse("/user/dashboard", status_code=302)
|
| 64 |
+
|
| 65 |
+
@app.post("/user/logout")
|
| 66 |
+
async def user_logout(request: Request):
|
| 67 |
+
request.session.clear() # Clear the session
|
| 68 |
+
return RedirectResponse("/user/login", status_code=302)
|
| 69 |
+
|
| 70 |
+
@app.get("/user/dashboard", response_class=HTMLResponse)
|
| 71 |
+
async def user_dashboard(request: Request, current=Depends(auth.get_current_user)):
|
| 72 |
+
preds = crud.get_predictions(current.id)
|
| 73 |
+
if not preds:
|
| 74 |
+
return RedirectResponse("/user/predict", status_code=302)
|
| 75 |
+
last = preds[-1]
|
| 76 |
+
return templates.TemplateResponse("dashboard.html", {"request":request, "pred":last})
|
| 77 |
+
|
| 78 |
+
@app.get("/user/predict", response_class=HTMLResponse)
|
| 79 |
+
async def predict_form(request: Request, current=Depends(auth.get_current_user)):
|
| 80 |
+
return templates.TemplateResponse("predict.html", {"request":request})
|
| 81 |
+
|
| 82 |
+
@app.post("/user/predict", response_class=HTMLResponse)
|
| 83 |
+
async def predict_submit(request: Request,
|
| 84 |
+
current=Depends(auth.get_current_user),
|
| 85 |
+
Age: int = Form(...), Gender: str = Form(...), HoursOfStudyPerDay: float = Form(...),
|
| 86 |
+
SchoolAttendanceRate: float = Form(...), TuitionAccess: str = Form(...),
|
| 87 |
+
AveragePreviousScores: float = Form(...), ParticipatesInClubs: str = Form(...),
|
| 88 |
+
HoursOfSleep: float = Form(...), BreakfastDaily: str = Form(...),
|
| 89 |
+
ScreenTimeHours: float = Form(...), PhysicalActivityHours: float = Form(...),
|
| 90 |
+
PlaysSport: str = Form(...), MentalHealthScore: int = Form(...),
|
| 91 |
+
StudyEnvironmentRating: int = Form(...), FriendSupportScore: int = Form(...),
|
| 92 |
+
ParentalEducationLevel: str = Form(...), HouseholdIncomeLevel: str = Form(...),
|
| 93 |
+
PartTimeWork: str = Form(...)
|
| 94 |
+
):
|
| 95 |
+
gen = {"Female":[1,0,0],"Male":[0,1,0],"Other":[0,0,1]}[Gender]
|
| 96 |
+
yn = lambda v:1 if v=="Yes" else 0
|
| 97 |
+
pedigree={"High school":0,"Graduate":1,"Postgrad":2}
|
| 98 |
+
income={"Low":0,"Medium":1,"High":2}
|
| 99 |
+
payload={
|
| 100 |
+
"Age":Age,
|
| 101 |
+
"Gender_Female":gen[0],"Gender_Male":gen[1],"Gender_Other":gen[2],
|
| 102 |
+
"HoursOfStudyPerDay":HoursOfStudyPerDay,
|
| 103 |
+
"SchoolAttendanceRate":SchoolAttendanceRate,
|
| 104 |
+
"TuitionAccess":yn(TuitionAccess),
|
| 105 |
+
"AveragePreviousScores":AveragePreviousScores,
|
| 106 |
+
"ParticipatesInClubs":yn(ParticipatesInClubs),
|
| 107 |
+
"HoursOfSleep":HoursOfSleep,
|
| 108 |
+
"BreakfastDaily":yn(BreakfastDaily),
|
| 109 |
+
"ScreenTimeHours":ScreenTimeHours,
|
| 110 |
+
"PhysicalActivityHours":PhysicalActivityHours,
|
| 111 |
+
"PlaysSport":yn(PlaysSport),
|
| 112 |
+
"MentalHealthScore":MentalHealthScore,
|
| 113 |
+
"StudyEnvironmentRating":StudyEnvironmentRating,
|
| 114 |
+
"FriendSupportScore":FriendSupportScore,
|
| 115 |
+
"ParentalEducationLevel_Encoded":pedigree[ParentalEducationLevel],
|
| 116 |
+
"HouseholdIncomeLevel_Encoded":income[HouseholdIncomeLevel],
|
| 117 |
+
"PartTimeWork":yn(PartTimeWork)
|
| 118 |
+
}
|
| 119 |
+
if sum([HoursOfStudyPerDay,HoursOfSleep,ScreenTimeHours,PhysicalActivityHours])>24:
|
| 120 |
+
return templates.TemplateResponse("predict.html", {"request":request, "error":"Total hours exceed 24."})
|
| 121 |
+
df = pd.DataFrame([payload])[scaler.feature_names_in_]
|
| 122 |
+
X = scaler.transform(df)
|
| 123 |
+
pred = model.predict(X)[0]
|
| 124 |
+
acc = getattr(model, 'accuracy_', 0.0)
|
| 125 |
+
algo = model.__class__.__name__
|
| 126 |
+
crud.save_prediction(current.id, float(pred), float(acc), algo, payload)
|
| 127 |
+
return RedirectResponse("/user/dashboard", status_code=302)
|
| 128 |
+
|
| 129 |
+
@app.post("/user/delete")
|
| 130 |
+
async def user_delete(request: Request, pred_id: int = Form(...), current=Depends(auth.get_current_user)):
|
| 131 |
+
crud.delete_prediction(pred_id, current.id)
|
| 132 |
+
return RedirectResponse("/user/predict", status_code=302)
|
| 133 |
+
|
| 134 |
+
# -------- Admin routes --------
|
| 135 |
+
@app.get("/admin/login", response_class=HTMLResponse)
|
| 136 |
+
async def admin_login_form(request: Request):
|
| 137 |
+
return templates.TemplateResponse("login.html", {"request":request, "role":"admin"})
|
| 138 |
+
|
| 139 |
+
@app.post("/admin/login")
|
| 140 |
+
async def admin_login(request: Request,
|
| 141 |
+
email: str = Form(...), password: str = Form(...)):
|
| 142 |
+
if email!="admin@gmail.com" or password!="Admin@149":
|
| 143 |
+
return templates.TemplateResponse("login.html", {"request":request, "error":"Invalid.", "role":"admin"})
|
| 144 |
+
request.session.update({"is_admin":True})
|
| 145 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 146 |
+
@app.post("/admin/logout")
|
| 147 |
+
async def admin_logout(request: Request):
|
| 148 |
+
request.session.clear() # Clear the session
|
| 149 |
+
return RedirectResponse("/admin/login", status_code=302)
|
| 150 |
+
|
| 151 |
+
@app.get("/admin/dashboard", response_class=HTMLResponse)
|
| 152 |
+
async def admin_dashboard(request: Request, admin=Depends(auth.get_current_admin)):
|
| 153 |
+
users = crud.list_users()
|
| 154 |
+
users_with_last = []
|
| 155 |
+
for u in users:
|
| 156 |
+
preds = crud.get_predictions(u.id)
|
| 157 |
+
last = preds[-1] if preds else None
|
| 158 |
+
users_with_last.append({
|
| 159 |
+
"id": u.id,
|
| 160 |
+
"name": u.name,
|
| 161 |
+
"email": u.email,
|
| 162 |
+
"last_pred": last.predicted if last else None,
|
| 163 |
+
"last_date": last.timestamp.split(" ")[0] if last else None
|
| 164 |
+
})
|
| 165 |
+
return templates.TemplateResponse("admin_dashboard.html", {
|
| 166 |
+
"request": request,
|
| 167 |
+
"users": users_with_last,
|
| 168 |
+
"total_users": len(users_with_last),
|
| 169 |
+
"crud" : crud
|
| 170 |
+
})
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@app.post("/admin/user/delete")
|
| 174 |
+
async def admin_delete_user(request: Request, user_id: int = Form(...), admin=Depends(auth.get_current_admin)):
|
| 175 |
+
crud.delete_user(user_id)
|
| 176 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 177 |
+
|
| 178 |
+
@app.post("/admin/user/update")
|
| 179 |
+
async def admin_update_user(request: Request,
|
| 180 |
+
user_id: int = Form(...), name: str = Form(...), email: str = Form(...), admin=Depends(auth.get_current_admin)):
|
| 181 |
+
crud.update_user(user_id, name, email)
|
| 182 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
if __name__ == "__main__":
|
| 189 |
+
import uvicorn
|
| 190 |
+
uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)
|
models.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from typing import Optional, Dict
|
| 3 |
+
|
| 4 |
+
class UserCreate(BaseModel):
|
| 5 |
+
email: str
|
| 6 |
+
name: str
|
| 7 |
+
password: str
|
| 8 |
+
|
| 9 |
+
class User(BaseModel):
|
| 10 |
+
id: int
|
| 11 |
+
email: str
|
| 12 |
+
name: str
|
| 13 |
+
|
| 14 |
+
class Prediction(BaseModel):
|
| 15 |
+
id: int
|
| 16 |
+
predicted: float
|
| 17 |
+
accuracy: float
|
| 18 |
+
algorithm: str
|
| 19 |
+
input_json: Dict
|
| 20 |
+
timestamp: str
|
rf_cgpa_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bd6b721f592dfb6a0eaaef13b650aa468088540a2d4d4e72f134619cdde6abb1
|
| 3 |
+
size 1311997
|
scaler.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:657d2447cb7a85815958258415e8019653c5fab248608f1ee25c54257fbc8db0
|
| 3 |
+
size 1406
|
static/js/hours_check.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fields = ['HoursOfStudyPerDay','HoursOfSleep','ScreenTimeHours','PhysicalActivityHours']
|
| 2 |
+
.map(id => document.querySelector(`[name="${id}"]`));
|
| 3 |
+
const warning = document.getElementById('hoursWarning');
|
| 4 |
+
fields.forEach(f => f && f.addEventListener('input', () => {
|
| 5 |
+
const sum = fields.reduce((a,el) => a + parseFloat(el.value||0), 0);
|
| 6 |
+
warning?.classList.toggle('hidden', sum <= 24);
|
| 7 |
+
}));
|
templates/admin_dashboard.html
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}Admin Dashboard{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="bg-white p-6 rounded shadow">
|
| 5 |
+
<h2 class="text-2xl font-bold mb-4">Admin Panel</h2>
|
| 6 |
+
<p class="mb-4"><strong>Total Users:</strong> {{ total_users }}</p>
|
| 7 |
+
<table class="w-full table-auto border-collapse">
|
| 8 |
+
<thead>
|
| 9 |
+
<tr class="bg-gray-100">
|
| 10 |
+
<th class="p-2">ID</th>
|
| 11 |
+
<th class="p-2">Name</th>
|
| 12 |
+
<th class="p-2">Email</th>
|
| 13 |
+
<th class="p-2">Last CGPA</th>
|
| 14 |
+
<th class="p-2">Actions</th>
|
| 15 |
+
</tr>
|
| 16 |
+
</thead>
|
| 17 |
+
<tbody>
|
| 18 |
+
{% for u in users %}
|
| 19 |
+
{% set last = u.id | tojson | int and (crud.get_predictions(u.id)|last) %}
|
| 20 |
+
<tr class="hover:bg-gray-50">
|
| 21 |
+
<td class="p-2">{{ u.id }}</td>
|
| 22 |
+
<td class="p-2">{{ u.name }}</td>
|
| 23 |
+
<td class="p-2">{{ u.email }}</td>
|
| 24 |
+
<td class="p-2 text-center">
|
| 25 |
+
{% if last %}
|
| 26 |
+
<span class="px-2 py-1 rounded
|
| 27 |
+
{% if last.predicted < 6 %}bg-red-200 text-red-800{% else %}bg-green-200 text-green-800{% endif %}">
|
| 28 |
+
{{ last.predicted | round(2) }}
|
| 29 |
+
</span>
|
| 30 |
+
{% else %}
|
| 31 |
+
—
|
| 32 |
+
{% endif %}
|
| 33 |
+
</td>
|
| 34 |
+
<td class="p-2 space-x-2">
|
| 35 |
+
<form class="inline" method="post" action="/admin/user/delete">
|
| 36 |
+
<input type="hidden" name="user_id" value="{{ u.id }}">
|
| 37 |
+
<button class="px-2 py-1 bg-red-600 text-white rounded hover:bg-red-700">
|
| 38 |
+
Delete
|
| 39 |
+
</button>
|
| 40 |
+
</form>
|
| 41 |
+
</td>
|
| 42 |
+
</tr>
|
| 43 |
+
{% endfor %}
|
| 44 |
+
</tbody>
|
| 45 |
+
</table>
|
| 46 |
+
</div>
|
| 47 |
+
{% endblock %}
|
templates/admin_user_detail.html
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}User Details{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="max-w-4xl mx-auto bg-white p-8 rounded shadow">
|
| 5 |
+
<h2 class="text-2xl font-bold mb-4">User: {{ user.name }} (ID {{ user.id }})</h2>
|
| 6 |
+
<p class="mb-6"><strong>Email:</strong> {{ user.email }}</p>
|
| 7 |
+
|
| 8 |
+
<!-- Form Data Section -->
|
| 9 |
+
<h3 class="text-xl font-semibold mb-4">Form Data</h3>
|
| 10 |
+
<div class="grid grid-cols-2 gap-4 bg-gray-50 p-4 rounded mb-6">
|
| 11 |
+
{% for key, value in form_data.items() %}
|
| 12 |
+
<div>
|
| 13 |
+
<span class="font-semibold">{{ key | replace('_', ' ') | title }}:</span>
|
| 14 |
+
{{ value }}
|
| 15 |
+
</div>
|
| 16 |
+
{% endfor %}
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<!-- Prediction History Section -->
|
| 20 |
+
<h3 class="text-xl font-semibold mb-2">Prediction History</h3>
|
| 21 |
+
<table class="w-full table-auto mb-6">
|
| 22 |
+
<thead>
|
| 23 |
+
<tr class="bg-gray-100">
|
| 24 |
+
<th class="p-2">#</th>
|
| 25 |
+
<th class="p-2">CGPA</th>
|
| 26 |
+
<th class="p-2">Algorithm</th>
|
| 27 |
+
<th class="p-2">Date</th>
|
| 28 |
+
</tr>
|
| 29 |
+
</thead>
|
| 30 |
+
<tbody>
|
| 31 |
+
{% for p in predictions %}
|
| 32 |
+
<tr class="hover:bg-gray-50">
|
| 33 |
+
<td class="p-2">{{ p.id }}</td>
|
| 34 |
+
<td class="p-2">{{ p.predicted | round(2) }}</td>
|
| 35 |
+
<td class="p-2">{{ p.algorithm }}</td>
|
| 36 |
+
<td class="p-2">{{ p.timestamp.split(' ')[0] }}</td>
|
| 37 |
+
</tr>
|
| 38 |
+
{% endfor %}
|
| 39 |
+
</tbody>
|
| 40 |
+
</table>
|
| 41 |
+
|
| 42 |
+
<a href="/admin/dashboard" class="py-2 px-4 bg-gray-600 text-white rounded hover:bg-gray-700">
|
| 43 |
+
← Back to Dashboard
|
| 44 |
+
</a>
|
| 45 |
+
</div>
|
| 46 |
+
{% endblock %}
|
templates/base.html
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 6 |
+
<title>{% block title %}CGPA App{% endblock %}</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body class="bg-gray-100 min-h-screen">
|
| 9 |
+
<nav class="bg-blue-600 p-4 text-white">
|
| 10 |
+
<div class="container mx-auto flex justify-between">
|
| 11 |
+
<a href="/" class="font-bold">CGPA App</a>
|
| 12 |
+
<div>
|
| 13 |
+
{% if request.session.is_admin %}
|
| 14 |
+
<a href="/admin/dashboard" class="mr-4">Admin Dashboard</a>
|
| 15 |
+
<form action="/admin/logout" method="post" style="display:inline;">
|
| 16 |
+
<button type="submit" class="bg-red-500 text-white p-2 rounded">Logout</button>
|
| 17 |
+
</form>
|
| 18 |
+
{% elif request.session.user_id %}
|
| 19 |
+
<a href="/user/dashboard" class="mr-4">Dashboard</a>
|
| 20 |
+
<form action="/user/logout" method="post" style="display:inline;">
|
| 21 |
+
<button type="submit" class="bg-red-500 text-white p-2 rounded">Logout</button>
|
| 22 |
+
</form>
|
| 23 |
+
{% endif %}
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
</nav>
|
| 27 |
+
<main class="container mx-auto py-6">
|
| 28 |
+
{% block content %}{% endblock %}
|
| 29 |
+
</main>
|
| 30 |
+
</body>
|
| 31 |
+
</html>
|
templates/dashboard.html
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}Dashboard{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="max-w-2xl mx-auto bg-white p-8 rounded-xl shadow-lg">
|
| 5 |
+
<!-- Header with greeting and delete/re‑predict -->
|
| 6 |
+
<div class="flex justify-between items-center mb-6">
|
| 7 |
+
<h2 class="text-2xl font-bold">
|
| 8 |
+
Hello, {{ request.session.get('email').split('@')[0] | capitalize }}!
|
| 9 |
+
</h2>
|
| 10 |
+
<form method="post" action="/user/delete">
|
| 11 |
+
<input type="hidden" name="pred_id" value="{{ pred.id }}">
|
| 12 |
+
<button class="py-2 px-4 bg-red-600 text-white rounded hover:bg-red-700">
|
| 13 |
+
Delete & Re‑predict
|
| 14 |
+
</button>
|
| 15 |
+
</form>
|
| 16 |
+
</div>
|
| 17 |
+
|
| 18 |
+
<!-- CGPA + Model Details -->
|
| 19 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
| 20 |
+
<div class="p-4 border rounded text-center">
|
| 21 |
+
<h3 class="text-lg font-semibold mb-2">Your CGPA</h3>
|
| 22 |
+
<p class="text-5xl font-extrabold text-blue-600">{{ pred.predicted | round(2) }}</p>
|
| 23 |
+
</div>
|
| 24 |
+
<div class="p-4 border rounded">
|
| 25 |
+
<h3 class="text-lg font-semibold mb-2">Model Details</h3>
|
| 26 |
+
<p><strong>Algorithm:</strong> {{ pred.algorithm }}</p>
|
| 27 |
+
<p><strong>Date:</strong> {{ pred.timestamp.split(' ')[0] }}</p>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<!-- Input Summary Tiles -->
|
| 32 |
+
<div class="mb-6">
|
| 33 |
+
<h3 class="text-lg font-semibold mb-2">What You Entered</h3>
|
| 34 |
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 35 |
+
{% for key, value in pred.input_json.items() %}
|
| 36 |
+
<div class="p-3 bg-gray-50 rounded shadow-sm">
|
| 37 |
+
<span class="font-medium">{{ key.replace('_', ' ') | capitalize }}:</span>
|
| 38 |
+
<span>{{ value }}</span>
|
| 39 |
+
</div>
|
| 40 |
+
{% endfor %}
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
</div>
|
| 46 |
+
{% endblock %}
|
templates/login.html
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}{{ role|capitalize }} Login{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="max-w-md mx-auto bg-white p-6 rounded shadow">
|
| 5 |
+
<h2 class="text-xl font-bold mb-4">{{ role|capitalize }} Login</h2>
|
| 6 |
+
{% if error %}<div class="mb-4 text-red-600">{{ error }}</div>{% endif %}
|
| 7 |
+
<form method="post" class="space-y-4">
|
| 8 |
+
<div><label>Email</label><input name="email" type="email" required class="w-full p-2 border rounded"></div>
|
| 9 |
+
<div><label>Password</label><input name="password" type="password" required class="w-full p-2 border rounded"></div>
|
| 10 |
+
<button type="submit" class="w-full py-2 bg-blue-600 text-white rounded">Login</button>
|
| 11 |
+
</form>
|
| 12 |
+
{% if role=='user' %}
|
| 13 |
+
<p class="mt-4">Don't have an account? <a href="/user/signup" class="text-blue-600">Sign Up</a></p>
|
| 14 |
+
{% endif %}
|
| 15 |
+
</div>
|
| 16 |
+
{% endblock %}
|
templates/predict.html
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}Predict CGPA{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="max-w-xl mx-auto bg-white p-6 rounded shadow">
|
| 5 |
+
<h1 class="text-2xl font-bold mb-4">Student CGPA Predictor</h1>
|
| 6 |
+
|
| 7 |
+
{% if error %}
|
| 8 |
+
<div class="mb-4 p-3 bg-red-100 text-red-700 rounded">{{ error }}</div>
|
| 9 |
+
{% endif %}
|
| 10 |
+
|
| 11 |
+
<form id="predictForm" method="post" class="space-y-4">
|
| 12 |
+
<!-- Age -->
|
| 13 |
+
<div>
|
| 14 |
+
<label class="block font-medium">Age (18–29)</label>
|
| 15 |
+
<input name="Age" type="number" min="18" max="29" required
|
| 16 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<!-- Gender -->
|
| 20 |
+
<div>
|
| 21 |
+
<span class="block font-medium">Gender</span>
|
| 22 |
+
<label><input type="radio" name="Gender" value="Female" required/> Female</label>
|
| 23 |
+
<label class="ml-4"><input type="radio" name="Gender" value="Male"/> Male</label>
|
| 24 |
+
<label class="ml-4"><input type="radio" name="Gender" value="Other"/> Other</label>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
<!-- Hours of Study -->
|
| 28 |
+
<div>
|
| 29 |
+
<label class="block font-medium">Study Hours/Day (0–24)</label>
|
| 30 |
+
<input name="HoursOfStudyPerDay" type="number" step="0.1" min="0" max="24" required
|
| 31 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<!-- Attendance -->
|
| 35 |
+
<div>
|
| 36 |
+
<label class="block font-medium">Attendance Rate (%) (0–100)</label>
|
| 37 |
+
<input name="SchoolAttendanceRate" type="number" step="0.1" min="0" max="100" required
|
| 38 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<!-- Tuition Access -->
|
| 42 |
+
<div>
|
| 43 |
+
<label class="block font-medium">Tuition Access</label>
|
| 44 |
+
<select name="TuitionAccess" required class="w-full mt-1 p-2 border rounded">
|
| 45 |
+
<option value="">Select…</option>
|
| 46 |
+
<option>Yes</option>
|
| 47 |
+
<option>No</option>
|
| 48 |
+
</select>
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<!-- Previous Scores -->
|
| 52 |
+
<div>
|
| 53 |
+
<label class="block font-medium">Average Previous Score (0–100)</label>
|
| 54 |
+
<input name="AveragePreviousScores" type="number" step="0.1" min="0" max="100" required
|
| 55 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<!-- Clubs -->
|
| 59 |
+
<div>
|
| 60 |
+
<label class="block font-medium">Participates in Clubs</label>
|
| 61 |
+
<select name="ParticipatesInClubs" required class="w-full mt-1 p-2 border rounded">
|
| 62 |
+
<option value="">Select…</option>
|
| 63 |
+
<option>Yes</option>
|
| 64 |
+
<option>No</option>
|
| 65 |
+
</select>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<!-- Sleep Hours -->
|
| 69 |
+
<div>
|
| 70 |
+
<label class="block font-medium">Sleep Hours/Night (0–24)</label>
|
| 71 |
+
<input name="HoursOfSleep" type="number" step="0.1" min="0" max="24" required
|
| 72 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 73 |
+
</div>
|
| 74 |
+
|
| 75 |
+
<!-- Breakfast -->
|
| 76 |
+
<div>
|
| 77 |
+
<label class="block font-medium">Eat Breakfast Daily?</label>
|
| 78 |
+
<select name="BreakfastDaily" required class="w-full mt-1 p-2 border rounded">
|
| 79 |
+
<option value="">Select…</option>
|
| 80 |
+
<option>Yes</option>
|
| 81 |
+
<option>No</option>
|
| 82 |
+
</select>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<!-- Screen Time -->
|
| 86 |
+
<div>
|
| 87 |
+
<label class="block font-medium">Leisure Screen Time (0–24h)</label>
|
| 88 |
+
<input name="ScreenTimeHours" type="number" step="0.1" min="0" max="24" required
|
| 89 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
<!-- Physical Activity -->
|
| 93 |
+
<div>
|
| 94 |
+
<label class="block font-medium">Physical Activity Hours (0–24)</label>
|
| 95 |
+
<input name="PhysicalActivityHours" type="number" step="0.1" min="0" max="24" required
|
| 96 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<!-- Plays Sport -->
|
| 100 |
+
<div>
|
| 101 |
+
<label class="block font-medium">Plays Sport</label>
|
| 102 |
+
<select name="PlaysSport" required class="w-full mt-1 p-2 border rounded">
|
| 103 |
+
<option value="">Select…</option>
|
| 104 |
+
<option>Yes</option>
|
| 105 |
+
<option>No</option>
|
| 106 |
+
</select>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<!-- Mental Health -->
|
| 110 |
+
<div>
|
| 111 |
+
<label class="block font-medium">Mental Health Score (0–10)</label>
|
| 112 |
+
<input name="MentalHealthScore" type="number" min="0" max="10" required
|
| 113 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<!-- Study Environment -->
|
| 117 |
+
<div>
|
| 118 |
+
<label class="block font-medium">Study Environment Rating (1–5)</label>
|
| 119 |
+
<input name="StudyEnvironmentRating" type="number" min="1" max="5" required
|
| 120 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<!-- Friend Support -->
|
| 124 |
+
<div>
|
| 125 |
+
<label class="block font-medium">Friend Support Score (0–10)</label>
|
| 126 |
+
<input name="FriendSupportScore" type="number" min="0" max="10" required
|
| 127 |
+
class="w-full mt-1 p-2 border rounded"/>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<!-- Parental Education -->
|
| 131 |
+
<div>
|
| 132 |
+
<label class="block font-medium">Parental Education Level</label>
|
| 133 |
+
<select name="ParentalEducationLevel" required class="w-full mt-1 p-2 border rounded">
|
| 134 |
+
<option value="">Select…</option>
|
| 135 |
+
<option>High school</option>
|
| 136 |
+
<option>Graduate</option>
|
| 137 |
+
<option>Postgrad</option>
|
| 138 |
+
</select>
|
| 139 |
+
</div>
|
| 140 |
+
|
| 141 |
+
<!-- Household Income -->
|
| 142 |
+
<div>
|
| 143 |
+
<label class="block font-medium">Household Income Level</label>
|
| 144 |
+
<select name="HouseholdIncomeLevel" required class="w-full mt-1 p-2 border rounded">
|
| 145 |
+
<option value="">Select…</option>
|
| 146 |
+
<option>Low</option>
|
| 147 |
+
<option>Medium</option>
|
| 148 |
+
<option>High</option>
|
| 149 |
+
</select>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
<!-- Part‑time Work -->
|
| 153 |
+
<div>
|
| 154 |
+
<label class="block font-medium">Part‑Time Work</label>
|
| 155 |
+
<select name="PartTimeWork" required class="w-full mt-1 p-2 border rounded">
|
| 156 |
+
<option value="">Select…</option>
|
| 157 |
+
<option>Yes</option>
|
| 158 |
+
<option>No</option>
|
| 159 |
+
</select>
|
| 160 |
+
</div>
|
| 161 |
+
|
| 162 |
+
<!-- Cross‑field Hours Warning -->
|
| 163 |
+
<p id="hoursWarning" class="text-red-600 hidden">Total hours > 24!</p>
|
| 164 |
+
|
| 165 |
+
<button type="submit"
|
| 166 |
+
class="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700">
|
| 167 |
+
Predict CGPA
|
| 168 |
+
</button>
|
| 169 |
+
|
| 170 |
+
</form>
|
| 171 |
+
</div>
|
| 172 |
+
<script src="/static/js/hours_check.js"></script>
|
| 173 |
+
{% endblock %}
|
templates/signup.html
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "base.html" %}
|
| 2 |
+
{% block title %}Sign Up{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="max-w-md mx-auto bg-white p-6 rounded shadow">
|
| 5 |
+
<h2 class="text-xl font-bold mb-4">User Sign Up</h2>
|
| 6 |
+
{% if error %}<div class="mb-4 text-red-600">{{ error }}</div>{% endif %}
|
| 7 |
+
<form method="post" class="space-y-4">
|
| 8 |
+
<div><label>Email</label><input name="email" type="email" required class="w-full p-2 border rounded"></div>
|
| 9 |
+
<div><label>Name</label><input name="name" type="text" required class="w-full p-2 border rounded"></div>
|
| 10 |
+
<div><label>Password</label><input name="password" type="password" required class="w-full p-2 border rounded"></div>
|
| 11 |
+
<button type="submit" class="w-full py-2 bg-blue-600 text-white rounded">Sign Up</button>
|
| 12 |
+
</form>
|
| 13 |
+
<p class="mt-4">Already have an account? <a href="/user/login" class="text-blue-600">Login</a></p>
|
| 14 |
+
</div>
|
| 15 |
+
{% endblock %}
|
users.db
ADDED
|
Binary file (16.4 kB). View file
|
|
|