triflix commited on
Commit
9173642
·
verified ·
1 Parent(s): 1116f14

Upload 22 files

Browse files
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 &amp; 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