Spaces:
Sleeping
Sleeping
Download app.py from Learnix-AI-Lab/Study-Pilot-Ai: direct link, hf CLI and curl.
- Browser
- Download file 33.7 kB
-
https://huggingface.co/spaces/Learnix-AI-Lab/Study-Pilot-Ai/resolve/main/app.py
- Command line
-
hf download hf://spaces/Learnix-AI-Lab/Study-Pilot-Ai/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Learnix-AI-Lab/Study-Pilot-Ai/resolve/main/app.py
33.7 kB
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import json | |
| import os | |
| from pathlib import Path | |
| from datetime import datetime, date | |
| import matplotlib.pyplot as plt | |
| # ========================================================= | |
| # StudyPilot AI | |
| # Reinforcement Learning based Smart Study Planner | |
| # Hugging Face Gradio Deployment Ready | |
| # ========================================================= | |
| DATA_DIR = Path("data") | |
| DATA_DIR.mkdir(exist_ok=True) | |
| PROFILE_FILE = DATA_DIR / "profile.json" | |
| SUBJECTS_FILE = DATA_DIR / "subjects.csv" | |
| TOPICS_FILE = DATA_DIR / "topics.csv" | |
| Q_TABLE_FILE = DATA_DIR / "q_table.json" | |
| LOGS_FILE = DATA_DIR / "study_logs.csv" | |
| ACTIONS = [ | |
| "study_new_topic", | |
| "revise_topic", | |
| "practice_questions", | |
| "take_quiz", | |
| "quick_review" | |
| ] | |
| DEFAULT_PROFILE = { | |
| "name": "Student", | |
| "exam_type": "HSC", | |
| "exam_date": str(date.today()), | |
| "daily_hours": 3, | |
| "target_grade": "A+", | |
| "study_mode": "Balanced" | |
| } | |
| # ========================================================= | |
| # Storage Helpers | |
| # ========================================================= | |
| def ensure_files(): | |
| if not PROFILE_FILE.exists(): | |
| save_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| if not SUBJECTS_FILE.exists(): | |
| pd.DataFrame(columns=[ | |
| "subject", "importance", "confidence" | |
| ]).to_csv(SUBJECTS_FILE, index=False) | |
| if not TOPICS_FILE.exists(): | |
| pd.DataFrame(columns=[ | |
| "subject", "topic", "difficulty", "importance", "estimated_hours", | |
| "confidence", "quiz_score", "completed", "last_studied", "skip_count" | |
| ]).to_csv(TOPICS_FILE, index=False) | |
| if not Q_TABLE_FILE.exists(): | |
| save_json(Q_TABLE_FILE, {}) | |
| if not LOGS_FILE.exists(): | |
| pd.DataFrame(columns=[ | |
| "date", "subject", "topic", "action", "completed", | |
| "study_minutes", "old_score", "new_score", "reward" | |
| ]).to_csv(LOGS_FILE, index=False) | |
| def load_json(path, default=None): | |
| if not path.exists(): | |
| return default if default is not None else {} | |
| try: | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except Exception: | |
| return default if default is not None else {} | |
| def save_json(path, data): | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(data, f, indent=2, ensure_ascii=False) | |
| def load_subjects(): | |
| ensure_files() | |
| return pd.read_csv(SUBJECTS_FILE) | |
| def save_subjects(df): | |
| df.to_csv(SUBJECTS_FILE, index=False) | |
| def load_topics(): | |
| ensure_files() | |
| df = pd.read_csv(TOPICS_FILE) | |
| if not df.empty: | |
| df["completed"] = df["completed"].astype(str).map( | |
| {"True": True, "False": False, "true": True, "false": False, "1": True, "0": False} | |
| ).fillna(False) | |
| numeric_cols = ["difficulty", "importance", "estimated_hours", "confidence", "quiz_score", "skip_count"] | |
| for col in numeric_cols: | |
| if col in df.columns: | |
| df[col] = pd.to_numeric(df[col], errors="coerce").fillna(0) | |
| return df | |
| def save_topics(df): | |
| df.to_csv(TOPICS_FILE, index=False) | |
| def load_q_table(): | |
| ensure_files() | |
| return load_json(Q_TABLE_FILE, {}) | |
| def save_q_table(q_table): | |
| save_json(Q_TABLE_FILE, q_table) | |
| def load_logs(): | |
| ensure_files() | |
| return pd.read_csv(LOGS_FILE) | |
| def save_logs(df): | |
| df.to_csv(LOGS_FILE, index=False) | |
| ensure_files() | |
| # ========================================================= | |
| # Core Logic | |
| # ========================================================= | |
| def safe_float(x, default=0.0): | |
| try: | |
| if x is None or x == "": | |
| return default | |
| return float(x) | |
| except Exception: | |
| return default | |
| def parse_date(date_str): | |
| try: | |
| return datetime.strptime(str(date_str), "%Y-%m-%d").date() | |
| except Exception: | |
| return date.today() | |
| def get_days_left(): | |
| profile = load_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| exam_date = parse_date(profile.get("exam_date", str(date.today()))) | |
| return max((exam_date - date.today()).days, 0) | |
| def topic_key(subject, topic): | |
| return f"{subject}::{topic}" | |
| def get_q_values(subject, topic): | |
| q_table = load_q_table() | |
| key = topic_key(subject, topic) | |
| if key not in q_table: | |
| q_table[key] = {action: 0.5 for action in ACTIONS} | |
| save_q_table(q_table) | |
| return q_table[key] | |
| def best_action(subject, topic, study_mode="Balanced"): | |
| q_values = get_q_values(subject, topic) | |
| if study_mode == "Revision Focused": | |
| q_values["revise_topic"] += 0.10 | |
| q_values["quick_review"] += 0.08 | |
| elif study_mode == "Weakness Killer": | |
| q_values["practice_questions"] += 0.12 | |
| q_values["take_quiz"] += 0.08 | |
| elif study_mode == "Exam Crash": | |
| q_values["revise_topic"] += 0.14 | |
| q_values["practice_questions"] += 0.12 | |
| q_values["quick_review"] += 0.10 | |
| elif study_mode == "Confidence Builder": | |
| q_values["quick_review"] += 0.08 | |
| q_values["study_new_topic"] += 0.04 | |
| return max(q_values, key=q_values.get), max(q_values.values()) | |
| def days_since_last_studied(last_studied): | |
| try: | |
| d = datetime.strptime(str(last_studied), "%Y-%m-%d").date() | |
| return max((date.today() - d).days, 0) | |
| except Exception: | |
| return 30 | |
| def calculate_topic_priority(row, study_mode="Balanced"): | |
| days_left = max(get_days_left(), 1) | |
| quiz_score = safe_float(row.get("quiz_score", 0)) | |
| confidence = safe_float(row.get("confidence", 5)) | |
| difficulty = safe_float(row.get("difficulty", 5)) | |
| importance = safe_float(row.get("importance", 5)) | |
| completed = bool(row.get("completed", False)) | |
| skip_count = safe_float(row.get("skip_count", 0)) | |
| weakness_score = max(0, 100 - quiz_score) / 100 | |
| confidence_weakness = max(0, 10 - confidence) / 10 | |
| revision_need = min(days_since_last_studied(row.get("last_studied", "")) / 14, 1.0) | |
| exam_pressure = min(30 / days_left, 3.0) / 3.0 | |
| incomplete_bonus = 0 if completed else 1 | |
| action, q_value = best_action(row["subject"], row["topic"], study_mode) | |
| # Mode weights | |
| if study_mode == "Weakness Killer": | |
| w_weakness, w_importance, w_revision = 0.38, 0.24, 0.10 | |
| elif study_mode == "Revision Focused": | |
| w_weakness, w_importance, w_revision = 0.22, 0.24, 0.30 | |
| elif study_mode == "Exam Crash": | |
| w_weakness, w_importance, w_revision = 0.25, 0.33, 0.22 | |
| elif study_mode == "Confidence Builder": | |
| w_weakness, w_importance, w_revision = 0.24, 0.22, 0.16 | |
| else: | |
| w_weakness, w_importance, w_revision = 0.30, 0.27, 0.16 | |
| score = ( | |
| (importance / 10) * w_importance + | |
| (difficulty / 10) * 0.10 + | |
| weakness_score * w_weakness + | |
| confidence_weakness * 0.10 + | |
| revision_need * w_revision + | |
| exam_pressure * 0.10 + | |
| incomplete_bonus * 0.05 + | |
| min(skip_count / 5, 1) * 0.04 + | |
| q_value * 0.08 | |
| ) | |
| return round(score * 100, 2), action | |
| def explain_reason(row, action): | |
| reasons = [] | |
| quiz = safe_float(row.get("quiz_score", 0)) | |
| confidence = safe_float(row.get("confidence", 5)) | |
| importance = safe_float(row.get("importance", 5)) | |
| difficulty = safe_float(row.get("difficulty", 5)) | |
| last_days = days_since_last_studied(row.get("last_studied", "")) | |
| if quiz < 50: | |
| reasons.append("quiz score is low") | |
| if confidence <= 4: | |
| reasons.append("confidence is low") | |
| if importance >= 8: | |
| reasons.append("exam importance is high") | |
| if difficulty >= 8: | |
| reasons.append("topic difficulty is high") | |
| if last_days >= 7: | |
| reasons.append("revision gap is large") | |
| if not reasons: | |
| reasons.append("it is useful for steady progress") | |
| action_text = action.replace("_", " ") | |
| return f"Recommended action: {action_text}. Reason: " + ", ".join(reasons) + "." | |
| def action_to_task(action): | |
| mapping = { | |
| "study_new_topic": "Study concept + make short notes", | |
| "revise_topic": "Revise notes + solve examples", | |
| "practice_questions": "Practice MCQ/CQ/problem solving", | |
| "take_quiz": "Take quiz and review mistakes", | |
| "quick_review": "Quick revision and formula review" | |
| } | |
| return mapping.get(action, "Study this topic") | |
| def generate_today_plan(max_topics=3): | |
| profile = load_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| study_mode = profile.get("study_mode", "Balanced") | |
| daily_hours = max(safe_float(profile.get("daily_hours", 3), 3), 1) | |
| topics = load_topics() | |
| if topics.empty: | |
| return "No topics added yet. Please add subjects and topics first.", pd.DataFrame(), None | |
| scored_rows = [] | |
| for _, row in topics.iterrows(): | |
| score, action = calculate_topic_priority(row, study_mode) | |
| scored_rows.append({ | |
| "Priority Score": score, | |
| "Subject": row["subject"], | |
| "Topic": row["topic"], | |
| "Recommended Task": action_to_task(action), | |
| "Action": action, | |
| "Quiz Score": row["quiz_score"], | |
| "Confidence": row["confidence"], | |
| "Importance": row["importance"], | |
| "Difficulty": row["difficulty"], | |
| "Reason": explain_reason(row, action) | |
| }) | |
| plan_df = pd.DataFrame(scored_rows).sort_values("Priority Score", ascending=False).head(int(max_topics)) | |
| total_priority = plan_df["Priority Score"].sum() | |
| total_minutes = int(daily_hours * 60) | |
| if total_priority <= 0: | |
| plan_df["Study Time"] = int(total_minutes / len(plan_df)) | |
| else: | |
| plan_df["Study Time"] = plan_df["Priority Score"].apply( | |
| lambda x: max(20, int((x / total_priority) * total_minutes)) | |
| ) | |
| days_left = get_days_left() | |
| readiness = calculate_readiness() | |
| message = f""" | |
| # Today’s Smart Study Plan | |
| **Exam in:** {days_left} days | |
| **Study Mode:** {study_mode} | |
| **Daily Study Time:** {daily_hours} hours | |
| **Overall Readiness:** {readiness}% | |
| Start with the highest priority topic first. The app ranks topics using quiz score, confidence, importance, revision gap, exam pressure, and RL memory. | |
| """ | |
| fig = make_priority_chart(plan_df) | |
| return message, plan_df, fig | |
| def calculate_readiness(): | |
| topics = load_topics() | |
| if topics.empty: | |
| return 0 | |
| quiz_component = topics["quiz_score"].clip(0, 100).mean() | |
| confidence_component = (topics["confidence"].clip(0, 10).mean() / 10) * 100 | |
| completion_component = topics["completed"].astype(bool).mean() * 100 if len(topics) else 0 | |
| readiness = quiz_component * 0.45 + confidence_component * 0.30 + completion_component * 0.25 | |
| return round(float(readiness), 1) | |
| def calculate_risk_label(readiness): | |
| if readiness >= 80: | |
| return "Low Risk" | |
| elif readiness >= 60: | |
| return "Medium Risk" | |
| elif readiness >= 40: | |
| return "High Risk" | |
| return "Very High Risk" | |
| def make_priority_chart(plan_df): | |
| if plan_df is None or plan_df.empty: | |
| return None | |
| fig, ax = plt.subplots(figsize=(8, 4)) | |
| labels = [f"{s}\n{t}" for s, t in zip(plan_df["Subject"], plan_df["Topic"])] | |
| ax.bar(labels, plan_df["Priority Score"]) | |
| ax.set_title("Today’s Topic Priority") | |
| ax.set_ylabel("Priority Score") | |
| ax.tick_params(axis="x", labelrotation=20) | |
| plt.tight_layout() | |
| return fig | |
| def make_subject_progress_chart(): | |
| topics = load_topics() | |
| if topics.empty: | |
| return None | |
| grouped = topics.groupby("subject").agg( | |
| avg_quiz=("quiz_score", "mean"), | |
| avg_confidence=("confidence", "mean"), | |
| completion=("completed", lambda x: x.astype(bool).mean() * 100) | |
| ).reset_index() | |
| grouped["readiness"] = ( | |
| grouped["avg_quiz"] * 0.50 + | |
| (grouped["avg_confidence"] / 10 * 100) * 0.25 + | |
| grouped["completion"] * 0.25 | |
| ) | |
| fig, ax = plt.subplots(figsize=(8, 4)) | |
| ax.bar(grouped["subject"], grouped["readiness"]) | |
| ax.set_ylim(0, 100) | |
| ax.set_title("Subject Readiness") | |
| ax.set_ylabel("Readiness %") | |
| ax.tick_params(axis="x", labelrotation=20) | |
| plt.tight_layout() | |
| return fig | |
| def calculate_reward(old_score, new_score, completed, study_minutes, topic_row): | |
| reward = 0 | |
| old_score = safe_float(old_score, 0) | |
| new_score = safe_float(new_score, 0) | |
| study_minutes = safe_float(study_minutes, 0) | |
| importance = safe_float(topic_row.get("importance", 5), 5) | |
| quiz_improvement = new_score - old_score | |
| if completed: | |
| reward += 20 | |
| else: | |
| reward -= 10 | |
| if quiz_improvement > 0: | |
| reward += min(30, quiz_improvement) | |
| elif quiz_improvement < 0: | |
| reward -= min(15, abs(quiz_improvement)) | |
| if old_score < 50 and completed: | |
| reward += 15 | |
| if importance >= 8 and completed: | |
| reward += 10 | |
| if study_minutes >= 30: | |
| reward += 10 | |
| if not completed and importance >= 8: | |
| reward -= 15 | |
| return round(float(reward), 2) | |
| def update_q_table(subject, topic, action, reward): | |
| q_table = load_q_table() | |
| key = topic_key(subject, topic) | |
| if key not in q_table: | |
| q_table[key] = {a: 0.5 for a in ACTIONS} | |
| if action not in q_table[key]: | |
| q_table[key][action] = 0.5 | |
| old_q = q_table[key][action] | |
| learning_rate = 0.12 | |
| discount_factor = 0.90 | |
| max_future_q = max(q_table[key].values()) | |
| normalized_reward = reward / 100 | |
| new_q = old_q + learning_rate * (normalized_reward + discount_factor * max_future_q - old_q) | |
| q_table[key][action] = round(float(new_q), 4) | |
| save_q_table(q_table) | |
| return old_q, new_q | |
| # ========================================================= | |
| # Gradio Functions | |
| # ========================================================= | |
| def save_profile(name, exam_type, exam_date, daily_hours, target_grade, study_mode): | |
| profile = { | |
| "name": name or "Student", | |
| "exam_type": exam_type or "General Exam", | |
| "exam_date": exam_date, | |
| "daily_hours": safe_float(daily_hours, 3), | |
| "target_grade": target_grade or "A+", | |
| "study_mode": study_mode or "Balanced" | |
| } | |
| save_json(PROFILE_FILE, profile) | |
| return f"Profile saved for {profile['name']}." | |
| def get_profile_summary(): | |
| profile = load_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| days_left = get_days_left() | |
| readiness = calculate_readiness() | |
| risk = calculate_risk_label(readiness) | |
| return f""" | |
| # StudyPilot AI Dashboard | |
| **Student:** {profile.get("name", "Student")} | |
| **Exam:** {profile.get("exam_type", "Exam")} | |
| **Exam Date:** {profile.get("exam_date", "")} | |
| **Days Left:** {days_left} | |
| **Daily Study Hours:** {profile.get("daily_hours", 3)} | |
| **Study Mode:** {profile.get("study_mode", "Balanced")} | |
| **Overall Readiness:** {readiness}% | |
| **Risk Level:** {risk} | |
| """ | |
| def add_subject(subject, importance, confidence): | |
| subject = str(subject).strip() | |
| if not subject: | |
| return "Subject name is required.", load_subjects() | |
| df = load_subjects() | |
| if not df.empty and subject.lower() in df["subject"].astype(str).str.lower().values: | |
| return "This subject already exists.", df | |
| new_row = pd.DataFrame([{ | |
| "subject": subject, | |
| "importance": int(safe_float(importance, 5)), | |
| "confidence": int(safe_float(confidence, 5)) | |
| }]) | |
| df = pd.concat([df, new_row], ignore_index=True) | |
| save_subjects(df) | |
| return f"Subject added: {subject}", df | |
| def add_topic(subject, topic, difficulty, importance, estimated_hours, confidence, quiz_score): | |
| subject = str(subject).strip() | |
| topic = str(topic).strip() | |
| if not subject or not topic: | |
| return "Subject and topic are required.", load_topics() | |
| subjects = load_subjects() | |
| if subjects.empty or subject.lower() not in subjects["subject"].astype(str).str.lower().values: | |
| # Auto-create subject for convenience | |
| subjects = pd.concat([subjects, pd.DataFrame([{ | |
| "subject": subject, | |
| "importance": 7, | |
| "confidence": 5 | |
| }])], ignore_index=True) | |
| save_subjects(subjects) | |
| df = load_topics() | |
| duplicate = False | |
| if not df.empty: | |
| duplicate = ((df["subject"].astype(str).str.lower() == subject.lower()) & | |
| (df["topic"].astype(str).str.lower() == topic.lower())).any() | |
| if duplicate: | |
| return "This topic already exists under this subject.", df | |
| new_row = pd.DataFrame([{ | |
| "subject": subject, | |
| "topic": topic, | |
| "difficulty": int(safe_float(difficulty, 5)), | |
| "importance": int(safe_float(importance, 5)), | |
| "estimated_hours": safe_float(estimated_hours, 2), | |
| "confidence": int(safe_float(confidence, 5)), | |
| "quiz_score": int(safe_float(quiz_score, 0)), | |
| "completed": False, | |
| "last_studied": str(date.today()), | |
| "skip_count": 0 | |
| }]) | |
| df = pd.concat([df, new_row], ignore_index=True) | |
| save_topics(df) | |
| # Create initial q values | |
| get_q_values(subject, topic) | |
| return f"Topic added: {subject} → {topic}", df | |
| def get_subject_choices(): | |
| df = load_subjects() | |
| if df.empty: | |
| return [] | |
| return sorted(df["subject"].dropna().astype(str).unique().tolist()) | |
| def get_topic_choices(): | |
| df = load_topics() | |
| if df.empty: | |
| return [] | |
| return [f"{row.subject} :: {row.topic}" for row in df.itertuples()] | |
| def refresh_all_tables(): | |
| return load_subjects(), load_topics(), load_logs(), get_profile_summary(), make_subject_progress_chart() | |
| def submit_feedback(topic_selection, action, completed, study_minutes, new_quiz_score, difficulty_after): | |
| if not topic_selection: | |
| return "Please select a topic.", load_topics(), load_logs(), make_subject_progress_chart() | |
| try: | |
| subject, topic = [x.strip() for x in topic_selection.split("::", 1)] | |
| except Exception: | |
| return "Invalid topic selection.", load_topics(), load_logs(), make_subject_progress_chart() | |
| topics = load_topics() | |
| mask = (topics["subject"].astype(str) == subject) & (topics["topic"].astype(str) == topic) | |
| if not mask.any(): | |
| return "Topic not found.", topics, load_logs(), make_subject_progress_chart() | |
| idx = topics[mask].index[0] | |
| old_score = safe_float(topics.loc[idx, "quiz_score"], 0) | |
| completed_bool = bool(completed) | |
| new_score = safe_float(new_quiz_score, old_score) | |
| topic_row = topics.loc[idx].to_dict() | |
| reward = calculate_reward(old_score, new_score, completed_bool, study_minutes, topic_row) | |
| old_q, new_q = update_q_table(subject, topic, action, reward) | |
| topics.loc[idx, "quiz_score"] = new_score | |
| topics.loc[idx, "completed"] = completed_bool | |
| topics.loc[idx, "confidence"] = difficulty_to_confidence(difficulty_after, topics.loc[idx, "confidence"]) | |
| topics.loc[idx, "last_studied"] = str(date.today()) | |
| if completed_bool: | |
| topics.loc[idx, "skip_count"] = max(0, safe_float(topics.loc[idx, "skip_count"], 0) - 1) | |
| else: | |
| topics.loc[idx, "skip_count"] = safe_float(topics.loc[idx, "skip_count"], 0) + 1 | |
| save_topics(topics) | |
| logs = load_logs() | |
| new_log = pd.DataFrame([{ | |
| "date": str(date.today()), | |
| "subject": subject, | |
| "topic": topic, | |
| "action": action, | |
| "completed": completed_bool, | |
| "study_minutes": safe_float(study_minutes, 0), | |
| "old_score": old_score, | |
| "new_score": new_score, | |
| "reward": reward | |
| }]) | |
| logs = pd.concat([logs, new_log], ignore_index=True) | |
| save_logs(logs) | |
| msg = f""" | |
| # Feedback Saved | |
| **Topic:** {subject} → {topic} | |
| **Reward:** {reward} points | |
| **Q-value updated:** {round(old_q, 4)} → {round(new_q, 4)} | |
| The planner will use this feedback to improve your next recommendation. | |
| """ | |
| return msg, topics, logs, make_subject_progress_chart() | |
| def difficulty_to_confidence(difficulty_after, current_confidence): | |
| current = safe_float(current_confidence, 5) | |
| if difficulty_after == "Easy": | |
| return min(10, current + 1) | |
| if difficulty_after == "Medium": | |
| return current | |
| if difficulty_after == "Hard": | |
| return max(1, current - 1) | |
| return current | |
| def weak_topics_table(): | |
| topics = load_topics() | |
| if topics.empty: | |
| return pd.DataFrame() | |
| df = topics.copy() | |
| df["weakness"] = 100 - df["quiz_score"] | |
| df = df.sort_values(["weakness", "importance"], ascending=False) | |
| return df[["subject", "topic", "quiz_score", "confidence", "importance", "difficulty", "completed"]].head(10) | |
| def ai_coach(question): | |
| profile = load_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| topics = load_topics() | |
| readiness = calculate_readiness() | |
| risk = calculate_risk_label(readiness) | |
| days_left = get_days_left() | |
| if topics.empty: | |
| return "Add some subjects and topics first. Then I can coach you based on your progress." | |
| weakest = topics.sort_values(["quiz_score", "confidence"], ascending=[True, True]).head(1).iloc[0] | |
| strongest = topics.sort_values(["quiz_score", "confidence"], ascending=[False, False]).head(1).iloc[0] | |
| q = (question or "").lower() | |
| if "why" in q or "keno" in q or "কেন" in q: | |
| return f""" | |
| You should focus on **{weakest['subject']} → {weakest['topic']}** because your quiz score is **{weakest['quiz_score']}%**, confidence is **{weakest['confidence']}/10**, and importance is **{weakest['importance']}/10**. | |
| Exam is in **{days_left} days**, so weak and important topics should get priority. | |
| """ | |
| if "motivation" in q or "motivate" in q: | |
| return f""" | |
| You do not need to finish everything today. Your only goal is to improve one weak topic. | |
| Start with **{weakest['topic']}** for 25 minutes. Small progress daily beats panic study before exam. | |
| """ | |
| if "risk" in q or "readiness" in q: | |
| return f""" | |
| Your current readiness is **{readiness}%** and your risk level is **{risk}**. | |
| Weakest topic: **{weakest['subject']} → {weakest['topic']}** | |
| Strongest topic: **{strongest['subject']} → {strongest['topic']}** | |
| Improve weak topics first to reduce exam risk. | |
| """ | |
| return f""" | |
| Based on your current data: | |
| - Readiness: **{readiness}%** | |
| - Risk: **{risk}** | |
| - Days left: **{days_left}** | |
| - Weakest topic: **{weakest['subject']} → {weakest['topic']}** | |
| - Best topic: **{strongest['subject']} → {strongest['topic']}** | |
| Recommended next step: study your weakest high-importance topic first, then take a short quiz. | |
| """ | |
| def reset_demo_data(): | |
| save_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| subjects = pd.DataFrame([ | |
| {"subject": "Math", "importance": 10, "confidence": 3}, | |
| {"subject": "Physics", "importance": 9, "confidence": 4}, | |
| {"subject": "English", "importance": 7, "confidence": 6} | |
| ]) | |
| save_subjects(subjects) | |
| topics = pd.DataFrame([ | |
| { | |
| "subject": "Math", | |
| "topic": "Differentiation", | |
| "difficulty": 9, | |
| "importance": 10, | |
| "estimated_hours": 5, | |
| "confidence": 3, | |
| "quiz_score": 35, | |
| "completed": False, | |
| "last_studied": str(date.today()), | |
| "skip_count": 1 | |
| }, | |
| { | |
| "subject": "Physics", | |
| "topic": "Current Electricity", | |
| "difficulty": 8, | |
| "importance": 9, | |
| "estimated_hours": 4, | |
| "confidence": 4, | |
| "quiz_score": 42, | |
| "completed": False, | |
| "last_studied": str(date.today()), | |
| "skip_count": 0 | |
| }, | |
| { | |
| "subject": "English", | |
| "topic": "Writing Part", | |
| "difficulty": 6, | |
| "importance": 8, | |
| "estimated_hours": 3, | |
| "confidence": 6, | |
| "quiz_score": 65, | |
| "completed": False, | |
| "last_studied": str(date.today()), | |
| "skip_count": 0 | |
| } | |
| ]) | |
| save_topics(topics) | |
| save_json(Q_TABLE_FILE, {}) | |
| save_logs(pd.DataFrame(columns=[ | |
| "date", "subject", "topic", "action", "completed", | |
| "study_minutes", "old_score", "new_score", "reward" | |
| ])) | |
| return "Demo data loaded.", subjects, topics, load_logs(), get_profile_summary(), make_subject_progress_chart() | |
| def clear_all_data(): | |
| save_json(PROFILE_FILE, DEFAULT_PROFILE) | |
| save_subjects(pd.DataFrame(columns=["subject", "importance", "confidence"])) | |
| save_topics(pd.DataFrame(columns=[ | |
| "subject", "topic", "difficulty", "importance", "estimated_hours", | |
| "confidence", "quiz_score", "completed", "last_studied", "skip_count" | |
| ])) | |
| save_json(Q_TABLE_FILE, {}) | |
| save_logs(pd.DataFrame(columns=[ | |
| "date", "subject", "topic", "action", "completed", | |
| "study_minutes", "old_score", "new_score", "reward" | |
| ])) | |
| return "All data cleared.", load_subjects(), load_topics(), load_logs(), get_profile_summary(), None | |
| # ========================================================= | |
| # Gradio UI | |
| # ========================================================= | |
| custom_css = """ | |
| #main-title { | |
| text-align: center; | |
| margin-bottom: 10px; | |
| } | |
| .metric-card { | |
| border-radius: 14px; | |
| padding: 16px; | |
| } | |
| """ | |
| with gr.Blocks( | |
| theme=gr.themes.Soft(), | |
| css=custom_css, | |
| title="StudyPilot AI" | |
| ) as demo: | |
| gr.Markdown( | |
| """ | |
| # StudyPilot AI | |
| ### Reinforcement Learning Based Smart Study Planner | |
| Dynamic subjects, adaptive topic recommendation, quiz feedback, reward system, and progress analytics. | |
| """, | |
| elem_id="main-title" | |
| ) | |
| with gr.Tab("Dashboard"): | |
| dashboard_md = gr.Markdown(get_profile_summary()) | |
| refresh_btn = gr.Button("Refresh Dashboard") | |
| subject_chart = gr.Plot(value=make_subject_progress_chart()) | |
| weak_table = gr.Dataframe(value=weak_topics_table(), label="Top Weak Topics", interactive=False) | |
| refresh_btn.click( | |
| fn=lambda: (get_profile_summary(), make_subject_progress_chart(), weak_topics_table()), | |
| outputs=[dashboard_md, subject_chart, weak_table] | |
| ) | |
| with gr.Tab("Student Profile"): | |
| with gr.Row(): | |
| name = gr.Textbox(label="Student Name", value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("name", "Student")) | |
| exam_type = gr.Textbox(label="Exam Type", value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("exam_type", "HSC")) | |
| with gr.Row(): | |
| exam_date = gr.Textbox(label="Exam Date YYYY-MM-DD", value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("exam_date", str(date.today()))) | |
| daily_hours = gr.Number(label="Daily Study Hours", value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("daily_hours", 3)) | |
| with gr.Row(): | |
| target_grade = gr.Textbox(label="Target Grade", value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("target_grade", "A+")) | |
| study_mode = gr.Dropdown( | |
| choices=["Balanced", "Weakness Killer", "Revision Focused", "Exam Crash", "Confidence Builder"], | |
| value=load_json(PROFILE_FILE, DEFAULT_PROFILE).get("study_mode", "Balanced"), | |
| label="Study Mode" | |
| ) | |
| save_profile_btn = gr.Button("Save Profile") | |
| profile_status = gr.Markdown() | |
| save_profile_btn.click( | |
| fn=save_profile, | |
| inputs=[name, exam_type, exam_date, daily_hours, target_grade, study_mode], | |
| outputs=profile_status | |
| ) | |
| with gr.Tab("Subject Manager"): | |
| gr.Markdown("### Add any subject. Example: Physics, Accounting, Marketing, Programming, English, Biology.") | |
| with gr.Row(): | |
| subject_input = gr.Textbox(label="Subject Name") | |
| subject_importance = gr.Slider(1, 10, value=7, step=1, label="Subject Importance") | |
| subject_confidence = gr.Slider(1, 10, value=5, step=1, label="Current Confidence") | |
| add_subject_btn = gr.Button("Add Subject") | |
| subject_status = gr.Markdown() | |
| subject_table = gr.Dataframe(value=load_subjects(), label="Subjects", interactive=False) | |
| add_subject_btn.click( | |
| fn=add_subject, | |
| inputs=[subject_input, subject_importance, subject_confidence], | |
| outputs=[subject_status, subject_table] | |
| ) | |
| with gr.Tab("Topic Manager"): | |
| gr.Markdown("### Add topics under any subject.") | |
| with gr.Row(): | |
| topic_subject = gr.Textbox(label="Subject Name") | |
| topic_name = gr.Textbox(label="Topic Name") | |
| with gr.Row(): | |
| topic_difficulty = gr.Slider(1, 10, value=5, step=1, label="Topic Difficulty") | |
| topic_importance = gr.Slider(1, 10, value=7, step=1, label="Exam Importance") | |
| estimated_hours = gr.Number(label="Estimated Hours Needed", value=2) | |
| with gr.Row(): | |
| topic_confidence = gr.Slider(1, 10, value=5, step=1, label="Your Confidence") | |
| quiz_score = gr.Slider(0, 100, value=0, step=1, label="Current Quiz Score") | |
| add_topic_btn = gr.Button("Add Topic") | |
| topic_status = gr.Markdown() | |
| topic_table = gr.Dataframe(value=load_topics(), label="Topics", interactive=False) | |
| add_topic_btn.click( | |
| fn=add_topic, | |
| inputs=[ | |
| topic_subject, topic_name, topic_difficulty, topic_importance, | |
| estimated_hours, topic_confidence, quiz_score | |
| ], | |
| outputs=[topic_status, topic_table] | |
| ) | |
| with gr.Tab("Smart Study Plan"): | |
| gr.Markdown("### Generate today’s adaptive study plan.") | |
| max_topics = gr.Slider(1, 6, value=3, step=1, label="Number of Topics for Today") | |
| generate_btn = gr.Button("Generate Today’s Plan") | |
| plan_md = gr.Markdown() | |
| plan_table = gr.Dataframe(label="Recommended Plan", interactive=False) | |
| priority_plot = gr.Plot() | |
| generate_btn.click( | |
| fn=generate_today_plan, | |
| inputs=max_topics, | |
| outputs=[plan_md, plan_table, priority_plot] | |
| ) | |
| with gr.Tab("Quiz & Feedback"): | |
| gr.Markdown("### After studying, give feedback. This updates the RL memory.") | |
| topic_dropdown = gr.Dropdown(choices=get_topic_choices(), label="Select Topic") | |
| refresh_topics_btn = gr.Button("Refresh Topic List") | |
| action_dropdown = gr.Dropdown(choices=ACTIONS, value="practice_questions", label="Action Taken") | |
| completed_checkbox = gr.Checkbox(label="Completed?", value=True) | |
| study_minutes = gr.Number(label="Study Minutes", value=60) | |
| new_score = gr.Slider(0, 100, value=50, step=1, label="New Quiz Score") | |
| difficulty_after = gr.Dropdown(choices=["Easy", "Medium", "Hard"], value="Medium", label="How did the topic feel?") | |
| feedback_btn = gr.Button("Submit Feedback") | |
| feedback_output = gr.Markdown() | |
| updated_topic_table = gr.Dataframe(value=load_topics(), label="Updated Topics", interactive=False) | |
| log_table = gr.Dataframe(value=load_logs(), label="Study Logs", interactive=False) | |
| feedback_chart = gr.Plot(value=make_subject_progress_chart()) | |
| refresh_topics_btn.click( | |
| fn=lambda: gr.update(choices=get_topic_choices()), | |
| outputs=topic_dropdown | |
| ) | |
| feedback_btn.click( | |
| fn=submit_feedback, | |
| inputs=[topic_dropdown, action_dropdown, completed_checkbox, study_minutes, new_score, difficulty_after], | |
| outputs=[feedback_output, updated_topic_table, log_table, feedback_chart] | |
| ) | |
| with gr.Tab("AI Coach"): | |
| gr.Markdown("### Ask the study coach. Example: Why should I study Math today?") | |
| coach_question = gr.Textbox(label="Your Question", placeholder="Ask about weakness, risk, motivation, or next topic...") | |
| coach_btn = gr.Button("Ask Coach") | |
| coach_answer = gr.Markdown() | |
| coach_btn.click( | |
| fn=ai_coach, | |
| inputs=coach_question, | |
| outputs=coach_answer | |
| ) | |
| with gr.Tab("Admin / Demo"): | |
| gr.Markdown("Use demo data for testing on Hugging Face.") | |
| demo_btn = gr.Button("Load Demo Data") | |
| clear_btn = gr.Button("Clear All Data") | |
| admin_status = gr.Markdown() | |
| admin_subjects = gr.Dataframe(value=load_subjects(), label="Subjects") | |
| admin_topics = gr.Dataframe(value=load_topics(), label="Topics") | |
| admin_logs = gr.Dataframe(value=load_logs(), label="Logs") | |
| admin_dashboard = gr.Markdown(get_profile_summary()) | |
| admin_chart = gr.Plot(value=make_subject_progress_chart()) | |
| demo_btn.click( | |
| fn=reset_demo_data, | |
| outputs=[admin_status, admin_subjects, admin_topics, admin_logs, admin_dashboard, admin_chart] | |
| ) | |
| clear_btn.click( | |
| fn=clear_all_data, | |
| outputs=[admin_status, admin_subjects, admin_topics, admin_logs, admin_dashboard, admin_chart] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |