Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- ai_chatbot.py +100 -0
- app_hf.py +240 -0
- database_recommender.py +293 -0
- requirements.txt +7 -0
ai_chatbot.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sentence_transformers import SentenceTransformer
|
| 2 |
+
import numpy as np
|
| 3 |
+
from typing import List, Dict, Tuple
|
| 4 |
+
import re
|
| 5 |
+
|
| 6 |
+
class AIChatbot:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
# Load the pre-trained model (can use a smaller model for more speed)
|
| 9 |
+
self.model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 10 |
+
# Warm up the model to avoid first-request slowness
|
| 11 |
+
_ = self.model.encode(["Hello, world!"])
|
| 12 |
+
self.faq_embeddings = None
|
| 13 |
+
self.faqs = None
|
| 14 |
+
self.load_faqs()
|
| 15 |
+
|
| 16 |
+
def load_faqs(self):
|
| 17 |
+
"""Disable FAQs entirely; operate as a general-conversation bot."""
|
| 18 |
+
self.faqs = []
|
| 19 |
+
self.faq_embeddings = None
|
| 20 |
+
|
| 21 |
+
def save_unanswered_question(self, question):
|
| 22 |
+
"""Log unanswered questions to console (can be extended to save to file)"""
|
| 23 |
+
print(f"Unanswered question logged: {question}")
|
| 24 |
+
# In a real implementation, you could save this to a file or send to an admin
|
| 25 |
+
|
| 26 |
+
def _tokenize(self, text: str):
|
| 27 |
+
if not text:
|
| 28 |
+
return []
|
| 29 |
+
return [t for t in re.findall(r"[a-z0-9]+", text.lower()) if len(t) > 2]
|
| 30 |
+
|
| 31 |
+
def _overlap_ratio(self, q_tokens, faq_tokens):
|
| 32 |
+
if not q_tokens or not faq_tokens:
|
| 33 |
+
return 0.0
|
| 34 |
+
q_set = set(q_tokens)
|
| 35 |
+
f_set = set(faq_tokens)
|
| 36 |
+
inter = len(q_set & f_set)
|
| 37 |
+
denom = max(len(q_set), 1)
|
| 38 |
+
return inter / denom
|
| 39 |
+
|
| 40 |
+
def _wh_class(self, text: str) -> str:
|
| 41 |
+
if not text:
|
| 42 |
+
return ''
|
| 43 |
+
s = text.strip().lower()
|
| 44 |
+
# simple heuristic classification by leading wh-word
|
| 45 |
+
for key in ['who', 'where', 'when', 'what', 'how', 'why', 'which']:
|
| 46 |
+
if s.startswith(key + ' ') or s.startswith(key + "?"):
|
| 47 |
+
return key
|
| 48 |
+
# also check presence if not leading
|
| 49 |
+
for key in ['who', 'where', 'when', 'what', 'how', 'why', 'which']:
|
| 50 |
+
if f' {key} ' in f' {s} ':
|
| 51 |
+
return key
|
| 52 |
+
return ''
|
| 53 |
+
|
| 54 |
+
def find_best_match(self, question: str, threshold: float = 0.7) -> Tuple[str, float]:
|
| 55 |
+
print(f"find_best_match called with: {question}") # Debug print
|
| 56 |
+
# Always act as a general-conversation bot
|
| 57 |
+
return self._generate_general_response(question)
|
| 58 |
+
|
| 59 |
+
def _generate_general_response(self, question: str) -> Tuple[str, float]:
|
| 60 |
+
"""Generate general conversation responses for non-FAQ questions"""
|
| 61 |
+
question_lower = question.lower().strip()
|
| 62 |
+
|
| 63 |
+
# Greeting responses
|
| 64 |
+
if any(greeting in question_lower for greeting in ['hello', 'hi', 'hey', 'good morning', 'good afternoon', 'good evening']):
|
| 65 |
+
return "Hello! I'm the PSAU AI assistant. I'm here to help you with questions about university admissions, courses, and general information about Pangasinan State University. How can I assist you today?", 0.8
|
| 66 |
+
|
| 67 |
+
# Thank you responses
|
| 68 |
+
if any(thanks in question_lower for thanks in ['thank you', 'thanks', 'thank', 'appreciate']):
|
| 69 |
+
return "You're very welcome! I'm happy to help. Is there anything else you'd like to know about PSAU or university admissions?", 0.9
|
| 70 |
+
|
| 71 |
+
# Goodbye responses
|
| 72 |
+
if any(goodbye in question_lower for goodbye in ['bye', 'goodbye', 'see you', 'farewell']):
|
| 73 |
+
return "Goodbye! It was nice chatting with you. Feel free to come back anytime if you have more questions about PSAU. Good luck with your academic journey!", 0.9
|
| 74 |
+
|
| 75 |
+
# How are you responses
|
| 76 |
+
if any(how in question_lower for how in ['how are you', 'how do you do', 'how is it going']):
|
| 77 |
+
return "I'm doing great, thank you for asking! I'm here and ready to help you with any questions about PSAU admissions, courses, or university life. What would you like to know?", 0.8
|
| 78 |
+
|
| 79 |
+
# What can you do responses
|
| 80 |
+
if any(what in question_lower for what in ['what can you do', 'what do you do', 'what are your capabilities']):
|
| 81 |
+
return "I can help you with:\n• University admission requirements and procedures\n• Course information and recommendations\n• General questions about PSAU\n• Academic guidance and support\n• Information about campus life\n\nWhat specific information are you looking for?", 0.9
|
| 82 |
+
|
| 83 |
+
# About PSAU responses
|
| 84 |
+
if any(about in question_lower for about in ['about psa', 'about psu', 'about pangasinan state', 'tell me about']):
|
| 85 |
+
return "Pangasinan State University (PSAU) is a premier state university in the Philippines offering quality education across various fields. We provide undergraduate and graduate programs in areas like Computer Science, Business, Education, Nursing, and more. We're committed to academic excellence and student success. What would you like to know more about?", 0.8
|
| 86 |
+
|
| 87 |
+
# Help responses
|
| 88 |
+
if any(help in question_lower for help in ['help', 'assist', 'support']):
|
| 89 |
+
return "I'm here to help! I can assist you with:\n• Admission requirements and deadlines\n• Course information and recommendations\n• Academic programs and majors\n• Campus facilities and services\n• General university information\n\nJust ask me any question and I'll do my best to help you!", 0.9
|
| 90 |
+
|
| 91 |
+
# Default general response
|
| 92 |
+
return "I understand you're asking about something, but I'm specifically designed to help with PSAU-related questions like admissions, courses, and university information. Could you rephrase your question to be more specific about what you'd like to know about Pangasinan State University? I'm here to help with academic guidance and university-related inquiries!", 0.6
|
| 93 |
+
|
| 94 |
+
def get_suggested_questions(self, question: str, num_suggestions: int = 3) -> List[str]:
|
| 95 |
+
"""No suggestions when FAQs are disabled."""
|
| 96 |
+
return []
|
| 97 |
+
|
| 98 |
+
def add_faq(self, question: str, answer: str) -> bool:
|
| 99 |
+
"""No-op when FAQs are disabled."""
|
| 100 |
+
return False
|
app_hf.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import numpy as np
|
| 5 |
+
from ai_chatbot import AIChatbot
|
| 6 |
+
from database_recommender import CourseRecommender
|
| 7 |
+
import warnings
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
# Suppress warnings
|
| 11 |
+
warnings.filterwarnings('ignore')
|
| 12 |
+
logging.getLogger('tensorflow').setLevel(logging.ERROR)
|
| 13 |
+
|
| 14 |
+
# Initialize components
|
| 15 |
+
try:
|
| 16 |
+
chatbot = AIChatbot()
|
| 17 |
+
print("✅ Chatbot initialized successfully")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"⚠️ Warning: Could not initialize chatbot: {e}")
|
| 20 |
+
chatbot = None
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
recommender = CourseRecommender()
|
| 24 |
+
print("✅ Recommender initialized successfully")
|
| 25 |
+
except Exception as e:
|
| 26 |
+
print(f"⚠️ Warning: Could not initialize recommender: {e}")
|
| 27 |
+
recommender = None
|
| 28 |
+
|
| 29 |
+
def chat_with_bot(message, history):
|
| 30 |
+
"""Handle chatbot interactions"""
|
| 31 |
+
if chatbot is None:
|
| 32 |
+
return "Sorry, the chatbot is not available at the moment. Please try again later."
|
| 33 |
+
|
| 34 |
+
if not message.strip():
|
| 35 |
+
return "Please enter a message to start the conversation."
|
| 36 |
+
|
| 37 |
+
# Get answer from chatbot
|
| 38 |
+
answer, confidence = chatbot.find_best_match(message)
|
| 39 |
+
|
| 40 |
+
# For general conversation, just return the answer
|
| 41 |
+
# For FAQ questions, include suggested questions
|
| 42 |
+
if confidence > 0.7: # High confidence FAQ match
|
| 43 |
+
suggested_questions = chatbot.get_suggested_questions(message)
|
| 44 |
+
if suggested_questions:
|
| 45 |
+
response = f"{answer}\n\n**Related Questions:**\n"
|
| 46 |
+
for i, q in enumerate(suggested_questions, 1):
|
| 47 |
+
response += f"{i}. {q}\n"
|
| 48 |
+
return response
|
| 49 |
+
|
| 50 |
+
# For general conversation or low confidence, just return the answer
|
| 51 |
+
return answer
|
| 52 |
+
|
| 53 |
+
def get_course_recommendations(stanine, gwa, strand, hobbies):
|
| 54 |
+
"""Get course recommendations"""
|
| 55 |
+
if recommender is None:
|
| 56 |
+
return "Sorry, the recommendation system is not available at the moment. Please try again later."
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
# Validate and convert inputs
|
| 60 |
+
try:
|
| 61 |
+
stanine = int(stanine.strip()) if stanine else 0
|
| 62 |
+
except (ValueError, AttributeError):
|
| 63 |
+
return "❌ Stanine score must be a valid number between 1 and 9"
|
| 64 |
+
|
| 65 |
+
try:
|
| 66 |
+
gwa = float(gwa.strip()) if gwa else 0
|
| 67 |
+
except (ValueError, AttributeError):
|
| 68 |
+
return "❌ GWA must be a valid number between 75 and 100"
|
| 69 |
+
|
| 70 |
+
# Validate ranges
|
| 71 |
+
if not (1 <= stanine <= 9):
|
| 72 |
+
return "❌ Stanine score must be between 1 and 9"
|
| 73 |
+
|
| 74 |
+
if not (75 <= gwa <= 100):
|
| 75 |
+
return "❌ GWA must be between 75 and 100"
|
| 76 |
+
|
| 77 |
+
if not strand:
|
| 78 |
+
return "❌ Please select a strand"
|
| 79 |
+
|
| 80 |
+
if not hobbies or not hobbies.strip():
|
| 81 |
+
return "❌ Please enter your hobbies/interests"
|
| 82 |
+
|
| 83 |
+
# Get recommendations
|
| 84 |
+
recommendations = recommender.recommend_courses(
|
| 85 |
+
stanine=stanine,
|
| 86 |
+
gwa=gwa,
|
| 87 |
+
strand=strand,
|
| 88 |
+
hobbies=hobbies
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
if not recommendations:
|
| 92 |
+
return "No recommendations available at the moment."
|
| 93 |
+
|
| 94 |
+
# Format recommendations
|
| 95 |
+
response = f"## 🎯 Course Recommendations for You\n\n"
|
| 96 |
+
response += f"**Profile:** Stanine {stanine}, GWA {gwa}, {strand} Strand\n"
|
| 97 |
+
response += f"**Interests:** {hobbies}\n\n"
|
| 98 |
+
|
| 99 |
+
for i, rec in enumerate(recommendations, 1):
|
| 100 |
+
response += f"### {i}. {rec['code']} - {rec['name']}\n"
|
| 101 |
+
response += f"**Match Score:** {rec.get('rating', rec.get('probability', 0)):.1f}%\n\n"
|
| 102 |
+
|
| 103 |
+
return response
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
return f"❌ Error getting recommendations: {str(e)}"
|
| 107 |
+
|
| 108 |
+
def get_faqs():
|
| 109 |
+
"""Get available FAQs"""
|
| 110 |
+
if chatbot and chatbot.faqs:
|
| 111 |
+
faq_text = "## 📚 Frequently Asked Questions\n\n"
|
| 112 |
+
for i, faq in enumerate(chatbot.faqs, 1):
|
| 113 |
+
faq_text += f"**{i}. {faq['question']}**\n"
|
| 114 |
+
faq_text += f"{faq['answer']}\n\n"
|
| 115 |
+
return faq_text
|
| 116 |
+
return "No FAQs available at the moment."
|
| 117 |
+
|
| 118 |
+
def get_available_courses():
|
| 119 |
+
"""Get available courses"""
|
| 120 |
+
if recommender and recommender.courses:
|
| 121 |
+
course_text = "## 🎓 Available Courses\n\n"
|
| 122 |
+
for code, name in recommender.courses.items():
|
| 123 |
+
course_text += f"**{code}** - {name}\n"
|
| 124 |
+
return course_text
|
| 125 |
+
return "No courses available at the moment."
|
| 126 |
+
|
| 127 |
+
# Create Gradio interface
|
| 128 |
+
with gr.Blocks(title="PSAU AI Chatbot & Course Recommender", theme=gr.themes.Soft()) as demo:
|
| 129 |
+
gr.Markdown(
|
| 130 |
+
"""
|
| 131 |
+
# 🤖 PSAU AI Chatbot & Course Recommender
|
| 132 |
+
|
| 133 |
+
Welcome to the Pangasinan State University AI-powered admission assistant!
|
| 134 |
+
Get instant answers to your questions and receive personalized course recommendations.
|
| 135 |
+
"""
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
with gr.Tabs():
|
| 139 |
+
# Chatbot Tab
|
| 140 |
+
with gr.Tab("🤖 AI Chatbot"):
|
| 141 |
+
gr.Markdown("""
|
| 142 |
+
**Chat with the PSAU AI Assistant!**
|
| 143 |
+
|
| 144 |
+
I can help you with:
|
| 145 |
+
• University admission questions
|
| 146 |
+
• Course information and guidance
|
| 147 |
+
• General conversation
|
| 148 |
+
• Academic support
|
| 149 |
+
|
| 150 |
+
Just type your message below and I'll respond naturally!
|
| 151 |
+
""")
|
| 152 |
+
|
| 153 |
+
chatbot_interface = gr.ChatInterface(
|
| 154 |
+
fn=chat_with_bot,
|
| 155 |
+
title="PSAU AI Assistant",
|
| 156 |
+
description="Chat with me about university admissions, courses, or just say hello!",
|
| 157 |
+
examples=[
|
| 158 |
+
"Hello!",
|
| 159 |
+
"What are the admission requirements?",
|
| 160 |
+
"How are you?",
|
| 161 |
+
"What courses are available?",
|
| 162 |
+
"Tell me about PSAU",
|
| 163 |
+
"What can you help me with?",
|
| 164 |
+
"Thank you",
|
| 165 |
+
"Goodbye"
|
| 166 |
+
],
|
| 167 |
+
cache_examples=True
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
# Course Recommender Tab
|
| 171 |
+
with gr.Tab("🎯 Course Recommender"):
|
| 172 |
+
gr.Markdown("""
|
| 173 |
+
Get personalized course recommendations based on your academic profile and interests!
|
| 174 |
+
|
| 175 |
+
**Input Guidelines:**
|
| 176 |
+
- **Stanine Score**: Enter a number between 1-9 (from your entrance exam)
|
| 177 |
+
- **GWA**: Enter your General Weighted Average (75-100)
|
| 178 |
+
- **Strand**: Select your senior high school strand
|
| 179 |
+
- **Hobbies**: Describe your interests and hobbies in detail
|
| 180 |
+
""")
|
| 181 |
+
|
| 182 |
+
with gr.Row():
|
| 183 |
+
with gr.Column():
|
| 184 |
+
stanine_input = gr.Textbox(
|
| 185 |
+
label="Stanine Score (1-9)",
|
| 186 |
+
placeholder="Enter your stanine score (1-9)",
|
| 187 |
+
info="Your stanine score from entrance examination",
|
| 188 |
+
value="7"
|
| 189 |
+
)
|
| 190 |
+
gwa_input = gr.Textbox(
|
| 191 |
+
label="GWA (75-100)",
|
| 192 |
+
placeholder="Enter your GWA (75-100)",
|
| 193 |
+
info="Your General Weighted Average",
|
| 194 |
+
value="85.0"
|
| 195 |
+
)
|
| 196 |
+
strand_input = gr.Dropdown(
|
| 197 |
+
choices=["STEM", "ABM", "HUMSS"],
|
| 198 |
+
value="STEM",
|
| 199 |
+
label="High School Strand",
|
| 200 |
+
info="Your senior high school strand"
|
| 201 |
+
)
|
| 202 |
+
hobbies_input = gr.Textbox(
|
| 203 |
+
label="Hobbies & Interests",
|
| 204 |
+
placeholder="e.g., programming, gaming, business, teaching, healthcare...",
|
| 205 |
+
info="Describe your interests and hobbies"
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
recommend_btn = gr.Button("Get Recommendations", variant="primary")
|
| 209 |
+
|
| 210 |
+
with gr.Column():
|
| 211 |
+
recommendations_output = gr.Markdown()
|
| 212 |
+
|
| 213 |
+
recommend_btn.click(
|
| 214 |
+
fn=get_course_recommendations,
|
| 215 |
+
inputs=[stanine_input, gwa_input, strand_input, hobbies_input],
|
| 216 |
+
outputs=recommendations_output
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
# Information Tab
|
| 220 |
+
with gr.Tab("📚 Information"):
|
| 221 |
+
with gr.Row():
|
| 222 |
+
with gr.Column():
|
| 223 |
+
gr.Markdown("### FAQ Section")
|
| 224 |
+
faq_btn = gr.Button("Show FAQs")
|
| 225 |
+
faq_output = gr.Markdown()
|
| 226 |
+
faq_btn.click(fn=get_faqs, outputs=faq_output)
|
| 227 |
+
|
| 228 |
+
with gr.Column():
|
| 229 |
+
gr.Markdown("### Available Courses")
|
| 230 |
+
courses_btn = gr.Button("Show Courses")
|
| 231 |
+
courses_output = gr.Markdown()
|
| 232 |
+
courses_btn.click(fn=get_available_courses, outputs=courses_output)
|
| 233 |
+
|
| 234 |
+
if __name__ == "__main__":
|
| 235 |
+
demo.launch(
|
| 236 |
+
server_name="0.0.0.0",
|
| 237 |
+
server_port=7860,
|
| 238 |
+
share=False,
|
| 239 |
+
show_error=True
|
| 240 |
+
)
|
database_recommender.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
from sklearn.neighbors import KNeighborsClassifier
|
| 4 |
+
from sklearn.preprocessing import LabelEncoder, StandardScaler
|
| 5 |
+
import joblib
|
| 6 |
+
import json
|
| 7 |
+
|
| 8 |
+
class CourseRecommender:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
self.model = None
|
| 11 |
+
self.label_encoders = {}
|
| 12 |
+
self.scaler = StandardScaler()
|
| 13 |
+
self.courses = self.get_courses()
|
| 14 |
+
self.training_data = self.get_training_data()
|
| 15 |
+
self.train_model()
|
| 16 |
+
|
| 17 |
+
def get_courses(self):
|
| 18 |
+
"""Get static course data"""
|
| 19 |
+
return {
|
| 20 |
+
'BSCS': 'Bachelor of Science in Computer Science',
|
| 21 |
+
'BSIT': 'Bachelor of Science in Information Technology',
|
| 22 |
+
'BSBA': 'Bachelor of Science in Business Administration',
|
| 23 |
+
'BSED': 'Bachelor of Science in Education',
|
| 24 |
+
'BSN': 'Bachelor of Science in Nursing',
|
| 25 |
+
'BSArch': 'Bachelor of Science in Architecture',
|
| 26 |
+
'BSIE': 'Bachelor of Science in Industrial Engineering',
|
| 27 |
+
'BSHM': 'Bachelor of Science in Hospitality Management',
|
| 28 |
+
'BSA': 'Bachelor of Science in Accountancy',
|
| 29 |
+
'BSPsych': 'Bachelor of Science in Psychology',
|
| 30 |
+
'BSAgri': 'Bachelor of Science in Agriculture'
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
def save_student_data(self, stanine, gwa, strand, course, rating, hobbies=None):
|
| 34 |
+
"""Save student feedback to in-memory storage (for demonstration purposes)"""
|
| 35 |
+
try:
|
| 36 |
+
# In a real implementation, you could save this to a file or external storage
|
| 37 |
+
print(f"Student feedback saved: Stanine={stanine}, GWA={gwa}, Strand={strand}, Course={course}, Rating={rating}, Hobbies={hobbies}")
|
| 38 |
+
return True
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"Error saving student feedback: {e}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
def get_training_data(self):
|
| 44 |
+
"""Get static training data for demonstration purposes"""
|
| 45 |
+
# Sample training data to demonstrate the recommender system
|
| 46 |
+
training_data = [
|
| 47 |
+
# STEM students
|
| 48 |
+
(8, 95, 'STEM', 'BSCS', 5, 'programming, gaming, technology'),
|
| 49 |
+
(7, 90, 'STEM', 'BSIT', 4, 'computers, software, coding'),
|
| 50 |
+
(9, 98, 'STEM', 'BSCS', 5, 'programming, algorithms, math'),
|
| 51 |
+
(6, 85, 'STEM', 'BSIT', 3, 'technology, computers'),
|
| 52 |
+
(8, 92, 'STEM', 'BSArch', 4, 'design, drawing, creativity'),
|
| 53 |
+
(7, 88, 'STEM', 'BSIE', 4, 'engineering, problem solving'),
|
| 54 |
+
|
| 55 |
+
# ABM students
|
| 56 |
+
(8, 90, 'ABM', 'BSBA', 5, 'business, management, leadership'),
|
| 57 |
+
(7, 85, 'ABM', 'BSA', 4, 'accounting, numbers, finance'),
|
| 58 |
+
(6, 82, 'ABM', 'BSBA', 3, 'business, marketing'),
|
| 59 |
+
(9, 95, 'ABM', 'BSA', 5, 'accounting, finance, analysis'),
|
| 60 |
+
|
| 61 |
+
# HUMSS students
|
| 62 |
+
(8, 88, 'HUMSS', 'BSED', 5, 'teaching, helping, education'),
|
| 63 |
+
(7, 85, 'HUMSS', 'BSPsych', 4, 'psychology, helping, people'),
|
| 64 |
+
(6, 80, 'HUMSS', 'BSED', 3, 'teaching, children'),
|
| 65 |
+
(9, 92, 'HUMSS', 'BSPsych', 5, 'psychology, counseling, people'),
|
| 66 |
+
|
| 67 |
+
# General interests
|
| 68 |
+
(7, 87, 'STEM', 'BSN', 4, 'helping, healthcare, caring'),
|
| 69 |
+
(8, 89, 'ABM', 'BSHM', 4, 'hospitality, service, management'),
|
| 70 |
+
(6, 83, 'HUMSS', 'BSAgri', 3, 'agriculture, environment, nature'),
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
return pd.DataFrame(training_data, columns=['stanine', 'gwa', 'strand', 'course', 'rating', 'hobbies'])
|
| 74 |
+
|
| 75 |
+
def train_model(self):
|
| 76 |
+
"""Train the recommendation model using the training data"""
|
| 77 |
+
try:
|
| 78 |
+
training_data = self.get_training_data()
|
| 79 |
+
|
| 80 |
+
if training_data.empty:
|
| 81 |
+
print("No training data available - using default recommendations")
|
| 82 |
+
return
|
| 83 |
+
|
| 84 |
+
# Prepare features (hobbies required)
|
| 85 |
+
feature_columns = ['stanine', 'gwa', 'strand', 'hobbies']
|
| 86 |
+
|
| 87 |
+
# Create feature matrix
|
| 88 |
+
X = training_data[feature_columns].copy()
|
| 89 |
+
y = training_data['course']
|
| 90 |
+
|
| 91 |
+
# Handle categorical variables
|
| 92 |
+
categorical_columns = ['strand', 'hobbies']
|
| 93 |
+
|
| 94 |
+
# Refit encoders every training to incorporate new categories
|
| 95 |
+
for col in categorical_columns:
|
| 96 |
+
if col in X.columns:
|
| 97 |
+
X[col] = X[col].fillna('unknown')
|
| 98 |
+
self.label_encoders[col] = LabelEncoder()
|
| 99 |
+
X[col] = self.label_encoders[col].fit_transform(X[col])
|
| 100 |
+
|
| 101 |
+
# Scale numerical features
|
| 102 |
+
numerical_columns = ['stanine', 'gwa']
|
| 103 |
+
if not X[numerical_columns].empty:
|
| 104 |
+
X[numerical_columns] = self.scaler.fit_transform(X[numerical_columns])
|
| 105 |
+
|
| 106 |
+
# Train KNN model
|
| 107 |
+
self.model = KNeighborsClassifier(n_neighbors=3, weights='distance')
|
| 108 |
+
self.model.fit(X, y)
|
| 109 |
+
|
| 110 |
+
print("✅ Model trained successfully (hobbies required and encoded)")
|
| 111 |
+
|
| 112 |
+
except Exception as e:
|
| 113 |
+
print(f"Error training model: {e}")
|
| 114 |
+
self.model = None
|
| 115 |
+
|
| 116 |
+
def get_default_recommendations(self, stanine, gwa, strand):
|
| 117 |
+
"""Provide default recommendations based on basic rules when no training data is available"""
|
| 118 |
+
courses = self.courses
|
| 119 |
+
recommendations = []
|
| 120 |
+
|
| 121 |
+
# Basic rules for recommendations
|
| 122 |
+
if strand == 'STEM':
|
| 123 |
+
if stanine >= 8 and gwa >= 90:
|
| 124 |
+
priority_courses = ['BSCS', 'BSIT']
|
| 125 |
+
else:
|
| 126 |
+
priority_courses = ['BSIT', 'BSCS']
|
| 127 |
+
elif strand == 'ABM':
|
| 128 |
+
priority_courses = ['BSBA']
|
| 129 |
+
elif strand == 'HUMSS':
|
| 130 |
+
priority_courses = ['BSED']
|
| 131 |
+
else:
|
| 132 |
+
priority_courses = list(courses.keys())
|
| 133 |
+
|
| 134 |
+
# Add courses with default probabilities
|
| 135 |
+
for i, course in enumerate(priority_courses[:2]): # Only take top 2
|
| 136 |
+
if course in courses:
|
| 137 |
+
recommendations.append({
|
| 138 |
+
'code': course,
|
| 139 |
+
'name': courses[course],
|
| 140 |
+
'probability': 1.0 - (i * 0.2) # Decreasing probability for each course
|
| 141 |
+
})
|
| 142 |
+
|
| 143 |
+
return recommendations
|
| 144 |
+
|
| 145 |
+
def recommend_courses(self, stanine, gwa, strand, hobbies=None, top_n=5):
|
| 146 |
+
"""Recommend courses based on student profile (hobbies required)"""
|
| 147 |
+
try:
|
| 148 |
+
if self.model is None:
|
| 149 |
+
return self.get_default_recommendations(stanine, gwa, strand)
|
| 150 |
+
|
| 151 |
+
# Prepare input features
|
| 152 |
+
input_data = pd.DataFrame([{
|
| 153 |
+
'stanine': stanine,
|
| 154 |
+
'gwa': gwa,
|
| 155 |
+
'strand': strand,
|
| 156 |
+
'hobbies': (hobbies or '').strip()
|
| 157 |
+
}])
|
| 158 |
+
# Validate hobbies
|
| 159 |
+
if not input_data['hobbies'].iloc[0]:
|
| 160 |
+
raise ValueError('hobbies is required for recommendations')
|
| 161 |
+
|
| 162 |
+
# Encode categorical variables
|
| 163 |
+
for col in ['strand', 'hobbies']:
|
| 164 |
+
if col in input_data.columns and col in self.label_encoders:
|
| 165 |
+
value = input_data[col].iloc[0]
|
| 166 |
+
if value not in self.label_encoders[col].classes_:
|
| 167 |
+
# Extend encoder classes to include unseen value at inference
|
| 168 |
+
self.label_encoders[col].classes_ = np.append(self.label_encoders[col].classes_, value)
|
| 169 |
+
input_data[col] = self.label_encoders[col].transform(input_data[col])
|
| 170 |
+
|
| 171 |
+
# Scale numerical features
|
| 172 |
+
numerical_columns = ['stanine', 'gwa']
|
| 173 |
+
if not input_data[numerical_columns].empty:
|
| 174 |
+
input_data[numerical_columns] = self.scaler.transform(input_data[numerical_columns])
|
| 175 |
+
|
| 176 |
+
# Get predictions
|
| 177 |
+
predictions = self.model.predict_proba(input_data)
|
| 178 |
+
courses = self.model.classes_
|
| 179 |
+
|
| 180 |
+
# Get top recommendations
|
| 181 |
+
top_indices = np.argsort(predictions[0])[-top_n:][::-1]
|
| 182 |
+
recommendations = []
|
| 183 |
+
|
| 184 |
+
course_map = self.courses
|
| 185 |
+
for idx in top_indices:
|
| 186 |
+
code = courses[idx]
|
| 187 |
+
confidence = predictions[0][idx]
|
| 188 |
+
recommendations.append({
|
| 189 |
+
'code': code,
|
| 190 |
+
'name': course_map.get(code, code),
|
| 191 |
+
'rating': round(confidence * 100, 1)
|
| 192 |
+
})
|
| 193 |
+
|
| 194 |
+
return recommendations
|
| 195 |
+
|
| 196 |
+
except Exception as e:
|
| 197 |
+
print(f"Error recommending courses: {e}")
|
| 198 |
+
return self.get_default_recommendations(stanine, gwa, strand)
|
| 199 |
+
|
| 200 |
+
def _get_recommendation_reason(self, course, stanine, gwa, strand, hobbies, interests, personality_type, learning_style, career_goals):
|
| 201 |
+
"""Generate personalized reason for recommendation"""
|
| 202 |
+
reasons = []
|
| 203 |
+
|
| 204 |
+
# Academic performance reasons
|
| 205 |
+
if stanine >= 8:
|
| 206 |
+
reasons.append("Excellent academic performance")
|
| 207 |
+
elif stanine >= 6:
|
| 208 |
+
reasons.append("Good academic foundation")
|
| 209 |
+
|
| 210 |
+
if gwa >= 85:
|
| 211 |
+
reasons.append("High academic achievement")
|
| 212 |
+
elif gwa >= 80:
|
| 213 |
+
reasons.append("Strong academic record")
|
| 214 |
+
|
| 215 |
+
# Strand alignment
|
| 216 |
+
if strand == "STEM" and course in ["BSCS", "BSIT", "BSArch", "BSIE", "BSN"]:
|
| 217 |
+
reasons.append("Perfect match with your STEM background")
|
| 218 |
+
elif strand == "ABM" and course in ["BSBA", "BSA"]:
|
| 219 |
+
reasons.append("Excellent alignment with your ABM strand")
|
| 220 |
+
elif strand == "HUMSS" and course in ["BSED", "BSPsych"]:
|
| 221 |
+
reasons.append("Great fit with your HUMSS background")
|
| 222 |
+
|
| 223 |
+
# Hobbies and interests alignment
|
| 224 |
+
if hobbies and any(hobby in hobbies.lower() for hobby in ["gaming", "programming", "technology", "computers"]):
|
| 225 |
+
if course in ["BSCS", "BSIT"]:
|
| 226 |
+
reasons.append("Matches your technology interests")
|
| 227 |
+
|
| 228 |
+
if hobbies and any(hobby in hobbies.lower() for hobby in ["business", "leadership", "management"]):
|
| 229 |
+
if course in ["BSBA", "BSA"]:
|
| 230 |
+
reasons.append("Aligns with your business interests")
|
| 231 |
+
|
| 232 |
+
if hobbies and any(hobby in hobbies.lower() for hobby in ["helping", "teaching", "caring"]):
|
| 233 |
+
if course in ["BSED", "BSN", "BSPsych"]:
|
| 234 |
+
reasons.append("Perfect for your helping nature")
|
| 235 |
+
|
| 236 |
+
# Personality type alignment
|
| 237 |
+
if personality_type == "introvert" and course in ["BSCS", "BSA", "BSArch"]:
|
| 238 |
+
reasons.append("Suits your introverted personality")
|
| 239 |
+
elif personality_type == "extrovert" and course in ["BSBA", "BSED", "BSHM"]:
|
| 240 |
+
reasons.append("Great for your outgoing personality")
|
| 241 |
+
|
| 242 |
+
# Learning style alignment
|
| 243 |
+
if learning_style == "hands-on" and course in ["BSIT", "BSHM", "BSAgri"]:
|
| 244 |
+
reasons.append("Matches your hands-on learning preference")
|
| 245 |
+
elif learning_style == "visual" and course in ["BSArch", "BSCS"]:
|
| 246 |
+
reasons.append("Perfect for your visual learning style")
|
| 247 |
+
|
| 248 |
+
# Career goals alignment
|
| 249 |
+
if career_goals and any(goal in career_goals.lower() for goal in ["developer", "programmer", "software"]):
|
| 250 |
+
if course in ["BSCS", "BSIT"]:
|
| 251 |
+
reasons.append("Direct path to your career goals")
|
| 252 |
+
|
| 253 |
+
if career_goals and any(goal in career_goals.lower() for goal in ["business", "entrepreneur", "manager"]):
|
| 254 |
+
if course in ["BSBA", "BSA"]:
|
| 255 |
+
reasons.append("Direct path to your business goals")
|
| 256 |
+
|
| 257 |
+
# Default reason if no specific matches
|
| 258 |
+
if not reasons:
|
| 259 |
+
reasons.append("Good academic and personal fit")
|
| 260 |
+
|
| 261 |
+
return " • ".join(reasons[:3]) # Limit to top 3 reasons
|
| 262 |
+
|
| 263 |
+
def save_model(self, model_path='course_recommender_model.joblib'):
|
| 264 |
+
"""Save the trained model"""
|
| 265 |
+
if self.model is None:
|
| 266 |
+
raise Exception("No model to save!")
|
| 267 |
+
|
| 268 |
+
model_data = {
|
| 269 |
+
'model': self.model,
|
| 270 |
+
'scaler': self.scaler,
|
| 271 |
+
'label_encoders': self.label_encoders
|
| 272 |
+
}
|
| 273 |
+
joblib.dump(model_data, model_path)
|
| 274 |
+
|
| 275 |
+
def load_model(self, model_path='course_recommender_model.joblib'):
|
| 276 |
+
"""Load a trained model"""
|
| 277 |
+
model_data = joblib.load(model_path)
|
| 278 |
+
self.model = model_data['model']
|
| 279 |
+
self.scaler = model_data['scaler']
|
| 280 |
+
self.label_encoders = model_data['label_encoders']
|
| 281 |
+
|
| 282 |
+
# Example usage
|
| 283 |
+
if __name__ == "__main__":
|
| 284 |
+
recommender = CourseRecommender()
|
| 285 |
+
|
| 286 |
+
# Example recommendation
|
| 287 |
+
recommendations = recommender.recommend_courses(
|
| 288 |
+
stanine=8,
|
| 289 |
+
gwa=95,
|
| 290 |
+
strand='STEM',
|
| 291 |
+
hobbies='programming, gaming, technology'
|
| 292 |
+
)
|
| 293 |
+
print("Recommended courses:", json.dumps(recommendations, indent=2))
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
numpy
|
| 3 |
+
pandas
|
| 4 |
+
scikit-learn
|
| 5 |
+
joblib
|
| 6 |
+
sentence-transformers
|
| 7 |
+
torch
|