From ca344776b6eb99f5aaa84e457bbc3fc03af681fe Mon Sep 17 00:00:00 2001 From: manasvi-maheshwari Date: Wed, 8 Apr 2026 23:06:58 +0530 Subject: [PATCH 1/2] Add files via upload --- README.md | 72 +++++++++++ app.py | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ brain.py | 28 +++++ logic.py | 61 ++++++++++ 4 files changed, 512 insertions(+) create mode 100644 README.md create mode 100644 app.py create mode 100644 brain.py create mode 100644 logic.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef752fc --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# ๐Ÿง  Synapse โ€” AI-Powered Study Planner + +Synapse is an intelligent, personalized study planner that uses **Reinforcement Learning (Q-Learning)** to generate optimized study roadmaps based on your strengths and weaknesses. + +It combines AI decision-making with a clean GUI to help you focus on what matters most. + +--- + +## ๐Ÿš€ Features + +- ๐ŸŽฏ Personalized Study Plan based on your current skill levels +- ๐Ÿง  AI-Powered Scheduling using Q-learning +- ๐Ÿ“Š Adaptive Learning โ€“ improves recommendations over time +- ๐Ÿ”„ Balanced Subject Distribution (avoids repetition & burnout) +- ๐Ÿ’ก Actionable Tasks for each subject (Beginner โ†’ Advanced) +- ๐Ÿ–ฅ๏ธ Modern GUI built with Tkinter + +--- + +## ๐Ÿ—‚๏ธ Project Structure + +. +โ”œโ”€โ”€ app.py +โ”œโ”€โ”€ brain.py +โ”œโ”€โ”€ logic.py +โ””โ”€โ”€ q_table.json + +--- + +## โš™๏ธ How It Works + +1. Input your confidence levels for each subject (0โ€“100%) +2. The AI identifies weak areas +3. Generates a study sequence using reinforcement learning +4. Displays a roadmap with actionable tasks + +--- + +## ๐Ÿง  AI Approach + +- Uses Q-Learning +- State: (time_slot, weakest_subject) +- Action: next subject to study +- Reward system encourages improvement and balance + +--- + +## ๐Ÿ–ฅ๏ธ Installation & Setup + +### Clone the repository +git clone https://github.com/manasvi-maheshwari/synapse-study-planner.git + +### Install dependencies +pip install numpy + +### Run the application +python app.py + +--- + +## ๐Ÿ”ฎ Future Improvements + +- Data visualization +- Web & mobile versions +- Cloud sync +- More customization + +--- + +## ๐Ÿ“„ License + +MIT License diff --git a/app.py b/app.py new file mode 100644 index 0000000..d3bf7ca --- /dev/null +++ b/app.py @@ -0,0 +1,351 @@ +import tkinter as tk +from tkinter import ttk +from brain import StudyAI +from logic import run_simulation + +# โ”€โ”€ DESIGN SYSTEM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +UI = { + # Warm off-white / parchment palette + "bg": "#F5F0E8", # warm parchment base + "surface": "#FDFAF4", # card surface, soft cream + "surface2": "#EDE8DC", # subtle inset / sidebar bg + "border": "#DDD8CC", # gentle divider + "border_dark": "#C8C0B0", # slightly stronger border + + # Terracotta + slate accent system + "accent": "#C0654A", # terracotta / rust โ€“ main CTA + "accent_light": "#E8927A", # lighter hover tint + "accent_muted": "#F0DDD7", # very soft tint background + "green": "#4A8C6A", # positive / growth indicator + "green_muted": "#D6EAE0", # soft green background + + # Typography + "text_h1": "#1E1A14", # near-black headings + "text_body": "#5A5248", # warm mid-tone body + "text_muted": "#9C9488", # placeholder / label text + + # Fonts โ€“ warm humanist choices + "font_logo": ("Georgia", 17, "bold"), + "font_sub": ("Georgia", 9, "italic"), + "font_h2": ("Georgia", 11, "bold"), + "font_label": ("Helvetica", 8), + "font_label_b": ("Helvetica", 8, "bold"), + "font_body": ("Helvetica", 10), + "font_body_b": ("Helvetica", 10, "bold"), + "font_number": ("Georgia", 20, "bold"), + "font_pct": ("Georgia", 13, "bold"), + "font_small": ("Helvetica", 7), +} + +SUBJECT_ICONS = { + "Python": "โŒจ", + "Java": "โ˜•", + "DSA": "โއ", + "Aptitude": "โŠž", + "Communication": "โ‹", +} + +class Synapse(tk.Tk): + def __init__(self): + super().__init__() + self.title("Synapse โ€” Study Planner") + self.geometry("1060x740") + self.configure(bg=UI["bg"]) + self.resizable(True, True) + + self.diffs = { + "Python": 0.9, "Java": 0.8, "DSA": 0.9, + "Aptitude": 0.5, "Communication": 0.3 + } + self.subjs = list(self.diffs.keys()) + self.ai = StudyAI(self.subjs) + self.ai.load() + + self.vars = {s: tk.IntVar(value=40) for s in self.subjs} + self._setup() + + def _setup(self): + # โ”€โ”€ Top nav bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + nav = tk.Frame(self, bg=UI["surface"], pady=0) + nav.pack(fill="x") + + # Thin accent stripe at very top + tk.Frame(nav, bg=UI["accent"], height=3).pack(fill="x") + + nav_inner = tk.Frame(nav, bg=UI["surface"], padx=48, pady=18) + nav_inner.pack(fill="x") + + # Logo + logo_frame = tk.Frame(nav_inner, bg=UI["surface"]) + logo_frame.pack(side="left") + tk.Label(logo_frame, text="Synapse", font=UI["font_logo"], + bg=UI["surface"], fg=UI["text_h1"]).pack(side="left") + + # Nav tagline right + tk.Label(nav_inner, text="Personalised ยท AI-Powered ยท Actionable", + font=UI["font_small"], bg=UI["surface"], fg=UI["text_muted"]).pack(side="right") + + # Thin border below nav + tk.Frame(self, bg=UI["border"], height=1).pack(fill="x") + + # โ”€โ”€ Main layout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + main = tk.Frame(self, bg=UI["bg"]) + main.pack(fill="both", expand=True) + main.columnconfigure(0, weight=0) # sidebar fixed + main.columnconfigure(1, weight=1) # content expands + main.rowconfigure(0, weight=1) + + # โ”€โ”€ Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + aside = tk.Frame(main, bg=UI["surface2"]) + aside.grid(row=0, column=0, sticky="nsew") + main.columnconfigure(0, minsize=300, weight=0) + + aside_inner = tk.Frame(aside, bg=UI["surface2"], padx=30, pady=32) + aside_inner.pack(fill="both", expand=True) + + tk.Label(aside_inner, text="Skill Matrix", + font=UI["font_h2"], bg=UI["surface2"], fg=UI["text_h1"]).pack(anchor="w") + tk.Label(aside_inner, text="Rate your current confidence", + font=UI["font_label"], bg=UI["surface2"], fg=UI["text_muted"]).pack(anchor="w", pady=(2, 20)) + + # Skill sliders + for s in self.subjs: + card = tk.Frame(aside_inner, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"]) + card.pack(fill="x", pady=6) + + card_inner = tk.Frame(card, bg=UI["surface"], padx=14, pady=12) + card_inner.pack(fill="x") + + # Header row + head = tk.Frame(card_inner, bg=UI["surface"]) + head.pack(fill="x") + + icon = SUBJECT_ICONS.get(s, "โ€ข") + tk.Label(head, text=f"{icon} {s}", + font=UI["font_body_b"], bg=UI["surface"], fg=UI["text_h1"]).pack(side="left") + + v_lbl = tk.Label(head, text=f"{self.vars[s].get()}%", + font=UI["font_pct"], bg=UI["surface"], fg=UI["accent"]) + v_lbl.pack(side="right") + + # Custom rounded-feel trough via ttk style + style_name = f"{s}.Horizontal.TScale" + + slider = tk.Scale( + card_inner, from_=0, to=100, orient="horizontal", + variable=self.vars[s], + bg=UI["surface"], fg=UI["text_body"], + highlightthickness=0, + troughcolor=UI["border"], + activebackground=UI["accent_light"], + showvalue=False, width=5, sliderlength=18, + command=lambda v, l=v_lbl: l.config(text=f"{int(float(v))}%") + ) + slider.pack(fill="x", pady=(8, 0)) + + # Confidence hint + hint_frame = tk.Frame(card_inner, bg=UI["surface"]) + hint_frame.pack(fill="x", pady=(4, 0)) + for label, pos in [("Beginner", "left"), ("Expert", "right")]: + tk.Label(hint_frame, text=label, font=UI["font_small"], + bg=UI["surface"], fg=UI["text_muted"]).pack(side=pos) + + # Spacer + tk.Frame(aside_inner, bg=UI["surface2"], height=10).pack() + + # CTA button + btn_frame = tk.Frame(aside_inner, bg=UI["surface2"]) + btn_frame.pack(fill="x", pady=(16, 0)) + + self.btn = tk.Button( + btn_frame, text="Generate My Roadmap โ†’", + font=UI["font_body_b"], + bg=UI["accent"], fg="#FDFAF4", + relief="flat", pady=14, padx=20, + cursor="hand2", activebackground=UI["accent_light"], + activeforeground="#FDFAF4", + command=self.sync + ) + self.btn.pack(fill="x") + + tk.Label(aside_inner, text="Results update instantly each time", + font=UI["font_small"], bg=UI["surface2"], fg=UI["text_muted"]).pack(pady=(8, 0)) + + # Vertical divider + tk.Frame(main, bg=UI["border"], width=1).grid(row=0, column=0, sticky="nse") + + # โ”€โ”€ Roadmap panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + right = tk.Frame(main, bg=UI["bg"]) + right.grid(row=0, column=1, sticky="nsew") + + # Panel header + panel_head = tk.Frame(right, bg=UI["bg"], padx=44, pady=28) + panel_head.pack(fill="x") + + tk.Label(panel_head, text="Your Study Roadmap", + font=("Georgia", 15, "bold"), bg=UI["bg"], fg=UI["text_h1"]).pack(side="left") + + self.badge = tk.Label(panel_head, text="", + font=UI["font_label_b"], bg=UI["green_muted"], fg=UI["green"], + padx=10, pady=4) + self.badge.pack(side="right", pady=(4, 0)) + + tk.Frame(right, bg=UI["border"], height=1).pack(fill="x", padx=44) + + # Scrollable content area (using canvas for scroll support) + self.content_frame = tk.Frame(right, bg=UI["bg"]) + self.content_frame.pack(fill="both", expand=True, padx=44, pady=20) + + # Empty state + self.hint_frame = tk.Frame(self.content_frame, bg=UI["bg"]) + self.hint_frame.pack(expand=True, fill="both") + + empty_box = tk.Frame(self.hint_frame, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"], + padx=40, pady=50) + empty_box.place(relx=0.5, rely=0.45, anchor="center") + + tk.Label(empty_box, text="โœฆ", font=("Georgia", 28), + bg=UI["surface"], fg=UI["border"]).pack() + tk.Label(empty_box, text="No roadmap yet", + font=("Georgia", 13, "bold"), bg=UI["surface"], fg=UI["text_body"]).pack(pady=(12, 4)) + tk.Label(empty_box, text="Adjust your skill levels on the left\nand click Generate to begin.", + font=UI["font_body"], bg=UI["surface"], fg=UI["text_muted"], + justify="center").pack() + + def sync(self): + # Clear content + for w in self.content_frame.winfo_children(): + w.destroy() + + scores = {s: self.vars[s].get() / 100.0 for s in self.subjs} + plan = run_simulation(self.ai, self.subjs, self.diffs, scores, explore=False) + + total_gain = sum(step["gain"] for step in plan) + self.badge.config(text=f" Est. total growth +{total_gain:.0%} ") + + for i, step in enumerate(plan): + self._render_step(i, step, len(plan), scores) + + # โ”€โ”€ Subject-specific plan content โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + PLANS = { + "Python": { + "beginner": ["Learn variables, loops, and functions", "Complete 10 beginner exercises on HackerRank", "Build a simple calculator or to-do CLI app"], + "intermediate": ["Study OOP: classes, inheritance, decorators", "Practice file I/O and exception handling", "Build a REST API with Flask or FastAPI"], + "advanced": ["Deep-dive into generators, asyncio, and metaclasses", "Optimise with profiling tools (cProfile, line_profiler)", "Contribute to an open-source Python project"], + }, + "Java": { + "beginner": ["Understand JVM basics, types, and control flow", "Practice OOP: classes, interfaces, and inheritance", "Build a simple bank account simulator"], + "intermediate": ["Study Collections, Generics, and Streams API", "Learn exception handling and multithreading basics", "Build a CRUD app with Spring Boot"], + "advanced": ["Master concurrency: locks, executors, CompletableFuture", "Explore JVM internals and memory management", "Design a microservice with Spring Boot + Docker"], + }, + "DSA": { + "beginner": ["Revise arrays, strings, and basic recursion", "Solve 5 easy LeetCode problems daily", "Understand time/space complexity (Big-O)"], + "intermediate": ["Study linked lists, stacks, queues, and trees", "Solve medium LeetCode problems on sliding window & BFS/DFS", "Practice sorting algorithms from scratch"], + "advanced": ["Master dynamic programming patterns", "Study graphs: Dijkstra, Floyd-Warshall, topological sort", "Mock interview: 2 hard problems per week timed"], + }, + "Aptitude": { + "beginner": ["Review percentages, ratios, and averages", "Attempt 20 questions daily from IndiaBix", "Focus on speed: use shortcuts for multiplication"], + "intermediate": ["Practice time & work, pipes & cisterns, profit & loss", "Attempt sectional mock tests (30 min per topic)", "Analyse mistakes and revisit weak areas weekly"], + "advanced": ["Solve full-length aptitude papers (TCS, Infosys pattern)", "Work on data interpretation and logical reasoning", "Target sub-60 sec per question consistently"], + }, + "Communication": { + "beginner": ["Read aloud for 10 minutes daily to build fluency", "Record yourself speaking and review for filler words", "Practice introducing yourself clearly in 60 seconds"], + "intermediate": ["Join a group discussion or debate practice session", "Write one structured email or summary per day", "Watch TED Talks and note structure & delivery techniques"], + "advanced": ["Present a 5-minute talk on a technical topic weekly", "Practise mock GDs and HR interview rounds", "Get feedback from peers and iterate on weak points"], + }, + } + + def _get_plan_tier(self, score): + if score < 0.4: return "beginner" + if score < 0.7: return "intermediate" + return "advanced" + + def _render_step(self, i, step, total, scores): + subj = step["subj"] + score = scores[subj] + tier = self._get_plan_tier(score) + tasks = self.PLANS.get(subj, {}).get(tier, ["Study core concepts", "Practice problems", "Review and revise"]) + + # Outer card + card = tk.Frame(self.content_frame, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"]) + card.pack(fill="x", pady=6) + + # โ”€โ”€ Header row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + header = tk.Frame(card, bg=UI["surface"], padx=24, pady=16) + header.pack(fill="x") + + # Step badge + num_badge = tk.Frame(header, bg=UI["accent_muted"], width=40, height=40) + num_badge.pack(side="left") + num_badge.pack_propagate(False) + tk.Label(num_badge, text=str(i + 1), font=("Georgia", 13, "bold"), + bg=UI["accent_muted"], fg=UI["accent"]).place(relx=0.5, rely=0.5, anchor="center") + + # Subject name + tier label + info = tk.Frame(header, bg=UI["surface"]) + info.pack(side="left", padx=(14, 0)) + + icon = SUBJECT_ICONS.get(subj, "โ€ข") + tk.Label(info, text=f"{icon} {subj}", + font=("Georgia", 12, "bold"), bg=UI["surface"], fg=UI["text_h1"]).pack(anchor="w") + + tier_colors = { + "beginner": (UI["accent_muted"], UI["accent"]), + "intermediate": ("#FFF3DC", "#B07D20"), + "advanced": (UI["green_muted"], UI["green"]), + } + tc_bg, tc_fg = tier_colors[tier] + tk.Label(info, text=f" {tier.capitalize()} level ", + font=UI["font_small"], bg=tc_bg, fg=tc_fg).pack(anchor="w", pady=(3, 0)) + + # Growth pill (right side) + gain_frame = tk.Frame(header, bg=UI["green_muted"], + highlightthickness=1, highlightbackground="#B8DCCB") + gain_frame.pack(side="right") + gain_inner = tk.Frame(gain_frame, bg=UI["green_muted"], padx=14, pady=8) + gain_inner.pack() + tk.Label(gain_inner, text=f"+{step['gain']:.1%}", + font=("Georgia", 13, "bold"), bg=UI["green_muted"], fg=UI["green"]).pack() + tk.Label(gain_inner, text="est. growth", + font=UI["font_small"], bg=UI["green_muted"], fg=UI["green"]).pack() + + # โ”€โ”€ Divider โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + tk.Frame(card, bg=UI["border"], height=1).pack(fill="x", padx=24) + + # โ”€โ”€ Task list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + tasks_frame = tk.Frame(card, bg=UI["surface"], padx=24, pady=14) + tasks_frame.pack(fill="x") + + tk.Label(tasks_frame, text="Recommended actions", + font=UI["font_label_b"], bg=UI["surface"], fg=UI["text_muted"]).pack(anchor="w", pady=(0, 8)) + + for t, task in enumerate(tasks): + row = tk.Frame(tasks_frame, bg=UI["surface"]) + row.pack(fill="x", pady=3) + tk.Label(row, text=f"{t+1}.", font=UI["font_label_b"], + bg=UI["surface"], fg=UI["accent"], width=2).pack(side="left") + tk.Label(row, text=task, font=UI["font_body"], + bg=UI["surface"], fg=UI["text_body"], anchor="w").pack(side="left", padx=(6, 0)) + + # โ”€โ”€ Progress bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + bar_frame = tk.Frame(card, bg=UI["surface"], padx=24, pady=(4, 14)) + bar_frame.pack(fill="x") + + bar_bg = tk.Frame(bar_frame, bg=UI["border"], height=4) + bar_bg.pack(fill="x") + + fill_width = min(1.0, step["gain"] * 5) + + def draw_bar(event, bg=bar_bg, fw=fill_width): + w = bg.winfo_width() + bar_fill = tk.Frame(bg, bg=UI["green"], height=4, width=int(w * fw)) + bar_fill.place(x=0, y=0) + + bar_bg.bind("", draw_bar) + + +if __name__ == "__main__": + Synapse().mainloop() \ No newline at end of file diff --git a/brain.py b/brain.py new file mode 100644 index 0000000..6307005 --- /dev/null +++ b/brain.py @@ -0,0 +1,28 @@ +import numpy as np +import json +import os + +class StudyAI: + def __init__(self, subjects, slots=6): + self.subjects = subjects + self.q = np.zeros((slots + 1, len(subjects), len(subjects))) + + def choose(self, slot, weakest_idx, explore=True): + import random + if explore and random.random() < 0.2: + return random.randint(0, len(self.subjects) - 1) + return int(np.argmax(self.q[slot][weakest_idx])) + + def learn(self, slot, weakest, choice, reward, next_slot, next_weakest): + cur = self.q[slot][weakest][choice] + best = np.max(self.q[next_slot][next_weakest]) + self.q[slot][weakest][choice] += 0.1 * (reward + 0.9 * best - cur) + + def save(self, file="q_table.json"): + with open(file, "w") as f: json.dump(self.q.tolist(), f) + + def load(self, file="q_table.json"): + if os.path.exists(file): + with open(file) as f: self.q = np.array(json.load(f)) + return True + return False \ No newline at end of file diff --git a/logic.py b/logic.py new file mode 100644 index 0000000..890914c --- /dev/null +++ b/logic.py @@ -0,0 +1,61 @@ +import random + +def run_simulation(ai, subjects, difficulty, scores, slots=6, explore=True): + current_scores = scores.copy() + history = [] + last_choice = -1 + recent_choices = [] # track last N picks for cooldown + subject_counts = {s: 0 for s in subjects} + n = len(subjects) + + # Ensure slots >= number of subjects so every subject gets at least one slot + slots = max(slots, n) + + for slot in range(slots): + weakest_idx = min(range(n), key=lambda i: current_scores[subjects[i]]) + + # --- Cooldown mask: block subjects picked in last (n-1) turns --- + cooldown = max(1, n - 1) + blocked = set(recent_choices[-cooldown:]) if len(subjects) > 1 else set() + + # Force-include any subject that has had zero slots so far + unvisited = [i for i in range(n) if subject_counts[subjects[i]] == 0] + if unvisited: + # Pick the weakest unvisited subject + choice = min(unvisited, key=lambda i: current_scores[subjects[i]]) + else: + # Let AI choose, but mask out recently used subjects + raw_choice = ai.choose(slot, weakest_idx, explore) + if raw_choice in blocked: + # Fall back to weakest subject not on cooldown + available = [i for i in range(n) if i not in blocked] + if not available: + available = list(range(n)) # safety valve + choice = min(available, key=lambda i: current_scores[subjects[i]]) + else: + choice = raw_choice + + subj = subjects[choice] + subject_counts[subj] += 1 + + # --- Gain: diminishing returns as score rises --- + gap = 1.0 - current_scores[subj] + gain = gap * difficulty[subj] * random.uniform(0.12, 0.22) + current_scores[subj] = min(1.0, current_scores[subj] + gain) + + # --- Reward shaping --- + reward = gain * 20 + if choice == weakest_idx: + reward += 5 # good: tackled weakest + if choice == last_choice: + reward -= 15 # strong repetition penalty + if subject_counts[subj] > slots // n + 1: + reward -= 10 # penalise over-concentration + + ai.learn(slot, weakest_idx, choice, reward, slot + 1, weakest_idx) + history.append({"subj": subj, "score": current_scores[subj], "gain": gain}) + + recent_choices.append(choice) + last_choice = choice + + return history \ No newline at end of file From 62ed5213e2ba650bd362ea561dcf122cbf4517ec Mon Sep 17 00:00:00 2001 From: Manasvi Maheshwari Date: Thu, 9 Apr 2026 00:00:34 +0530 Subject: [PATCH 2/2] 510_23BAI10157_23BAI10274 --- 510_23BAI10157_23BAI10274/README.md | 72 ++++++ 510_23BAI10157_23BAI10274/app.py | 351 ++++++++++++++++++++++++++++ 510_23BAI10157_23BAI10274/brain.py | 28 +++ 510_23BAI10157_23BAI10274/logic.py | 61 +++++ CAM DATA B21 RL.xlsx | Bin 13345 -> 0 bytes 5 files changed, 512 insertions(+) create mode 100644 510_23BAI10157_23BAI10274/README.md create mode 100644 510_23BAI10157_23BAI10274/app.py create mode 100644 510_23BAI10157_23BAI10274/brain.py create mode 100644 510_23BAI10157_23BAI10274/logic.py delete mode 100644 CAM DATA B21 RL.xlsx diff --git a/510_23BAI10157_23BAI10274/README.md b/510_23BAI10157_23BAI10274/README.md new file mode 100644 index 0000000..ef752fc --- /dev/null +++ b/510_23BAI10157_23BAI10274/README.md @@ -0,0 +1,72 @@ +# ๐Ÿง  Synapse โ€” AI-Powered Study Planner + +Synapse is an intelligent, personalized study planner that uses **Reinforcement Learning (Q-Learning)** to generate optimized study roadmaps based on your strengths and weaknesses. + +It combines AI decision-making with a clean GUI to help you focus on what matters most. + +--- + +## ๐Ÿš€ Features + +- ๐ŸŽฏ Personalized Study Plan based on your current skill levels +- ๐Ÿง  AI-Powered Scheduling using Q-learning +- ๐Ÿ“Š Adaptive Learning โ€“ improves recommendations over time +- ๐Ÿ”„ Balanced Subject Distribution (avoids repetition & burnout) +- ๐Ÿ’ก Actionable Tasks for each subject (Beginner โ†’ Advanced) +- ๐Ÿ–ฅ๏ธ Modern GUI built with Tkinter + +--- + +## ๐Ÿ—‚๏ธ Project Structure + +. +โ”œโ”€โ”€ app.py +โ”œโ”€โ”€ brain.py +โ”œโ”€โ”€ logic.py +โ””โ”€โ”€ q_table.json + +--- + +## โš™๏ธ How It Works + +1. Input your confidence levels for each subject (0โ€“100%) +2. The AI identifies weak areas +3. Generates a study sequence using reinforcement learning +4. Displays a roadmap with actionable tasks + +--- + +## ๐Ÿง  AI Approach + +- Uses Q-Learning +- State: (time_slot, weakest_subject) +- Action: next subject to study +- Reward system encourages improvement and balance + +--- + +## ๐Ÿ–ฅ๏ธ Installation & Setup + +### Clone the repository +git clone https://github.com/manasvi-maheshwari/synapse-study-planner.git + +### Install dependencies +pip install numpy + +### Run the application +python app.py + +--- + +## ๐Ÿ”ฎ Future Improvements + +- Data visualization +- Web & mobile versions +- Cloud sync +- More customization + +--- + +## ๐Ÿ“„ License + +MIT License diff --git a/510_23BAI10157_23BAI10274/app.py b/510_23BAI10157_23BAI10274/app.py new file mode 100644 index 0000000..d3bf7ca --- /dev/null +++ b/510_23BAI10157_23BAI10274/app.py @@ -0,0 +1,351 @@ +import tkinter as tk +from tkinter import ttk +from brain import StudyAI +from logic import run_simulation + +# โ”€โ”€ DESIGN SYSTEM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +UI = { + # Warm off-white / parchment palette + "bg": "#F5F0E8", # warm parchment base + "surface": "#FDFAF4", # card surface, soft cream + "surface2": "#EDE8DC", # subtle inset / sidebar bg + "border": "#DDD8CC", # gentle divider + "border_dark": "#C8C0B0", # slightly stronger border + + # Terracotta + slate accent system + "accent": "#C0654A", # terracotta / rust โ€“ main CTA + "accent_light": "#E8927A", # lighter hover tint + "accent_muted": "#F0DDD7", # very soft tint background + "green": "#4A8C6A", # positive / growth indicator + "green_muted": "#D6EAE0", # soft green background + + # Typography + "text_h1": "#1E1A14", # near-black headings + "text_body": "#5A5248", # warm mid-tone body + "text_muted": "#9C9488", # placeholder / label text + + # Fonts โ€“ warm humanist choices + "font_logo": ("Georgia", 17, "bold"), + "font_sub": ("Georgia", 9, "italic"), + "font_h2": ("Georgia", 11, "bold"), + "font_label": ("Helvetica", 8), + "font_label_b": ("Helvetica", 8, "bold"), + "font_body": ("Helvetica", 10), + "font_body_b": ("Helvetica", 10, "bold"), + "font_number": ("Georgia", 20, "bold"), + "font_pct": ("Georgia", 13, "bold"), + "font_small": ("Helvetica", 7), +} + +SUBJECT_ICONS = { + "Python": "โŒจ", + "Java": "โ˜•", + "DSA": "โއ", + "Aptitude": "โŠž", + "Communication": "โ‹", +} + +class Synapse(tk.Tk): + def __init__(self): + super().__init__() + self.title("Synapse โ€” Study Planner") + self.geometry("1060x740") + self.configure(bg=UI["bg"]) + self.resizable(True, True) + + self.diffs = { + "Python": 0.9, "Java": 0.8, "DSA": 0.9, + "Aptitude": 0.5, "Communication": 0.3 + } + self.subjs = list(self.diffs.keys()) + self.ai = StudyAI(self.subjs) + self.ai.load() + + self.vars = {s: tk.IntVar(value=40) for s in self.subjs} + self._setup() + + def _setup(self): + # โ”€โ”€ Top nav bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + nav = tk.Frame(self, bg=UI["surface"], pady=0) + nav.pack(fill="x") + + # Thin accent stripe at very top + tk.Frame(nav, bg=UI["accent"], height=3).pack(fill="x") + + nav_inner = tk.Frame(nav, bg=UI["surface"], padx=48, pady=18) + nav_inner.pack(fill="x") + + # Logo + logo_frame = tk.Frame(nav_inner, bg=UI["surface"]) + logo_frame.pack(side="left") + tk.Label(logo_frame, text="Synapse", font=UI["font_logo"], + bg=UI["surface"], fg=UI["text_h1"]).pack(side="left") + + # Nav tagline right + tk.Label(nav_inner, text="Personalised ยท AI-Powered ยท Actionable", + font=UI["font_small"], bg=UI["surface"], fg=UI["text_muted"]).pack(side="right") + + # Thin border below nav + tk.Frame(self, bg=UI["border"], height=1).pack(fill="x") + + # โ”€โ”€ Main layout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + main = tk.Frame(self, bg=UI["bg"]) + main.pack(fill="both", expand=True) + main.columnconfigure(0, weight=0) # sidebar fixed + main.columnconfigure(1, weight=1) # content expands + main.rowconfigure(0, weight=1) + + # โ”€โ”€ Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + aside = tk.Frame(main, bg=UI["surface2"]) + aside.grid(row=0, column=0, sticky="nsew") + main.columnconfigure(0, minsize=300, weight=0) + + aside_inner = tk.Frame(aside, bg=UI["surface2"], padx=30, pady=32) + aside_inner.pack(fill="both", expand=True) + + tk.Label(aside_inner, text="Skill Matrix", + font=UI["font_h2"], bg=UI["surface2"], fg=UI["text_h1"]).pack(anchor="w") + tk.Label(aside_inner, text="Rate your current confidence", + font=UI["font_label"], bg=UI["surface2"], fg=UI["text_muted"]).pack(anchor="w", pady=(2, 20)) + + # Skill sliders + for s in self.subjs: + card = tk.Frame(aside_inner, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"]) + card.pack(fill="x", pady=6) + + card_inner = tk.Frame(card, bg=UI["surface"], padx=14, pady=12) + card_inner.pack(fill="x") + + # Header row + head = tk.Frame(card_inner, bg=UI["surface"]) + head.pack(fill="x") + + icon = SUBJECT_ICONS.get(s, "โ€ข") + tk.Label(head, text=f"{icon} {s}", + font=UI["font_body_b"], bg=UI["surface"], fg=UI["text_h1"]).pack(side="left") + + v_lbl = tk.Label(head, text=f"{self.vars[s].get()}%", + font=UI["font_pct"], bg=UI["surface"], fg=UI["accent"]) + v_lbl.pack(side="right") + + # Custom rounded-feel trough via ttk style + style_name = f"{s}.Horizontal.TScale" + + slider = tk.Scale( + card_inner, from_=0, to=100, orient="horizontal", + variable=self.vars[s], + bg=UI["surface"], fg=UI["text_body"], + highlightthickness=0, + troughcolor=UI["border"], + activebackground=UI["accent_light"], + showvalue=False, width=5, sliderlength=18, + command=lambda v, l=v_lbl: l.config(text=f"{int(float(v))}%") + ) + slider.pack(fill="x", pady=(8, 0)) + + # Confidence hint + hint_frame = tk.Frame(card_inner, bg=UI["surface"]) + hint_frame.pack(fill="x", pady=(4, 0)) + for label, pos in [("Beginner", "left"), ("Expert", "right")]: + tk.Label(hint_frame, text=label, font=UI["font_small"], + bg=UI["surface"], fg=UI["text_muted"]).pack(side=pos) + + # Spacer + tk.Frame(aside_inner, bg=UI["surface2"], height=10).pack() + + # CTA button + btn_frame = tk.Frame(aside_inner, bg=UI["surface2"]) + btn_frame.pack(fill="x", pady=(16, 0)) + + self.btn = tk.Button( + btn_frame, text="Generate My Roadmap โ†’", + font=UI["font_body_b"], + bg=UI["accent"], fg="#FDFAF4", + relief="flat", pady=14, padx=20, + cursor="hand2", activebackground=UI["accent_light"], + activeforeground="#FDFAF4", + command=self.sync + ) + self.btn.pack(fill="x") + + tk.Label(aside_inner, text="Results update instantly each time", + font=UI["font_small"], bg=UI["surface2"], fg=UI["text_muted"]).pack(pady=(8, 0)) + + # Vertical divider + tk.Frame(main, bg=UI["border"], width=1).grid(row=0, column=0, sticky="nse") + + # โ”€โ”€ Roadmap panel โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + right = tk.Frame(main, bg=UI["bg"]) + right.grid(row=0, column=1, sticky="nsew") + + # Panel header + panel_head = tk.Frame(right, bg=UI["bg"], padx=44, pady=28) + panel_head.pack(fill="x") + + tk.Label(panel_head, text="Your Study Roadmap", + font=("Georgia", 15, "bold"), bg=UI["bg"], fg=UI["text_h1"]).pack(side="left") + + self.badge = tk.Label(panel_head, text="", + font=UI["font_label_b"], bg=UI["green_muted"], fg=UI["green"], + padx=10, pady=4) + self.badge.pack(side="right", pady=(4, 0)) + + tk.Frame(right, bg=UI["border"], height=1).pack(fill="x", padx=44) + + # Scrollable content area (using canvas for scroll support) + self.content_frame = tk.Frame(right, bg=UI["bg"]) + self.content_frame.pack(fill="both", expand=True, padx=44, pady=20) + + # Empty state + self.hint_frame = tk.Frame(self.content_frame, bg=UI["bg"]) + self.hint_frame.pack(expand=True, fill="both") + + empty_box = tk.Frame(self.hint_frame, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"], + padx=40, pady=50) + empty_box.place(relx=0.5, rely=0.45, anchor="center") + + tk.Label(empty_box, text="โœฆ", font=("Georgia", 28), + bg=UI["surface"], fg=UI["border"]).pack() + tk.Label(empty_box, text="No roadmap yet", + font=("Georgia", 13, "bold"), bg=UI["surface"], fg=UI["text_body"]).pack(pady=(12, 4)) + tk.Label(empty_box, text="Adjust your skill levels on the left\nand click Generate to begin.", + font=UI["font_body"], bg=UI["surface"], fg=UI["text_muted"], + justify="center").pack() + + def sync(self): + # Clear content + for w in self.content_frame.winfo_children(): + w.destroy() + + scores = {s: self.vars[s].get() / 100.0 for s in self.subjs} + plan = run_simulation(self.ai, self.subjs, self.diffs, scores, explore=False) + + total_gain = sum(step["gain"] for step in plan) + self.badge.config(text=f" Est. total growth +{total_gain:.0%} ") + + for i, step in enumerate(plan): + self._render_step(i, step, len(plan), scores) + + # โ”€โ”€ Subject-specific plan content โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + PLANS = { + "Python": { + "beginner": ["Learn variables, loops, and functions", "Complete 10 beginner exercises on HackerRank", "Build a simple calculator or to-do CLI app"], + "intermediate": ["Study OOP: classes, inheritance, decorators", "Practice file I/O and exception handling", "Build a REST API with Flask or FastAPI"], + "advanced": ["Deep-dive into generators, asyncio, and metaclasses", "Optimise with profiling tools (cProfile, line_profiler)", "Contribute to an open-source Python project"], + }, + "Java": { + "beginner": ["Understand JVM basics, types, and control flow", "Practice OOP: classes, interfaces, and inheritance", "Build a simple bank account simulator"], + "intermediate": ["Study Collections, Generics, and Streams API", "Learn exception handling and multithreading basics", "Build a CRUD app with Spring Boot"], + "advanced": ["Master concurrency: locks, executors, CompletableFuture", "Explore JVM internals and memory management", "Design a microservice with Spring Boot + Docker"], + }, + "DSA": { + "beginner": ["Revise arrays, strings, and basic recursion", "Solve 5 easy LeetCode problems daily", "Understand time/space complexity (Big-O)"], + "intermediate": ["Study linked lists, stacks, queues, and trees", "Solve medium LeetCode problems on sliding window & BFS/DFS", "Practice sorting algorithms from scratch"], + "advanced": ["Master dynamic programming patterns", "Study graphs: Dijkstra, Floyd-Warshall, topological sort", "Mock interview: 2 hard problems per week timed"], + }, + "Aptitude": { + "beginner": ["Review percentages, ratios, and averages", "Attempt 20 questions daily from IndiaBix", "Focus on speed: use shortcuts for multiplication"], + "intermediate": ["Practice time & work, pipes & cisterns, profit & loss", "Attempt sectional mock tests (30 min per topic)", "Analyse mistakes and revisit weak areas weekly"], + "advanced": ["Solve full-length aptitude papers (TCS, Infosys pattern)", "Work on data interpretation and logical reasoning", "Target sub-60 sec per question consistently"], + }, + "Communication": { + "beginner": ["Read aloud for 10 minutes daily to build fluency", "Record yourself speaking and review for filler words", "Practice introducing yourself clearly in 60 seconds"], + "intermediate": ["Join a group discussion or debate practice session", "Write one structured email or summary per day", "Watch TED Talks and note structure & delivery techniques"], + "advanced": ["Present a 5-minute talk on a technical topic weekly", "Practise mock GDs and HR interview rounds", "Get feedback from peers and iterate on weak points"], + }, + } + + def _get_plan_tier(self, score): + if score < 0.4: return "beginner" + if score < 0.7: return "intermediate" + return "advanced" + + def _render_step(self, i, step, total, scores): + subj = step["subj"] + score = scores[subj] + tier = self._get_plan_tier(score) + tasks = self.PLANS.get(subj, {}).get(tier, ["Study core concepts", "Practice problems", "Review and revise"]) + + # Outer card + card = tk.Frame(self.content_frame, bg=UI["surface"], + highlightthickness=1, highlightbackground=UI["border"]) + card.pack(fill="x", pady=6) + + # โ”€โ”€ Header row โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + header = tk.Frame(card, bg=UI["surface"], padx=24, pady=16) + header.pack(fill="x") + + # Step badge + num_badge = tk.Frame(header, bg=UI["accent_muted"], width=40, height=40) + num_badge.pack(side="left") + num_badge.pack_propagate(False) + tk.Label(num_badge, text=str(i + 1), font=("Georgia", 13, "bold"), + bg=UI["accent_muted"], fg=UI["accent"]).place(relx=0.5, rely=0.5, anchor="center") + + # Subject name + tier label + info = tk.Frame(header, bg=UI["surface"]) + info.pack(side="left", padx=(14, 0)) + + icon = SUBJECT_ICONS.get(subj, "โ€ข") + tk.Label(info, text=f"{icon} {subj}", + font=("Georgia", 12, "bold"), bg=UI["surface"], fg=UI["text_h1"]).pack(anchor="w") + + tier_colors = { + "beginner": (UI["accent_muted"], UI["accent"]), + "intermediate": ("#FFF3DC", "#B07D20"), + "advanced": (UI["green_muted"], UI["green"]), + } + tc_bg, tc_fg = tier_colors[tier] + tk.Label(info, text=f" {tier.capitalize()} level ", + font=UI["font_small"], bg=tc_bg, fg=tc_fg).pack(anchor="w", pady=(3, 0)) + + # Growth pill (right side) + gain_frame = tk.Frame(header, bg=UI["green_muted"], + highlightthickness=1, highlightbackground="#B8DCCB") + gain_frame.pack(side="right") + gain_inner = tk.Frame(gain_frame, bg=UI["green_muted"], padx=14, pady=8) + gain_inner.pack() + tk.Label(gain_inner, text=f"+{step['gain']:.1%}", + font=("Georgia", 13, "bold"), bg=UI["green_muted"], fg=UI["green"]).pack() + tk.Label(gain_inner, text="est. growth", + font=UI["font_small"], bg=UI["green_muted"], fg=UI["green"]).pack() + + # โ”€โ”€ Divider โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + tk.Frame(card, bg=UI["border"], height=1).pack(fill="x", padx=24) + + # โ”€โ”€ Task list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + tasks_frame = tk.Frame(card, bg=UI["surface"], padx=24, pady=14) + tasks_frame.pack(fill="x") + + tk.Label(tasks_frame, text="Recommended actions", + font=UI["font_label_b"], bg=UI["surface"], fg=UI["text_muted"]).pack(anchor="w", pady=(0, 8)) + + for t, task in enumerate(tasks): + row = tk.Frame(tasks_frame, bg=UI["surface"]) + row.pack(fill="x", pady=3) + tk.Label(row, text=f"{t+1}.", font=UI["font_label_b"], + bg=UI["surface"], fg=UI["accent"], width=2).pack(side="left") + tk.Label(row, text=task, font=UI["font_body"], + bg=UI["surface"], fg=UI["text_body"], anchor="w").pack(side="left", padx=(6, 0)) + + # โ”€โ”€ Progress bar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + bar_frame = tk.Frame(card, bg=UI["surface"], padx=24, pady=(4, 14)) + bar_frame.pack(fill="x") + + bar_bg = tk.Frame(bar_frame, bg=UI["border"], height=4) + bar_bg.pack(fill="x") + + fill_width = min(1.0, step["gain"] * 5) + + def draw_bar(event, bg=bar_bg, fw=fill_width): + w = bg.winfo_width() + bar_fill = tk.Frame(bg, bg=UI["green"], height=4, width=int(w * fw)) + bar_fill.place(x=0, y=0) + + bar_bg.bind("", draw_bar) + + +if __name__ == "__main__": + Synapse().mainloop() \ No newline at end of file diff --git a/510_23BAI10157_23BAI10274/brain.py b/510_23BAI10157_23BAI10274/brain.py new file mode 100644 index 0000000..3cd0cef --- /dev/null +++ b/510_23BAI10157_23BAI10274/brain.py @@ -0,0 +1,28 @@ +import numpy as np +import json +import os + +class StudyAI: + def __init__(self, subjects, slots=6): + self.subjects = subjects + self.q = np.zeros((slots + 1, len(subjects), len(subjects))) + + def choose(self, slot, weakest_idx, explore=True): + import random + if explore and random.random() < 0.2: + return random.randint(0, len(self.subjects) - 1) + return int(np.argmax(self.q[slot][weakest_idx])) + + def learn(self, slot, weakest, choice, reward, next_slot, next_weakest): + cur = self.q[slot][weakest][choice] + best = np.max(self.q[next_slot][next_weakest]) + self.q[slot][weakest][choice] += 0.1 * (reward + 0.9 * best - cur) + + def save(self, file="q_table.json"): + with open(file, "w") as f: json.dump(self.q.tolist(), f) + + def load(self, file="q_table.json"): + if os.path.exists(file): + with open(file) as f: self.q = np.array(json.load(f)) + return True + return False \ No newline at end of file diff --git a/510_23BAI10157_23BAI10274/logic.py b/510_23BAI10157_23BAI10274/logic.py new file mode 100644 index 0000000..4dd4b8c --- /dev/null +++ b/510_23BAI10157_23BAI10274/logic.py @@ -0,0 +1,61 @@ +import random + +def run_simulation(ai, subjects, difficulty, scores, slots=6, explore=True): + current_scores = scores.copy() + history = [] + last_choice = -1 + recent_choices = [] # track last N picks for cooldown + subject_counts = {s: 0 for s in subjects} + n = len(subjects) + + # Ensure slots >= number of subjects so every subject gets at least one slot + slots = max(slots, n) + + for slot in range(slots): + weakest_idx = min(range(n), key=lambda i: current_scores[subjects[i]]) + + # --- Cooldown mask: block subjects picked in last (n-1) turns --- + cooldown = max(1, n - 1) + blocked = set(recent_choices[-cooldown:]) if len(subjects) > 1 else set() + + # Force-include any subject that has had zero slots so far + unvisited = [i for i in range(n) if subject_counts[subjects[i]] == 0] + if unvisited: + # Pick the weakest unvisited subject + choice = min(unvisited, key=lambda i: current_scores[subjects[i]]) + else: + # Let AI choose, but mask out recently used subjects + raw_choice = ai.choose(slot, weakest_idx, explore) + if raw_choice in blocked: + # Fall back to weakest subject not on cooldown + available = [i for i in range(n) if i not in blocked] + if not available: + available = list(range(n)) # safety valve + choice = min(available, key=lambda i: current_scores[subjects[i]]) + else: + choice = raw_choice + + subj = subjects[choice] + subject_counts[subj] += 1 + + # --- Gain: diminishing returns as score rises --- + gap = 1.0 - current_scores[subj] + gain = gap * difficulty[subj] * random.uniform(0.12, 0.22) + current_scores[subj] = min(1.0, current_scores[subj] + gain) + + # --- Reward shaping --- + reward = gain * 20 + if choice == weakest_idx: + reward += 5 # good: tackled weakest + if choice == last_choice: + reward -= 15 # strong repetition penalty + if subject_counts[subj] > slots // n + 1: + reward -= 10 # penalise over-concentration + + ai.learn(slot, weakest_idx, choice, reward, slot + 1, weakest_idx) + history.append({"subj": subj, "score": current_scores[subj], "gain": gain}) + + recent_choices.append(choice) + last_choice = choice + + return history \ No newline at end of file diff --git a/CAM DATA B21 RL.xlsx b/CAM DATA B21 RL.xlsx deleted file mode 100644 index 4aa4fae95becbdca8adb5eeb87fad1c5cc87e9b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13345 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 y^)Gn