Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| import copy | |
| import sqlite3 | |
| import operator | |
| import streamlit as st | |
| from math import ceil | |
| from datetime import datetime, timedelta | |
| from collections import defaultdict | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.messages import HumanMessage, SystemMessage | |
| from typing import Annotated, List | |
| from pydantic import BaseModel, Field | |
| from typing_extensions import TypedDict | |
| from langgraph.graph import StateGraph, START, END | |
| from langgraph.constants import Send | |
| # Page configuration | |
| st.set_page_config(layout="wide", page_title="JEE Roadmap Planner") | |
| # Initialize session state variables | |
| if "data" not in st.session_state: | |
| st.session_state.data = None | |
| if "full_roadmap" not in st.session_state: | |
| st.session_state.full_roadmap = None | |
| if "report_data" not in st.session_state: | |
| st.session_state.report_data = None | |
| if "incomplete_tasks" not in st.session_state: | |
| st.session_state.incomplete_tasks = None | |
| if "incomplete_task_list" not in st.session_state: | |
| st.session_state.incomplete_task_list = None | |
| if "final_report" not in st.session_state: | |
| st.session_state.final_report = None | |
| if "shifted_roadmap" not in st.session_state: | |
| st.session_state.shifted_roadmap = None | |
| if "available_dates" not in st.session_state: | |
| st.session_state.available_dates = [] | |
| if "updated_roadmap" not in st.session_state: | |
| st.session_state.updated_roadmap = None | |
| # Navigation sidebar setup | |
| st.sidebar.title("JEE Roadmap Planner") | |
| page = st.sidebar.radio("Navigation", ["Home", "Roadmap Manager", "Task Analysis","Roadmap Chatbot"]) | |
| # AGENT 1 | |
| def load_initial_data(): | |
| with st.spinner("Loading roadmap data..."): | |
| try: | |
| with open('fourdayRoadmap.json', 'r') as file: | |
| data = json.load(file) | |
| st.session_state.data = data | |
| with open("full_roadmap.json", 'r') as file: | |
| data = json.load(file) | |
| st.session_state.full_roadmap = data | |
| st.success("Data loaded successfully!") | |
| return True | |
| except Exception as e: | |
| st.error(f"Error loading data: {e}") | |
| return False | |
| # Function to mark tasks as incomplete | |
| def process_task_completion_data(): | |
| with st.spinner("Processing task completion data..."): | |
| data = st.session_state.data | |
| for day in data["schedule"]: | |
| for subject in day["subjects"]: | |
| for task in subject["tasks"]: | |
| task["task_completed"] = False | |
| task["completion_timestamp"] = None | |
| st.session_state.data = data | |
| st.success("Task completion data processed!") | |
| def check_tot_time(day, max_hours_per_day): | |
| tot_time = 0 | |
| for subject in day: | |
| for task in subject["tasks"]: | |
| tot_time += float(task['time'].split(" ")[0]) | |
| if tot_time > max_hours_per_day: | |
| return tot_time, True | |
| else: | |
| return tot_time, False | |
| def shift_roadmap(roadmap, max_hours_per_day): | |
| roadmap = copy.deepcopy(roadmap) | |
| incomplete_tasks_by_subject = defaultdict(list) | |
| prev_day = roadmap[0] | |
| for subject in prev_day["subjects"]: | |
| subject_name = subject["name"] | |
| tasks = subject["tasks"] | |
| # Separate completed and incomplete tasks | |
| incomplete_tasks = [task for task in tasks if task['task_completed'] == False] | |
| completed_tasks = [task for task in tasks if task['task_completed'] == True] | |
| # Store incomplete tasks per subject | |
| if incomplete_tasks: | |
| incomplete_tasks_by_subject[subject_name].extend(incomplete_tasks) | |
| # Keep only completed tasks in the previous day | |
| subject["tasks"] = completed_tasks | |
| # Step 2: Redistribute tasks across the next 3 days | |
| for i, next_day in enumerate(roadmap[1:]): # Next 3 days (Day 2, Day 3, Day 4) | |
| for subject in next_day["subjects"]: | |
| subject_name = subject["name"] | |
| if subject_name in incomplete_tasks_by_subject and incomplete_tasks_by_subject[subject_name]: | |
| total_tasks = len(incomplete_tasks_by_subject[subject_name]) | |
| # Task distribution based on 1/6, 2/6, and remaining | |
| if i == 0: # First day gets 1/6 of total | |
| tasks_to_add = ceil(total_tasks * (1 / 6)) | |
| elif i == 1: # Second day gets 2/6 of total | |
| tasks_to_add = ceil(total_tasks * (2 / 6)) | |
| else: # Remaining tasks on the last day | |
| tasks_to_add = len(incomplete_tasks_by_subject[subject_name]) | |
| # Append tasks to the current day's subject | |
| subject["tasks"].extend(incomplete_tasks_by_subject[subject_name][:tasks_to_add]) | |
| # Remove assigned tasks from backlog | |
| incomplete_tasks_by_subject[subject_name] = incomplete_tasks_by_subject[subject_name][tasks_to_add:] | |
| # Make sure the time limit doesn't exceed for any day | |
| _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day) | |
| while check_time: | |
| for subject in next_day["subjects"]: | |
| subject_name = subject["name"] | |
| if subject["tasks"]: | |
| task_to_add = subject["tasks"].pop() | |
| incomplete_tasks_by_subject[subject_name].append(task_to_add) | |
| _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day) | |
| if not check_time: | |
| break | |
| return roadmap, incomplete_tasks_by_subject | |
| def get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day): | |
| day_index = dayNumber-1 | |
| if day_index+4 <= len(roadmap['schedule']): | |
| shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:day_index+4], max_hours_per_day) | |
| else: | |
| shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:], max_hours_per_day) | |
| for day in shifted_roadmap: | |
| new_date = day["date"] | |
| for idx, existing_day in enumerate(roadmap['schedule']): | |
| if existing_day['date'] == new_date: | |
| roadmap['schedule'][idx] = day | |
| break | |
| if any(len(v) != 0 for v in incomplete_tasks_by_subject.values()): | |
| next_date = (datetime.strptime(roadmap['schedule'][-1]['date'], "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d") | |
| next_day = roadmap['schedule'][-1]['dayNumber'] + 1 | |
| subjects = [{"name": subject_name, "tasks": tasks} for subject_name, tasks in incomplete_tasks_by_subject.items()] | |
| roadmap['schedule'].append({ | |
| "dayNumber": next_day, | |
| "date": next_date, | |
| "subjects": subjects | |
| }) | |
| return roadmap | |
| # Step 0: Get Subjectwise Chapter and Topic order ready | |
| def get_subjectwise_tasks(roadmap): | |
| sub_tasks = { | |
| "Physics": defaultdict(list), | |
| "Chemistry": defaultdict(list), | |
| "Maths": defaultdict(list) | |
| } | |
| for day in roadmap["schedule"]: | |
| for subject in day['subjects']: | |
| sub = sub_tasks[subject['name']] | |
| for task in subject['tasks']: | |
| if task['subtopic'] not in sub[task['ChapterName']]: | |
| sub[task['ChapterName']].append(task['subtopic']) | |
| return sub_tasks | |
| # Step 1: Extract all tasks per subject and track time allocation per day | |
| def extract_tasks(roadmap): | |
| subjectwise_tasks = defaultdict(list) | |
| daily_subjectwise_time_allocation = defaultdict(lambda: defaultdict(float)) | |
| for day_index, day in enumerate(roadmap['schedule']): | |
| for subject in day["subjects"]: | |
| subject_name = subject["name"] | |
| total_time = sum(float(task['time'].split(" ")[0]) for task in subject["tasks"]) | |
| daily_subjectwise_time_allocation[day_index][subject_name] = total_time | |
| subjectwise_tasks[subject_name].extend(subject["tasks"]) | |
| return subjectwise_tasks, daily_subjectwise_time_allocation | |
| # Step 2: Sort all tasks for each subject | |
| def sort_tasks(tasks, reference): | |
| task_type_priority = { | |
| "Concept Understanding": 0, | |
| "Question Practice": 1, | |
| "Revision": 2, | |
| "Test": 3 | |
| } | |
| chapter_order = list(reference.keys()) | |
| def task_sort_key(task): | |
| chapter = task["ChapterName"] | |
| subtopic = task["subtopic"] | |
| type_priority = task_type_priority.get(task["type"], 99) | |
| chapter_idx = chapter_order.index(chapter) if chapter in reference else float('inf') | |
| subtopic_idx = reference[chapter].index(subtopic) if subtopic in reference.get(chapter, []) else float('inf') | |
| return (chapter_idx, subtopic_idx, type_priority) | |
| return sorted(tasks, key=task_sort_key) | |
| # Helper function to get task time in hours | |
| def get_task_time(task): | |
| return float(task['time'].split(" ")[0]) | |
| # Step 3: Sort the roadmap by arranging the sorted tasks, preserving original time allocation | |
| def shift_and_sort_the_roadmap(full_roadmap, roadmap, dayNumber, max_hours_per_day): | |
| roadmap = copy.deepcopy(roadmap) | |
| roadmap = get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day) | |
| subject_refs = get_subjectwise_tasks(full_roadmap) # Load the full roadmap to obtain the correct chapter orders | |
| subject_all_tasks, subject_day_time_allocation = extract_tasks(roadmap) | |
| # Sort all tasks for each subject | |
| for subject in subject_all_tasks: | |
| subject_all_tasks[subject] = sort_tasks(subject_all_tasks[subject], subject_refs[subject]) | |
| # Redistribute tasks based on time allocation, strictly maintaining sequence | |
| for day_index, day in enumerate(roadmap['schedule']): | |
| day_time = 0 | |
| for subject in day["subjects"]: | |
| subject_name = subject["name"] | |
| target_time = subject_day_time_allocation[day_index][subject_name] | |
| selected_tasks = [] | |
| current_time = 0 | |
| tasks = subject_all_tasks[subject_name] | |
| while tasks and current_time < target_time: | |
| next_task = tasks[0] # Take the next task from the sorted sequence | |
| task_time = get_task_time(next_task) | |
| if day_time + task_time <= max_hours_per_day: # Allow if its under the max limit | |
| selected_tasks.append(tasks.pop(0)) | |
| current_time += task_time | |
| day_time += task_time | |
| else: | |
| # If Task doesn't fit, save for next day | |
| break | |
| if day_index == len(roadmap['schedule']) - 1: | |
| if tasks: | |
| for task in tasks: | |
| selected_tasks.append(task) | |
| # Update the subject's tasks | |
| subject["tasks"] = selected_tasks | |
| subject_all_tasks[subject_name] = tasks | |
| with open("current_roadmap.json", "w") as f: | |
| json.dump(roadmap, f, indent=4) | |
| st.session_state.updated_roadmap = roadmap | |
| # AGENT 2 | |
| def generate_sql_for_report(llm, prompt): | |
| table_struct = """ | |
| CREATE TABLE IF NOT EXISTS roadmap ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| day_num INTEGER, | |
| date TEXT, | |
| subject TEXT, | |
| chapter_name TEXT, | |
| task_type TEXT, | |
| time TEXT, | |
| subtopic TEXT, | |
| task_completed BOOLEAN, | |
| completion_timestamp TEXT | |
| ) | |
| """ | |
| response = llm.invoke( | |
| [ | |
| SystemMessage(content=f"""You are a helper who runs in the background of an AI agent, | |
| which helps students for their JEE Preparation. Now your job is to analyze the user's prompt and | |
| create an SQL query to extract the related Information from an sqlite3 database with the table | |
| structure: {table_struct}. | |
| Note: For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and | |
| so on, it tells the amount of time required to complete that specific task. So make sure | |
| to create queries that compare just the numbers within the text. For the task_type column, | |
| the data is either of these (Concept Understanding, Question Practice, Revision or Test) | |
| You will also make sure multiple times that you give an SQL | |
| Query that adheres to the given table structure, and you output just the SQL query. | |
| Do not include anything else like new line statements, ```sql or any other text. Your output | |
| is going to be directly fed into a Python script to extract the required information. So, | |
| please follow all the given instructions. | |
| Verify multiple times that the SQL query is error free for the SQLite3 format."""), | |
| HumanMessage(content=f"""Keeping the table structure in mind: {table_struct}, | |
| Convert this prompt to an SQL query for the given table: {prompt}. Make sure your | |
| output is just the SQL query, which can directly be used to extract required content.""") | |
| ] | |
| ) | |
| return response.content.strip() | |
| def get_sql_data_for_report(sql_query): | |
| conn = sqlite3.connect("jee_full_roadmap.db") | |
| cursor = conn.cursor() | |
| results = [] | |
| queries = [q.strip() for q in sql_query.strip().split(';') if q.strip()] | |
| for query in queries: | |
| cursor.execute(query) | |
| columns = [desc[0] for desc in cursor.description] | |
| rows = cursor.fetchall() | |
| results.append({ | |
| "query": query, | |
| "columns": columns, | |
| "rows": rows | |
| }) | |
| conn.close() | |
| return results | |
| def create_db_for_report(roadmap_data): | |
| try: | |
| conn = sqlite3.connect("jee_full_roadmap.db") | |
| cursor = conn.cursor() | |
| cursor.execute("DROP TABLE IF EXISTS roadmap") | |
| cursor.execute(""" | |
| CREATE TABLE roadmap ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| day_num INTEGER, | |
| date TEXT, | |
| subject TEXT, | |
| chapter_name TEXT, | |
| task_type TEXT, | |
| time TEXT, | |
| subtopic TEXT, | |
| task_completed BOOLEAN, | |
| completion_timestamp TEXT | |
| ) | |
| """) | |
| for day in roadmap_data["schedule"]: | |
| date = day["date"] | |
| day_num = day["dayNumber"] | |
| for subj in day["subjects"]: | |
| subject = subj["name"] | |
| for task in subj["tasks"]: | |
| cursor.execute(""" | |
| INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic, task_completed, completion_timestamp) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) | |
| """, ( | |
| day_num, | |
| date, | |
| subject, | |
| task["ChapterName"], | |
| task["type"], | |
| task["time"], | |
| task["subtopic"], | |
| task["task_completed"], | |
| task["completion_timestamp"] | |
| )) | |
| conn.commit() | |
| conn.close() | |
| print("✅ Database created and data inserted successfully.") | |
| except Exception as e: | |
| print(f"⚠️ Error initializing database: {e}") | |
| # Function to generate report | |
| llm = ChatOpenAI(model="gpt-4o-mini") | |
| class Section(BaseModel): | |
| name: str = Field( | |
| description="Name for this section of the report.", | |
| ) | |
| description: str = Field( | |
| description="Brief overview of the main topics and concepts to be covered in this section.", | |
| ) | |
| data_requirements: str = Field( | |
| description="Description of the data needed from the roadmap database to write this section.", | |
| ) | |
| class Sections(BaseModel): | |
| sections: List[Section] = Field( | |
| description="Sections of the report.", | |
| ) | |
| planner = llm.with_structured_output(Sections) | |
| class State(TypedDict): | |
| sections: list[Section] # List of report sections | |
| completed_sections: Annotated[list, operator.add] # All workers write to this key in parallel | |
| final_report: str # Final report | |
| # Combined helper-worker state | |
| class ProcessorState(TypedDict): | |
| section: Section | |
| completed_sections: Annotated[list, operator.add] | |
| def orchestrator(state: State): | |
| """Orchestrator that generates a plan for the report with data requirements""" | |
| schema = """CREATE TABLE IF NOT EXISTS roadmap ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| day_num INTEGER, | |
| date TEXT, -- [yyyy-mm-dd] | |
| subject TEXT, -- (Physics, Chemistry or Maths) | |
| chapter_name TEXT, | |
| task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test) | |
| time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on -- Tells the amount of time required to finish the task | |
| subtopic TEXT, | |
| task_completed BOOLEAN, -- 0/1 indicates task completion status | |
| completion_timestamp TEXT | |
| )""" | |
| # Generate queries | |
| report_sections = planner.invoke( | |
| [ | |
| SystemMessage(content=f"""You are responsible for creating a structured plan for a JEE preparation analysis report. | |
| Audience: The report is intended primarily for students, but must also be insightful to mentors and parents. | |
| Keep the language motivational and supportive, with actionable insights backed by data. | |
| Report Format: The report will be composed of exactly 4 concise sections. Your job is to define these sections. Each section must include: | |
| - **Name**: A short, descriptive title | |
| - **Description**: What the section analyzes and how it helps the student | |
| - **Data Requirements**: A plain-English description of what fields and metrics are needed from the roadmap | |
| database whose schema is given here: {schema} | |
| DO NOT invent new sections or formats. Use exactly the following four section templates and fill in the | |
| descriptions and data requirements precisely. | |
| --- | |
| ### Study Time Analysis | |
| **Description**: Analyze how much total time the student planned to spend vs how much they actually completed, | |
| across different subjects and task types. This will help the student understand where their time is really going. | |
| **Data Requirements**: | |
| - Fields: `subject`, `task_type`, `time`, `task_completed` | |
| - Metrics: | |
| - Total planned time → SUM of all `time` | |
| - Total actual time → SUM of `time` where `task_completed = 1` | |
| - Grouped by both `subject` and `task_type` | |
| --- | |
| ### Task Completion Metrics | |
| **Description**: Measure the student’s consistency and follow-through by looking at completion rates across | |
| subjects and task types. | |
| **Data Requirements**: | |
| - Fields: `subject`, `task_type`, `task_completed` | |
| - Metrics: | |
| - Total tasks → COUNT of all tasks | |
| - Completed tasks → COUNT of tasks where `task_completed = 1` | |
| - Completion percentage per subject and task type | |
| --- | |
| ### Study Balance Analysis | |
| **Description**: Evaluate how the student's study time is distributed across task types (e.g., Practice, Revision, Test) | |
| within each subject. This highlights over- or under-emphasis on any category. | |
| **Data Requirements**: | |
| - Fields: `subject`, `task_type`, `time` | |
| - Metrics: | |
| - SUM of `time` for each (subject, task_type) pair where task_completed = 1 | |
| - Relative distribution of time per subject to detect imbalance | |
| --- | |
| ### Strengths and Areas for Improvement | |
| **Description**: | |
| This section analyzes how the student's effort is distributed — not by estimating how long they spent, | |
| but by combining how many tasks they completed and how much time those completed tasks represent. | |
| This helps identify: | |
| - Subjects and task types where the student is showing strong commitment | |
| - Areas that may be neglected or inconsistently approached | |
| **Data Requirements**: | |
| - Fields: subject, task_type, task_completed, time | |
| - Metrics (filtered where task_completed = 1): | |
| - Total Number of completed tasks | |
| - Total amount of time spent | |
| - Grouped by subject and task_type | |
| --- | |
| Important Constraints: | |
| - You must include **all the mentioned fields** in the `data_requirements` — no assumptions | |
| - Use only **aggregate metrics** — no need for per-task or per-day analysis | |
| - Keep descriptions student-focused, clear, and motivational | |
| - Do not alter section names or invent new ones | |
| - Do not output anything outside the strict format above | |
| Your output will be passed into a structured data pipeline. Return only the filled-out section definitions as described above. | |
| """), | |
| HumanMessage(content="""Use the given table structure of the roadmap and decide all the sections of | |
| the report along with what should be in it and the clearly mention all the data thats required for it | |
| from the roadmap table"""), | |
| ] | |
| ) | |
| return {"sections": report_sections.sections} | |
| def processor(state: ProcessorState): | |
| """Combined helper and worker - gets data and writes section in one step""" | |
| section = state['section'] | |
| # HELPER PART: Get data for this section | |
| sql_query = generate_sql_for_report(llm, section.data_requirements) | |
| rows = get_sql_data_for_report(sql_query) | |
| # WORKER PART: Write the section using the data | |
| section_result = llm.invoke( | |
| [ | |
| SystemMessage( | |
| content=f"""Create a concise, data-driven JEE preparation report section that provides actionable insights for students, | |
| parents, and mentors. | |
| Requirements: | |
| 1. Begin directly with key metrics and insights - no introductory preamble | |
| 2. Use specific numbers, percentages, and ratios to quantify performance | |
| 3. Include concise tables or bullet points for clarity where appropriate | |
| 4. Highlight patterns related to: | |
| - Task completion rates | |
| - Time allocation efficiency | |
| - Subject/topic focus distribution | |
| - Study consistency patterns | |
| 5. For each observation, provide a brief actionable recommendation focused on student improvement. | |
| 6. Use professional but motivational tone appropriate for academic context | |
| 7. Strictly use Markdown for formatting all the tables and the numbers | |
| 8. Strictly keep each section very focused and write it under 0 to 50 words | |
| 9. Verify the formatting of all the tables multiple times to ensure the markdown is correct. | |
| 10. Check all the numbers and calculations made by you multiple times to ensure accuracy | |
| Base all analysis strictly on the provided data - avoid assumptions beyond what's explicitly given to you. | |
| Don't assume anything else, even a little bit. | |
| *Important* | |
| If you receive an empty data input, understand that the student hasn't done tasks matching the given data description. Also, | |
| know that this report is for the student to improve themselves, and they have no part in making sure the data is logged for | |
| this analysis. Deeply analyze the SQL query ->{sql_query} and the data description ->{section.data_requirements} used to | |
| extract the data and figure out why there was no data available in the roadmap, which the student went through and write | |
| the section accordingly. | |
| """ | |
| ), | |
| HumanMessage( | |
| content=f"""Here is the section name: {section.name} and description: {section.description} | |
| Data for writing this section: {rows}""" | |
| ), | |
| ] | |
| ) | |
| # Return completed section | |
| return {"completed_sections": [section_result.content]} | |
| def synthesizer(state: State): | |
| """Synthesize full report from sections""" | |
| # List of completed sections | |
| completed_sections = state["completed_sections"] | |
| # Format completed section to str to use as context for final sections | |
| completed_report_sections = "\n\n---\n\n".join(completed_sections) | |
| return {"final_report": completed_report_sections} | |
| # Assign processors function | |
| def assign_processors(state: State): | |
| """Assign a processor to each section in the plan""" | |
| return [Send("processor", {"section": s}) for s in state["sections"]] | |
| def generate_report(full_roadmap): | |
| with st.spinner("Generating performance report using AI..."): | |
| # Build workflow | |
| workflow_builder = StateGraph(State) | |
| # Add the nodes | |
| workflow_builder.add_node("orchestrator", orchestrator) | |
| workflow_builder.add_node("processor", processor) | |
| workflow_builder.add_node("synthesizer", synthesizer) | |
| # Add edges to connect nodes | |
| workflow_builder.add_edge(START, "orchestrator") | |
| workflow_builder.add_conditional_edges("orchestrator", assign_processors, ["processor"]) | |
| workflow_builder.add_edge("processor", "synthesizer") | |
| workflow_builder.add_edge("synthesizer", END) | |
| # Compile the workflow | |
| workflow = workflow_builder.compile() | |
| # Initialize database | |
| create_db_for_report(full_roadmap) | |
| # Invoke | |
| state = workflow.invoke({}) | |
| st.session_state.final_report = state["final_report"] | |
| # AGENT 3 | |
| def initialize_roadmap_db(): | |
| if not os.path.exists("jee_roadmap.db"): | |
| try: | |
| with open("full_roadmap.json") as f: | |
| roadmap_data = json.load(f) | |
| conn = sqlite3.connect("jee_roadmap.db") | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS roadmap ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| day_num INTEGER, | |
| date TEXT, | |
| subject TEXT, | |
| chapter_name TEXT, | |
| task_type TEXT, | |
| time TEXT, | |
| subtopic TEXT | |
| ) | |
| """) | |
| for day in roadmap_data["schedule"]: | |
| date = day["date"] | |
| day_num = day["dayNumber"] | |
| for subj in day["subjects"]: | |
| subject = subj["name"] | |
| for task in subj["tasks"]: | |
| cursor.execute(""" | |
| INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic) | |
| VALUES (?, ?, ?, ?, ?, ?, ?) | |
| """, ( | |
| day_num, | |
| date, | |
| subject, | |
| task["ChapterName"], | |
| task["type"], | |
| task["time"], | |
| task["subtopic"] | |
| )) | |
| conn.commit() | |
| conn.close() | |
| print("✅ Database created and data inserted successfully.") | |
| except Exception as e: | |
| print(f"⚠️ Error initializing database: {e}") | |
| def get_chapters_and_subtopics(): | |
| with open("full_roadmap.json", "r") as f: | |
| data = json.load(f) | |
| ch_subt = { | |
| "Physics": {}, | |
| "Chemistry": {}, | |
| "Maths": {} | |
| } | |
| for day in data["schedule"]: | |
| for subject in day['subjects']: | |
| sub = ch_subt[subject['name']] | |
| for task in subject['tasks']: | |
| sub[task['ChapterName']] = [] | |
| for day in data["schedule"]: | |
| for subject in day['subjects']: | |
| sub = ch_subt[subject['name']] | |
| for task in subject['tasks']: | |
| if task['subtopic'] not in sub[task['ChapterName']]: | |
| sub[task['ChapterName']].append(task['subtopic']) | |
| return ch_subt | |
| # Function to convert NL query to SQL | |
| def generate_sql_from_nl(prompt): | |
| table_struct = """CREATE TABLE IF NOT EXISTS roadmap ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| day_num INTEGER, | |
| date TEXT, -- [yyyy-mm-dd] | |
| subject TEXT, -- [Physics, Chemistry or Maths] | |
| chapter_name TEXT, | |
| task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test) | |
| time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on | |
| subtopic TEXT, | |
| )""" | |
| ch_subt = get_chapters_and_subtopics() | |
| response = llm.invoke( | |
| [ | |
| SystemMessage( | |
| content=f"""You are an helper who runs in the background of an AI agent, | |
| which helps students for their JEE Preparation. Now your Job is to analyze the users prompt and | |
| create an SQL query to extract the related Information from an sqlite3 database with the table | |
| structure: {table_struct}. | |
| Note: | |
| - For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and | |
| so on. So make sure to create queries that compare just the numbers within the text. | |
| - If the student mention about any chapters or subtopics, browse through this json file {ch_subt}, | |
| find the one with the closest match to the users query and use only those exact names of Chapers | |
| and Subtopics present in this file to create SQL the query. | |
| - For date related queries, refer today's date {datetime.now().date()} | |
| You will also make sure multiple times that you give an SQL | |
| Query that adheres to the given table structure, and you Output just the SQL query. | |
| Do not include anyting else like new line statements, ```sql or any other text. Your output | |
| is going to be directly fed into a Python script to extract the required information. So, | |
| please follow all the given Instructions. | |
| """ | |
| ), | |
| HumanMessage( | |
| content=f"""Keeping the table structure in mind: {table_struct}, | |
| Convert this prompt to an SQL query for the given table: {prompt}. Make sure your | |
| output is just the SQL query, which can directly be used to extract required content""" | |
| ), | |
| ] | |
| ) | |
| # Return completed section | |
| return response.content.strip() | |
| # Function to fetch data from SQLite | |
| def fetch_data_from_sql(sql_query): | |
| conn = sqlite3.connect("jee_roadmap.db") | |
| cursor = conn.cursor() | |
| cursor.execute(sql_query) | |
| columns = [desc[0] for desc in cursor.description] | |
| rows = cursor.fetchall() | |
| data = { | |
| "query": sql_query, | |
| "columns": columns, | |
| "rows": rows | |
| } | |
| conn.close() | |
| return data | |
| # Function to convert SQL output to natural language | |
| def generate_nl_from_sql_output(prompt, data): | |
| response = llm.invoke( | |
| [ | |
| SystemMessage( | |
| content=f"""You are an helpful AI chatbot working under the roadmap | |
| section of an AI Agent, whose role is to aid students in their preparation for the JEE examination. | |
| You are going to play a very crucial role of a Roadmap Assistant, who helps the student out with whatever query | |
| they have related to their roadmap, the data required to answer the users query is already extracted | |
| from the Roadmap table of a SQLite3 database and given to you here {data}. Analyse the users query deeply and | |
| reply to it with the relevant information from the given data in a supportive manner. If you get empty data | |
| as an input, deeply analyze the user's prompt and the sql query and give a suitable reply.""" | |
| ), | |
| HumanMessage( | |
| content=f"""Answer to this users query using the data given to you, while keeping | |
| your role in mind: {prompt}""" | |
| ), | |
| ] | |
| ) | |
| # Return completed section | |
| return response.content.strip() | |
| # Main function for chatbot | |
| def answer_user_query(prompt): | |
| initialize_roadmap_db() | |
| query = generate_sql_from_nl(prompt) | |
| data = fetch_data_from_sql(query) | |
| return generate_nl_from_sql_output(prompt, data) | |
| # ---- HOME PAGE ---- | |
| if page == "Home": | |
| st.title("📚 JEE Roadmap Planner") | |
| st.markdown(""" | |
| ### Welcome to your JEE Study Roadmap Planner! | |
| This tool helps you manage your JEE preparation schedule by: | |
| 1. 📊 **Analyzing your study performance** | |
| 2. 🔄 **Redistributing incomplete tasks** | |
| 3. 📝 **Providing personalized feedback** | |
| Get started by loading your roadmap data and following the step-by-step process. | |
| """) | |
| st.info("Navigate using the sidebar to access different features of the app.") | |
| # Initial data loading | |
| if st.button("📂 Load Roadmap Data"): | |
| success = load_initial_data() | |
| if success: | |
| st.session_state.first_load = True | |
| # ---- ROADMAP MANAGER PAGE ---- | |
| elif page == "Roadmap Manager": # AGENT 2 | |
| st.title("🗓️ Roadmap Manager") | |
| if st.session_state.data is None: | |
| st.warning("Please load roadmap data first from the Home page.") | |
| else: | |
| st.markdown("### Roadmap Management Steps") | |
| st.subheader("Step 1: Process Tasks") | |
| if st.button("1️⃣ Mark Tasks as Incomplete"): | |
| process_task_completion_data() | |
| st.subheader("Step 2: Reschedule Tasks") | |
| if st.button("2️⃣ Optimize Task Distribution"): | |
| shift_and_sort_the_roadmap(st.session_state.full_roadmap, | |
| st.session_state.data, | |
| dayNumber = 1, | |
| max_hours_per_day = 8) | |
| # Display original and updated roadmaps side by side | |
| if st.session_state.data and st.session_state.updated_roadmap: | |
| st.subheader("Roadmap Comparison") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown("#### Original Roadmap") | |
| with st.expander("View Original Roadmap"): | |
| st.json(st.session_state.data) | |
| with col2: | |
| st.markdown("#### Updated Roadmap") | |
| with st.expander("View Updated Roadmap"): | |
| st.json(st.session_state.updated_roadmap) | |
| for day in st.session_state.updated_roadmap['schedule']: | |
| st.write(f"Day: {day['dayNumber']} -> Total Time: {check_tot_time(day['subjects'], 8)[0]} Hours") | |
| # ---- TASK ANALYSIS PAGE ---- | |
| elif page == "Task Analysis": # AGENT 1 | |
| st.title("📊 Task Analysis") | |
| choice = st.selectbox("Choose the roadmap to use for building report", ["Four Day Roadmap", "Full Roadmap"]) | |
| if choice == "Four Day Roadmap": | |
| if st.session_state.data is None: | |
| st.warning("Please load roadmap data first from the Home page.") | |
| st.session_state.report_data = st.session_state.data | |
| elif choice == "Full Roadmap": | |
| with open("synthesized_full_roadmap.json", "r") as f: | |
| st.session_state.report_data = json.load(f) | |
| st.markdown("### Performance Report") | |
| if st.button("🔍 Generate Performance Report"): | |
| generate_report(st.session_state.report_data) | |
| if st.session_state.final_report: | |
| st.markdown(st.session_state.final_report) | |
| else: | |
| st.info("Click the button above to generate your performance report.") | |
| # Add visualization options | |
| if st.session_state.data: | |
| st.subheader("Task Breakdown") | |
| # Simple task statistics | |
| if st.checkbox("Show Task Statistics"): | |
| task_count = 0 | |
| subject_counts = {} | |
| type_counts = {} | |
| for day in st.session_state.report_data["schedule"]: | |
| for subject in day["subjects"]: | |
| subject_name = subject["name"] | |
| if subject_name not in subject_counts: | |
| subject_counts[subject_name] = 0 | |
| for task in subject["tasks"]: | |
| subject_counts[subject_name] += 1 | |
| task_count += 1 | |
| # Count by task type | |
| task_type = task.get("type", "Unknown") | |
| if task_type not in type_counts: | |
| type_counts[task_type] = 0 | |
| type_counts[task_type] += 1 | |
| st.write(f"Total tasks: {task_count}") | |
| # Create charts for data visualization | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("Subject Distribution") | |
| st.bar_chart(subject_counts) | |
| with col2: | |
| st.subheader("Task Type Distribution") | |
| st.bar_chart(type_counts) | |
| # ---- ROADMAP CHATBOT PAGE ---- # AGENT 3 | |
| elif page == "Roadmap Chatbot": | |
| st.title("🤖 Roadmap Chatbot Assistant") | |
| user_query = st.text_input("Ask a question about your roadmap:", placeholder="e.g., What are my tasks on 14 Feb 2025?") | |
| if st.button("Ask") and user_query: | |
| with st.spinner("Thinking..."): | |
| try: | |
| response = answer_user_query(user_query) | |
| st.markdown(response) | |
| except Exception as e: | |
| st.error(f"Error: {e}") | |