Files changed (1) hide show
  1. app.py +758 -557
app.py CHANGED
@@ -4,12 +4,15 @@ import copy
4
  import sqlite3
5
  import operator
6
  import streamlit as st
7
- from openai import OpenAI
 
 
 
8
  from langchain_openai import ChatOpenAI
9
  from langchain_core.messages import HumanMessage, SystemMessage
10
  from typing import Annotated, List
11
  from pydantic import BaseModel, Field
12
- from typing_extensions import TypedDict, Literal
13
  from langgraph.graph import StateGraph, START, END
14
  from langgraph.constants import Send
15
 
@@ -19,196 +22,35 @@ st.set_page_config(layout="wide", page_title="JEE Roadmap Planner")
19
  # Initialize session state variables
20
  if "data" not in st.session_state:
21
  st.session_state.data = None
22
- if "data_old" not in st.session_state:
23
- st.session_state.data_old = None
24
  if "report_data" not in st.session_state:
25
  st.session_state.report_data = None
26
- if "incomplete_tasks" not in st.session_state:
27
- st.session_state.incomplete_tasks = None
28
- if "incomplete_task_list" not in st.session_state:
29
- st.session_state.incomplete_task_list = None
30
  if "final_report" not in st.session_state:
31
  st.session_state.final_report = None
32
- if "shifted_roadmap" not in st.session_state:
33
- st.session_state.shifted_roadmap = None
34
- if "available_dates" not in st.session_state:
35
- st.session_state.available_dates = []
36
  if "updated_roadmap" not in st.session_state:
37
  st.session_state.updated_roadmap = None
38
- if "max_optimizer_iterations" not in st.session_state:
39
- st.session_state.max_optimizer_iterations = 3 # Limit optimizer to 3 iterations
40
 
41
  # Navigation sidebar setup
42
  st.sidebar.title("JEE Roadmap Planner")
43
  page = st.sidebar.radio("Navigation", ["Home", "Roadmap Manager", "Task Analysis","Roadmap Chatbot"])
44
 
45
-
46
- # For roadmap chatbot
47
- def get_chapters_and_subtopics():
48
- with open("full_roadmap.json", "r") as f:
49
- data = json.load(f)
50
-
51
- ch_subt = {
52
- "Physics": {},
53
- "Chemistry": {},
54
- "Maths": {}
55
- }
56
-
57
- for day in data["schedule"]:
58
- for subject in day['subjects']:
59
- sub = ch_subt[subject['name']]
60
- for task in subject['tasks']:
61
- sub[task['ChapterName']] = []
62
-
63
- for day in data["schedule"]:
64
- for subject in day['subjects']:
65
- sub = ch_subt[subject['name']]
66
- for task in subject['tasks']:
67
- if task['subtopic'] not in sub[task['ChapterName']]:
68
- sub[task['ChapterName']].append(task['subtopic'])
69
-
70
- return ch_subt
71
-
72
- # Function to convert NL query to SQL
73
- def generate_sql_from_nl(prompt):
74
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
75
-
76
- table_struct = """CREATE TABLE IF NOT EXISTS roadmap (
77
- id INTEGER PRIMARY KEY AUTOINCREMENT,
78
- day_num INTEGER,
79
- date TEXT, -- [yyyy-mm-dd]
80
- subject TEXT, -- [Physics, Chemistry or Maths]
81
- chapter_name TEXT,
82
- task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test)
83
- time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on
84
- subtopic TEXT,
85
- )"""
86
-
87
- ch_subt = get_chapters_and_subtopics()
88
-
89
- response = client.chat.completions.create(
90
- model="gpt-4o-mini",
91
- messages=[
92
- {"role": "system", "content": f""""You are an helper who runs in the background of an AI agent,
93
- which helps students for their JEE Preparation. Now your Job is to analyze the users prompt and
94
- create an SQL query to extract the related Information from an sqlite3 database with the table
95
- structure: {table_struct}.
96
-
97
- Note:
98
- - For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
99
- so on. So make sure to create queries that compare just the numbers within the text.
100
- - If the student mention about any chapters or subtopics, browse through this json file {ch_subt},
101
- find the one with the closest match to the users query and use only those exact names of Chapers
102
- and Subtopics present in this file to create SQL the query.
103
-
104
- You will also make sure multiple times that you give an SQL
105
- Query that adheres to the given table structure, and you Output just the SQL query.
106
- Do not include anyting else like new line statements, ```sql or any other text. Your output
107
- is going to be directly fed into a Python script to extract the required information. So,
108
- please follow all the given Instructions.
109
- """},
110
- {"role": "user", "content": f"""Keeping the table structure in mind: {table_struct},
111
- Convert this prompt to an SQL query for the given table: {prompt}. Make sure your
112
- output is just the SQL query, which can directly be used to extract required content"""}
113
- ]
114
- )
115
- return response.choices[0].message.content.strip()
116
-
117
- # Function to convert SQL output to natural language
118
- def generate_nl_from_sql_output(prompt, query, data):
119
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
120
-
121
- response = client.chat.completions.create(
122
- model="gpt-4o-mini",
123
- messages=[
124
- {"role": "system", "content": f"""You are an helpful AI chatbot working under the roadmap
125
- section of an AI Agent, whose role is to aid students in their preparation for the JEE examination.
126
- You are going to play a very crucial role of a Roadmap Assistant, who helps the student out with whatever query
127
- they have related to their roadmap, the data required to answer the users query is already extracted
128
- from the Roadmap table of a SQLite3 database and given to you here {data}. Analyse the users query deeply and
129
- reply to it with the relevant information from the given data in a supportive manner. If you get empty data
130
- as an input, deeply analyze the user's prompt and the sql query and give a suitable reply."""},
131
- {"role": "user", "content": f"""Answer to this users query using the data given to you, while keeping
132
- your role in mind: {prompt}"""}
133
- ]
134
- )
135
- return response.choices[0].message.content.strip()
136
-
137
- # Function to fetch data from SQLite
138
- def fetch_data_from_sql(sql_query):
139
- conn = sqlite3.connect("jee_roadmap.db")
140
- cursor = conn.cursor()
141
- cursor.execute(sql_query)
142
- rows = cursor.fetchall()
143
- conn.close()
144
- return rows
145
-
146
- # Main function for chatbot
147
- def answer_user_query(prompt):
148
- initialize_roadmap_db()
149
- query = generate_sql_from_nl(prompt)
150
- st.write(query)
151
- rows = fetch_data_from_sql(query)
152
- st.write(rows)
153
- return generate_nl_from_sql_output(prompt, query, rows)
154
-
155
- def initialize_roadmap_db():
156
- if not os.path.exists("jee_roadmap.db"):
157
- try:
158
- with open("full_roadmap.json") as f:
159
- roadmap_data = json.load(f)
160
-
161
- conn = sqlite3.connect("jee_roadmap.db")
162
- cursor = conn.cursor()
163
-
164
- cursor.execute("""
165
- CREATE TABLE IF NOT EXISTS roadmap (
166
- id INTEGER PRIMARY KEY AUTOINCREMENT,
167
- day_num INTEGER,
168
- date TEXT,
169
- subject TEXT,
170
- chapter_name TEXT,
171
- task_type TEXT,
172
- time TEXT,
173
- subtopic TEXT
174
- )
175
- """)
176
-
177
- for day in roadmap_data["schedule"]:
178
- date = day["date"]
179
- day_num = day["dayNumber"]
180
- for subj in day["subjects"]:
181
- subject = subj["name"]
182
- for task in subj["tasks"]:
183
- cursor.execute("""
184
- INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic)
185
- VALUES (?, ?, ?, ?, ?, ?, ?)
186
- """, (
187
- day_num,
188
- date,
189
- subject,
190
- task["ChapterName"],
191
- task["type"],
192
- task["time"],
193
- task["subtopic"]
194
- ))
195
-
196
- conn.commit()
197
- conn.close()
198
- print("✅ Database created and data inserted successfully.")
199
- except Exception as e:
200
- print(f"⚠️ Error initializing database: {e}")
201
-
202
- # Function to load initial data
203
  def load_initial_data():
204
  with st.spinner("Loading roadmap data..."):
205
  try:
206
  with open('fourdayRoadmap.json', 'r') as file:
207
  data = json.load(file)
208
  st.session_state.data = data
209
- st.session_state.data_old = copy.deepcopy(data)
210
- st.success("Data loaded successfully!")
211
- return True
 
 
 
 
 
212
  except Exception as e:
213
  st.error(f"Error loading data: {e}")
214
  return False
@@ -222,63 +64,574 @@ def process_task_completion_data():
222
  for task in subject["tasks"]:
223
  task["task_completed"] = False
224
  task["completion_timestamp"] = None
 
225
  st.session_state.data = data
226
  st.success("Task completion data processed!")
227
 
228
- # Function to extract incomplete tasks
229
- def extract_incomplete_tasks():
230
- with st.spinner("Extracting incomplete tasks..."):
231
- data = st.session_state.data
232
- previous_day = data["schedule"][0]
233
-
234
- incomplete_tasks = {
235
- "dayNumber": previous_day["dayNumber"],
236
- "date": previous_day["date"],
237
- "subjects": []
238
- }
239
-
240
- for subject in previous_day["subjects"]:
241
- incomplete_subject_tasks = [
242
- {
243
- "ChapterName": task["ChapterName"],
244
- "type": task["type"],
245
- "subtopic": task["subtopic"],
246
- "time": task["time"],
247
- "task_completed": False,
248
- "completion_timestamp": None
249
- }
250
- for task in subject["tasks"] if not task["task_completed"]
251
- ]
252
-
253
- if incomplete_subject_tasks:
254
- incomplete_tasks["subjects"].append({
255
- "name": subject["name"],
256
- "tasks": incomplete_subject_tasks
257
- })
258
-
259
- # Convert to JSON format
260
- incomplete_tasks_json = json.dumps(incomplete_tasks, indent=4)
261
- st.session_state.incomplete_tasks = incomplete_tasks
262
-
263
- # Generate a list of incomplete tasks for the agent
264
- incomplete_task_list = []
265
- for subject in incomplete_tasks["subjects"]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  for task in subject["tasks"]:
267
- if not task["task_completed"]:
268
- incomplete_task = {
269
- "subject": subject["name"],
270
- "ChapterName": task["ChapterName"],
271
- "type": task["type"],
272
- "subtopic": task["subtopic"],
273
- "time": task["time"],
274
- "task_completed": task["task_completed"],
275
- "completion_timestamp": task["completion_timestamp"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  }
277
- incomplete_task_list.append(incomplete_task)
278
-
279
- st.session_state.incomplete_task_list = incomplete_task_list
280
- st.success("Incomplete tasks extracted!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  def generate_sql_for_report(llm, prompt):
283
  table_struct = """
284
  CREATE TABLE IF NOT EXISTS roadmap (
@@ -301,12 +654,10 @@ def generate_sql_for_report(llm, prompt):
301
  which helps students for their JEE Preparation. Now your job is to analyze the user's prompt and
302
  create an SQL query to extract the related Information from an sqlite3 database with the table
303
  structure: {table_struct}.
304
-
305
  Note: For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
306
  so on, it tells the amount of time required to complete that specific task. So make sure
307
  to create queries that compare just the numbers within the text. For the task_type column,
308
  the data is either of these (Concept Understanding, Question Practice, Revision or Test)
309
-
310
  You will also make sure multiple times that you give an SQL
311
  Query that adheres to the given table structure, and you output just the SQL query.
312
  Do not include anything else like new line statements, ```sql or any other text. Your output
@@ -622,279 +973,171 @@ def generate_report(full_roadmap):
622
 
623
  st.session_state.final_report = state["final_report"]
624
 
625
- # Function to extract available dates
626
- def extract_available_dates():
627
- with st.spinner("Extracting available dates for rescheduling..."):
628
- data = st.session_state.data
629
-
630
- def remove_the_first_day(roadmap):
631
- new_roadmap = {
632
- "schedule": []
633
- }
634
- for day in roadmap['schedule']:
635
- if day['dayNumber'] != 1:
636
- new_roadmap['schedule'].append(day)
637
- return new_roadmap
638
-
639
- roadmap = remove_the_first_day(data)
640
-
641
- available_dates = []
642
- for day in roadmap['schedule']:
643
- available_dates.append(day['date'])
644
-
645
- st.session_state.available_dates = available_dates
646
- st.success(f"Found {len(available_dates)} available dates for rescheduling!")
647
-
648
- # Function to shift incomplete tasks using the evaluator-optimizer approach
649
- def shift_incomplete_tasks():
650
- with st.spinner("Optimizing task distribution using evaluator-optimizer approach..."):
651
  try:
652
- # Initialize needed components
653
- llm = ChatOpenAI(model="gpt-4o-mini", api_key = os.getenv("OPENAI_API_KEY"))
654
-
655
- # Schema for structured output to use in evaluation
656
- class Feedback(BaseModel):
657
- grade: Literal["added", "not added"] = Field(
658
- description="Check if all the incomplete tasks are added to the roadmap or not",
659
- )
660
- feedback: str = Field(
661
- description="If some tasks are not added, give feedback to add those tasks also",
662
- )
663
-
664
- # Augment the LLM with schema for structured output
665
- evaluator = llm.with_structured_output(Feedback)
666
-
667
- # Graph state
668
- class State(TypedDict):
669
- roadmap: dict
670
- available_dates: list
671
- incomplete_task_list: list
672
- feedback: str
673
- added_or_not: str
674
- iteration_count: int
675
-
676
- # Initialize state
677
- current_state = {
678
- "roadmap": {},
679
- "available_dates": st.session_state.available_dates,
680
- "incomplete_task_list": st.session_state.incomplete_task_list,
681
- "feedback": "",
682
- "added_or_not": "",
683
- "iteration_count": 0
684
- }
685
-
686
- # Progress bar for iterations
687
- progress_bar = st.progress(0)
688
- iteration_status = st.empty()
689
-
690
- # First call to generator
691
- iteration_status.write("Iteration 1: Generating initial task distribution...")
692
- if current_state.get("feedback"):
693
- msg = llm.invoke(
694
- f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key and take into account the feedback {current_state['feedback']}
695
- and make sure that we dynamically add the tasks not increasing the load on just one day, also we have to add the tasks on
696
- following dates only {current_state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
697
- ```json
698
- {{
699
- "roadmap": [
700
- {{
701
- "date": "YYYY-MM-DD",
702
- "tasks": [
703
- {{
704
- "subject": "Subject Name",
705
- "ChapterName": "Chapter Name",
706
- "type": "Type of Task",
707
- "subtopic": "Subtopic Name",
708
- "time": "estimated time"
709
- }}
710
- ]
711
- }}
712
- ]
713
- }}
714
- ```
715
- """
716
- )
717
- else:
718
- msg = llm.invoke(
719
- f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key
720
- and make sure that we dynamically add the tasks not increasing the load on just one day, also we have to add the tasks on
721
- following dates only {current_state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
722
- ```json
723
- {{
724
- "roadmap": [
725
- {{
726
- "date": "YYYY-MM-DD",
727
- "tasks": [
728
- {{
729
- "subject": "Subject Name",
730
- "ChapterName": "Chapter Name",
731
- "type": "Type of Task",
732
- "subtopic": "Subtopic Name",
733
- "time": "estimated time"
734
- }}
735
- ]
736
- }}
737
- ]
738
- }}
739
- ```
740
- """
741
- )
742
-
743
- current_state["roadmap"] = msg.content
744
- progress_bar.progress(1/6)
745
-
746
- # Enter optimization loop
747
- max_iterations = st.session_state.max_optimizer_iterations
748
- current_iteration = 1
749
-
750
- while current_iteration <= max_iterations:
751
- # Evaluate the current roadmap
752
- iteration_status.write(f"Iteration {current_iteration}: Evaluating task distribution...")
753
-
754
- grade = evaluator.invoke(
755
- f"Grade the roadmap {current_state['roadmap']} by checking if {current_state['incomplete_task_list']} are all added or not and make sure that we dynamically add the tasks not increasing the load on just one day"
756
- )
757
-
758
- current_state["added_or_not"] = grade.grade
759
- current_state["feedback"] = grade.feedback
760
- current_state["iteration_count"] += 1
761
-
762
- progress_bar.progress((current_iteration * 2 - 1)/6)
763
-
764
- # Check if we're done or need another iteration
765
- if current_state["added_or_not"] == "added":
766
- iteration_status.write(f"✅ Success! All tasks distributed after {current_iteration} iterations.")
767
- break
768
-
769
- if current_iteration == max_iterations:
770
- iteration_status.write(f"⚠️ Reached maximum iterations ({max_iterations}). Using best result so far.")
771
- break
772
-
773
- # Generate an improved roadmap based on feedback
774
- iteration_status.write(f"Iteration {current_iteration + 1}: Improving task distribution based on feedback...")
775
-
776
- msg = llm.invoke(
777
- f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key
778
- and take into account the feedback: {current_state['feedback']}
779
- Make sure that we dynamically add the tasks not increasing the load on just one day.
780
- Only add tasks on these available dates: {current_state['available_dates']}
781
- Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
782
- ```json
783
- {{
784
- "roadmap": [
785
- {{
786
- "date": "YYYY-MM-DD",
787
- "tasks": [
788
- {{
789
- "subject": "Subject Name",
790
- "ChapterName": "Chapter Name",
791
- "type": "Type of Task",
792
- "subtopic": "Subtopic Name",
793
- "time": "estimated time"
794
- }}
795
- ]
796
- }}
797
- ]
798
- }}
799
- ```
800
- """
801
- )
802
-
803
- current_state["roadmap"] = msg.content
804
- current_iteration += 1
805
- progress_bar.progress((current_iteration * 2 - 2)/6)
806
-
807
- # Process the final roadmap content
808
- shifted_tasks_roadmap = current_state["roadmap"]
809
-
810
- # Extract JSON part from the response
811
- if "```json" in shifted_tasks_roadmap:
812
- shifted_tasks_roadmap = shifted_tasks_roadmap.split("```json")[1].split("```")[0]
813
- elif "```" in shifted_tasks_roadmap:
814
- shifted_tasks_roadmap = shifted_tasks_roadmap.split("```")[1].split("```")[0]
815
-
816
- st.session_state.shifted_roadmap = shifted_tasks_roadmap
817
- st.success(f"Tasks rescheduled successfully after {current_iteration} iterations!")
818
-
819
- progress_bar.progress(1.0)
820
-
821
- except Exception as e:
822
- st.error(f"Error in optimization process: {e}")
823
 
824
- # Function to merge shifted tasks into main roadmap
825
- def merge_shifted_tasks():
826
- with st.spinner("Merging rescheduled tasks into main roadmap..."):
827
- try:
828
- data = st.session_state.data
829
- shifted_roadmap = json.loads(st.session_state.shifted_roadmap)
830
-
831
- def add_task(roadmap, task, date_task_to_be_added):
832
- subject_name = task["subject"]
833
- chapter_name = task["ChapterName"]
834
- topic_name = task["subtopic"]
835
- type_name = task["type"]
836
-
837
- # Check if the date exists
838
- for day in roadmap['schedule']:
839
- if day['date'] == date_task_to_be_added:
840
- # Find or create subject
841
- subject_exists = False
842
- for subject in day['subjects']:
843
- if subject['name'] == subject_name:
844
- subject_exists = True
845
- # Check if task already exists
846
- task_exists = False
847
- for existing_task in subject['tasks']:
848
- if (existing_task.get('ChapterName') == chapter_name and
849
- existing_task.get('type') == type_name and
850
- (existing_task.get('subtopic', '') == topic_name or
851
- existing_task.get('topic', '') == topic_name)):
852
- task_exists = True
853
- break
854
-
855
- if not task_exists:
856
- # Add task
857
- temp_task = {
858
- "ChapterName": chapter_name,
859
- "type": type_name,
860
- "subtopic": topic_name,
861
- "time": task["time"],
862
- "task_completed": False,
863
- "completion_timestamp": None
864
- }
865
- subject['tasks'].append(temp_task)
866
- break
867
-
868
- # If subject doesn't exist, create it
869
- if not subject_exists:
870
- new_subject = {
871
- "name": subject_name,
872
- "tasks": [{
873
- "ChapterName": chapter_name,
874
- "type": type_name,
875
- "subtopic": topic_name,
876
- "time": task["time"],
877
- "task_completed": False,
878
- "completion_timestamp": None
879
- }]
880
- }
881
- day['subjects'].append(new_subject)
882
- break
883
-
884
- return roadmap
885
-
886
- # Process each task in the shifted roadmap
887
- for day in shifted_roadmap["roadmap"]:
888
- date = day['date']
889
- tasks = day['tasks']
890
- for task in tasks:
891
- data = add_task(data, task, date)
892
-
893
- st.session_state.updated_roadmap = data
894
- st.success("Tasks merged into roadmap successfully!")
895
-
896
  except Exception as e:
897
- st.error(f"Error merging tasks: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
898
 
899
  # ---- HOME PAGE ----
900
  if page == "Home":
@@ -912,16 +1155,6 @@ if page == "Home":
912
  Get started by loading your roadmap data and following the step-by-step process.
913
  """)
914
 
915
- # Settings section
916
- with st.expander("⚙️ Advanced Settings"):
917
- st.session_state.max_optimizer_iterations = st.slider(
918
- "Maximum Optimizer Iterations",
919
- min_value=1,
920
- max_value=5,
921
- value=st.session_state.max_optimizer_iterations,
922
- help="Limit how many times the optimizer will try to improve task distribution"
923
- )
924
-
925
  st.info("Navigate using the sidebar to access different features of the app.")
926
 
927
  # Initial data loading
@@ -931,77 +1164,45 @@ if page == "Home":
931
  st.session_state.first_load = True
932
 
933
  # ---- ROADMAP MANAGER PAGE ----
934
- elif page == "Roadmap Manager":
935
  st.title("🗓️ Roadmap Manager")
936
 
937
  if st.session_state.data is None:
938
  st.warning("Please load roadmap data first from the Home page.")
939
  else:
940
  st.markdown("### Roadmap Management Steps")
941
-
942
- col1, col2 = st.columns(2)
943
-
944
- with col1:
945
- st.subheader("Step 1: Process Tasks")
946
- if st.button("1️⃣ Mark Tasks as Incomplete"):
947
- process_task_completion_data()
948
-
949
- st.subheader("Step 2: Extract Tasks")
950
- if st.button("2️⃣ Extract Incomplete Tasks"):
951
- extract_incomplete_tasks()
952
 
953
- if st.session_state.incomplete_task_list:
954
- st.write(f"Found {len(st.session_state.incomplete_task_list)} incomplete tasks")
955
- with st.expander("View Incomplete Tasks"):
956
- st.json(st.session_state.incomplete_task_list)
957
 
958
- with col2:
959
- st.subheader("Step 3: Prepare for Rescheduling")
960
- if st.button("3️⃣ Extract Available Dates"):
961
- extract_available_dates()
962
-
963
- if st.session_state.available_dates:
964
- with st.expander("View Available Dates"):
965
- st.write(st.session_state.available_dates)
966
-
967
- st.subheader("Step 4: Reschedule Tasks")
968
- if st.button("4️⃣ Optimize Task Distribution"):
969
- if not st.session_state.incomplete_task_list or not st.session_state.available_dates:
970
- st.error("Please complete steps 2 and 3 first!")
971
- else:
972
- shift_incomplete_tasks()
973
-
974
- if st.session_state.shifted_roadmap:
975
- with st.expander("View Task Distribution Plan"):
976
- try:
977
- st.json(json.loads(st.session_state.shifted_roadmap))
978
- except:
979
- st.text(st.session_state.shifted_roadmap)
980
-
981
- st.subheader("Step 5: Update Roadmap")
982
- if st.button("5️⃣ Merge Tasks into Main Roadmap"):
983
- if not st.session_state.shifted_roadmap:
984
- st.error("Please complete step 4 first!")
985
- else:
986
- merge_shifted_tasks()
987
 
988
  # Display original and updated roadmaps side by side
989
- if st.session_state.data_old and st.session_state.updated_roadmap:
990
  st.subheader("Roadmap Comparison")
991
  col1, col2 = st.columns(2)
992
 
993
  with col1:
994
  st.markdown("#### Original Roadmap")
995
  with st.expander("View Original Roadmap"):
996
- st.json(st.session_state.data_old)
997
 
998
  with col2:
999
  st.markdown("#### Updated Roadmap")
1000
  with st.expander("View Updated Roadmap"):
1001
  st.json(st.session_state.updated_roadmap)
 
 
1002
 
1003
  # ---- TASK ANALYSIS PAGE ----
1004
- elif page == "Task Analysis":
1005
  st.title("📊 Task Analysis")
1006
 
1007
  choice = st.selectbox("Choose the roadmap to use for building report", ["Four Day Roadmap", "Full Roadmap"])
@@ -1061,7 +1262,7 @@ elif page == "Task Analysis":
1061
  with col2:
1062
  st.subheader("Task Type Distribution")
1063
  st.bar_chart(type_counts)
1064
- # ---- ROADMAP CHATBOT PAGE ----
1065
  elif page == "Roadmap Chatbot":
1066
  st.title("🤖 Roadmap Chatbot Assistant")
1067
 
 
4
  import sqlite3
5
  import operator
6
  import streamlit as st
7
+ from math import ceil
8
+ from datetime import datetime, timedelta
9
+ from dateutil import parser
10
+ from collections import defaultdict
11
  from langchain_openai import ChatOpenAI
12
  from langchain_core.messages import HumanMessage, SystemMessage
13
  from typing import Annotated, List
14
  from pydantic import BaseModel, Field
15
+ from typing_extensions import TypedDict
16
  from langgraph.graph import StateGraph, START, END
17
  from langgraph.constants import Send
18
 
 
22
  # Initialize session state variables
23
  if "data" not in st.session_state:
24
  st.session_state.data = None
25
+ if "full_roadmap" not in st.session_state:
26
+ st.session_state.full_roadmap = None
27
  if "report_data" not in st.session_state:
28
  st.session_state.report_data = None
 
 
 
 
29
  if "final_report" not in st.session_state:
30
  st.session_state.final_report = None
 
 
 
 
31
  if "updated_roadmap" not in st.session_state:
32
  st.session_state.updated_roadmap = None
33
+
 
34
 
35
  # Navigation sidebar setup
36
  st.sidebar.title("JEE Roadmap Planner")
37
  page = st.sidebar.radio("Navigation", ["Home", "Roadmap Manager", "Task Analysis","Roadmap Chatbot"])
38
 
39
+ # AGENT 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  def load_initial_data():
41
  with st.spinner("Loading roadmap data..."):
42
  try:
43
  with open('fourdayRoadmap.json', 'r') as file:
44
  data = json.load(file)
45
  st.session_state.data = data
46
+ with open("full_roadmap.json", 'r') as file:
47
+ data = json.load(file)
48
+ st.session_state.full_roadmap = data
49
+ with open("dependencies.json", 'r') as file:
50
+ data = json.load(file)
51
+ st.session_state.dependencies = data
52
+ st.success("Data loaded successfully!")
53
+ return True
54
  except Exception as e:
55
  st.error(f"Error loading data: {e}")
56
  return False
 
64
  for task in subject["tasks"]:
65
  task["task_completed"] = False
66
  task["completion_timestamp"] = None
67
+ task["rescheduled"] = 0
68
  st.session_state.data = data
69
  st.success("Task completion data processed!")
70
 
71
+ def add_test(roadmap, date, physics = [], chemistry = [], maths = []):
72
+ date = parser.parse(date).strftime("%Y-%m-%d")
73
+ for i, day in enumerate(roadmap["schedule"]):
74
+ if day["date"] == date:
75
+ roadmap["schedule"][i] = {
76
+ "dayNumber": day['dayNumber'],
77
+ "date": date,
78
+ "test_portion": [
79
+ {
80
+ "name": "Physics",
81
+ "chapters": physics
82
+ },
83
+ {
84
+ "name": "Chemistry",
85
+ "chapters": chemistry
86
+ },
87
+ {
88
+ "name": "Maths",
89
+ "chapters": maths
90
+ }
91
+ ],
92
+ "subjects": day['subjects']
93
+ }
94
+ return roadmap
95
+
96
+ def check_tot_time(day):
97
+ tot_time = 0
98
+ for subject in day['subjects']:
99
+ for task in subject["tasks"]:
100
+ tot_time += float(task['time'].split(" ")[0])
101
+ return tot_time
102
+
103
+ def extract_tasks(roadmap, test_portions=None, dependencies=None):
104
+ incomplete_tasks_by_subject = defaultdict(list)
105
+ subjectwise_tasks = defaultdict(list)
106
+
107
+ prev_day = roadmap[0]
108
+ for subject in prev_day["subjects"]:
109
+ subject_name = subject["name"]
110
+ tasks = subject["tasks"]
111
+
112
+ # Separate completed and incomplete tasks
113
+ incomplete_tasks = [task for task in tasks if task['task_completed'] == False]
114
+ completed_tasks = [task for task in tasks if task['task_completed'] == True]
115
+
116
+ for task in incomplete_tasks:
117
+ task['rescheduled'] += 1
118
+ # Store incomplete tasks per subject
119
+ if incomplete_tasks:
120
+ incomplete_tasks_by_subject[subject_name].extend(incomplete_tasks)
121
+
122
+ # Keep only completed tasks in the previous day
123
+ subject["tasks"] = completed_tasks
124
+
125
+ for day_index, day in enumerate(roadmap[1:]):
126
+ for subject in day["subjects"]:
127
+ subject_name = subject["name"]
128
+ subjectwise_tasks[subject_name].extend(subject["tasks"])
129
+
130
+ if test_portions and dependencies:
131
+ dependent_tasks_by_subject = defaultdict(list)
132
+ dependent_chapters = set()
133
+ for subject in test_portions:
134
+ sub_name = subject['name']
135
+ for chapter in subject['chapters']:
136
+ if chapter in dependencies[sub_name]:
137
+ dependent_chapters.update(dependencies[sub_name][chapter])
138
+
139
+ for subject, tasks in subjectwise_tasks.items():
140
+ retained_tasks = []
141
+ for task in tasks:
142
+ if task.get("ChapterName") in dependent_chapters:
143
+ dependent_tasks_by_subject[subject].append(task)
144
+ else:
145
+ retained_tasks.append(task)
146
+ subjectwise_tasks[subject] = retained_tasks
147
+
148
+ for subject, tasks in incomplete_tasks_by_subject.items():
149
+ retained_tasks = []
150
+ for task in tasks:
151
+ if task.get("ChapterName") in dependent_chapters:
152
+ dependent_tasks_by_subject[subject].append(task)
153
+ else:
154
+ retained_tasks.append(task)
155
+ incomplete_tasks_by_subject[subject] = retained_tasks
156
+ return roadmap, subjectwise_tasks, incomplete_tasks_by_subject, dependent_tasks_by_subject
157
+ return roadmap, subjectwise_tasks, incomplete_tasks_by_subject
158
+
159
+ def get_task_time(task):
160
+ return round(float(task['time'].split(" ")[0]), 3)
161
+
162
+ def calculate_time_distribution(roadmap, incomplete_tasks, incomplete_tasks_by_subject, max_hours_per_day):
163
+ total_hours = 0
164
+ num_days = len(roadmap[1:])
165
+ extra_day=False
166
+
167
+ extra_hours = 0
168
+ if incomplete_tasks_by_subject:
169
+ for subject in incomplete_tasks_by_subject:
170
+ for task in incomplete_tasks_by_subject[subject]:
171
+ extra_hours += get_task_time(task)
172
+ extra_day=True
173
+
174
+ for subject in incomplete_tasks:
175
+ for task in incomplete_tasks[subject]:
176
+ total_hours += get_task_time(task)
177
+
178
+ for day in roadmap[1:]:
179
+ if day['dayNumber'] >= 550:
180
+ max_hours_per_day = 16
181
+ for subject in day["subjects"]:
182
  for task in subject["tasks"]:
183
+ total_hours += get_task_time(task)
184
+
185
+ if num_days <= 0:
186
+ return [], [total_hours + extra_hours] if total_hours+extra_hours > 0 else []
187
+ max_possible_hours = num_days * max_hours_per_day
188
+
189
+ if total_hours <= max_possible_hours and not extra_day:
190
+ # Calculate base hours per day (minimum)
191
+ base_hours = total_hours // num_days
192
+
193
+ # Calculate remaining hours
194
+ remaining_hours = total_hours - (base_hours * num_days)
195
+
196
+ # Start with all days having base hours
197
+ distribution = [base_hours] * num_days
198
+
199
+ # Distribute remaining hours starting from the last day
200
+ for i in range(num_days - 1, -1, -1):
201
+ if remaining_hours > 0:
202
+ additional = min(1, remaining_hours, max_hours_per_day - distribution[i])
203
+ distribution[i] += additional
204
+ remaining_hours -= additional
205
+
206
+ return distribution, []
207
+
208
+ # Otherwise, max out all current days and prepare for extra days
209
+ distribution = [max_hours_per_day] * num_days
210
+ remaining_hours = total_hours - max_possible_hours
211
+
212
+ if extra_day:
213
+ base_hours = total_hours // num_days
214
+ remaining_hours = total_hours - (base_hours * num_days)
215
+ distribution = [base_hours] * num_days
216
+ for i in range(num_days - 1, -1, -1):
217
+ if remaining_hours > 0:
218
+ additional = min(1, remaining_hours, max_hours_per_day - distribution[i])
219
+ distribution[i] += additional
220
+ remaining_hours -= additional
221
+ remaining_hours = extra_hours
222
+
223
+ extra_distribution = []
224
+ while remaining_hours > 0:
225
+ hours = min(max_hours_per_day, remaining_hours)
226
+ extra_distribution.append(hours)
227
+ remaining_hours -= hours
228
+
229
+ return distribution, extra_distribution
230
+
231
+ def add_tasks_for_extra_days(subject_all_tasks, incomplete_tasks, extra_day_tasks, extra_distribution, ratio, max_hours_per_day):
232
+ subject_names = list(subject_all_tasks.keys()) or list(incomplete_tasks.keys())
233
+ has_incomplete_tasks = any(tasks for tasks in incomplete_tasks.values())
234
+ for i, target_time in enumerate(extra_distribution):
235
+ day_time = 0
236
+ if subject_all_tasks:
237
+ regular_task_limit = ceil(target_time * ratio[0] / 100) if has_incomplete_tasks else target_time
238
+ incomplete_task_limit = ceil(target_time * ratio[1] / 100) if has_incomplete_tasks else 0
239
+ else:
240
+ regular_task_limit = 0
241
+ incomplete_task_limit = target_time
242
+
243
+ # Create a new day with subjects
244
+ new_day = {"subjects": [{"name": n, "tasks": []} for n in subject_names]}
245
+
246
+ # Step 1: Allocate regular tasks up to their limit
247
+ regular_time = 0
248
+ while regular_time < regular_task_limit and day_time < max_hours_per_day:
249
+ added = False
250
+ for subject in new_day["subjects"]:
251
+ subject_name = subject["name"]
252
+ if not subject_all_tasks[subject_name]:
253
+ continue
254
+
255
+ next_task = subject_all_tasks[subject_name][0]
256
+ task_time = get_task_time(next_task)
257
+
258
+ if regular_time + task_time <= regular_task_limit and day_time + task_time <= max_hours_per_day:
259
+ subject["tasks"].append(subject_all_tasks[subject_name].pop(0))
260
+ regular_time += task_time
261
+ day_time += task_time
262
+ added = True
263
+ if not added:
264
+ break
265
+
266
+ # Step 2: Allocate incomplete tasks up to their limit
267
+ incomplete_time = 0
268
+ while incomplete_time < incomplete_task_limit and day_time < max_hours_per_day:
269
+ added = False
270
+ for subject in new_day["subjects"]:
271
+ subject_name = subject["name"]
272
+ if not incomplete_tasks[subject_name]:
273
+ continue
274
+
275
+ next_task = incomplete_tasks[subject_name][0]
276
+ task_time = get_task_time(next_task)
277
+
278
+ if incomplete_time + task_time <= incomplete_task_limit and day_time + task_time <= max_hours_per_day:
279
+ subject["tasks"].append(incomplete_tasks[subject_name].pop(0))
280
+ incomplete_time += task_time
281
+ day_time += task_time
282
+ added = True
283
+ if not added:
284
+ break
285
+
286
+ # Step 3: Use remaining time for additional regular tasks if available
287
+ if day_time < target_time:
288
+ while day_time < target_time:
289
+ added = False
290
+ for subject in new_day["subjects"]:
291
+ subject_name = subject["name"]
292
+ if not subject_all_tasks[subject_name]:
293
+ continue
294
+
295
+ next_task = subject_all_tasks[subject_name][0]
296
+ task_time = get_task_time(next_task)
297
+
298
+ if day_time + task_time <= max_hours_per_day:
299
+ subject["tasks"].append(subject_all_tasks[subject_name].pop(0))
300
+ day_time += task_time
301
+ added = True
302
+ if day_time > target_time:
303
+ break
304
+ if not added:
305
+ break
306
+ if i == len(extra_distribution) - 1:
307
+ for subject in new_day["subjects"]:
308
+ subject_name = subject["name"]
309
+
310
+ # Add remaining regular tasks
311
+ while subject_all_tasks[subject_name]:
312
+ subject["tasks"].append(subject_all_tasks[subject_name].pop(0))
313
+
314
+ # Add remaining incomplete tasks
315
+ while incomplete_tasks[subject_name]:
316
+ subject["tasks"].append(incomplete_tasks[subject_name].pop(0))
317
+
318
+ extra_day_tasks.append(new_day)
319
+ return extra_day_tasks
320
+
321
+ def shift_the_roadmap(roadmap, max_hours_per_day, ratio=(80, 20), dependencies=None, test_portions=None):
322
+ roadmap = copy.deepcopy(roadmap)
323
+ # Extract tasks based on ratio mode
324
+ if ratio == (80, 20):
325
+ roadmap, subject_all_tasks, incomplete_tasks = extract_tasks(roadmap)
326
+ dependent_tasks = None
327
+ incomplete_tasks_by_subject = None
328
+ else:
329
+ roadmap, subject_all_tasks, incomplete_tasks_by_subject, dependent_tasks = extract_tasks(
330
+ roadmap, test_portions, dependencies
331
+ )
332
+ incomplete_tasks = dependent_tasks
333
+
334
+ # Distribute time across days
335
+ time_distribution, extra_distribution = calculate_time_distribution(roadmap, incomplete_tasks,
336
+ incomplete_tasks_by_subject,
337
+ max_hours_per_day)
338
+ # Check if there are any incomplete tasks
339
+ has_incomplete_tasks = any(tasks for tasks in incomplete_tasks.values())
340
+
341
+ # Prepare containers for task assignments
342
+ pending_regular_tasks = defaultdict(lambda: defaultdict(list))
343
+ pending_incomplete_tasks = defaultdict(lambda: defaultdict(list))
344
+
345
+ # Redistribute tasks for each day
346
+ for day_index, day in enumerate(roadmap[1:], 1):
347
+ target_time = time_distribution[day_index - 1]
348
+ day_time = 0
349
+
350
+ # Set task limits based on whether incomplete tasks exist
351
+ regular_task_limit = ceil(target_time * ratio[0] / 100) if has_incomplete_tasks else target_time
352
+ incomplete_task_limit = ceil(target_time * ratio[1] / 100) if has_incomplete_tasks else 0
353
+
354
+ # Step 1: Allocate regular tasks up to their limit (either 80% or 100%)
355
+ regular_time = 0
356
+ while regular_time < regular_task_limit and day_time < max_hours_per_day:
357
+ added = False
358
+ for subject in day["subjects"]:
359
+ subject_name = subject["name"]
360
+ if not subject_all_tasks[subject_name]:
361
+ continue
362
+
363
+ next_task = subject_all_tasks[subject_name][0]
364
+ task_time = get_task_time(next_task)
365
+
366
+ if regular_time + task_time <= regular_task_limit and day_time + task_time <= max_hours_per_day:
367
+ pending_regular_tasks[day_index][subject_name].append(subject_all_tasks[subject_name].pop(0))
368
+ regular_time += task_time
369
+ day_time += task_time
370
+ added = True
371
+ if not added:
372
+ break
373
+
374
+ # Step 2: Allocate incomplete tasks if they exist
375
+ if has_incomplete_tasks and incomplete_task_limit > 0:
376
+ incomplete_time = 0
377
+ while incomplete_time < incomplete_task_limit and day_time < max_hours_per_day:
378
+ added = False
379
+ for subject in day["subjects"]:
380
+ subject_name = subject["name"]
381
+ if not incomplete_tasks[subject_name]:
382
+ continue
383
+
384
+ next_task = incomplete_tasks[subject_name][0]
385
+ task_time = get_task_time(next_task)
386
+
387
+ if incomplete_time + task_time <= incomplete_task_limit and day_time + task_time <= max_hours_per_day:
388
+ pending_incomplete_tasks[day_index][subject_name].append(incomplete_tasks[subject_name].pop(0))
389
+ incomplete_time += task_time
390
+ day_time += task_time
391
+ added = True
392
+
393
+ # Check if we've depleted all incomplete tasks
394
+ if not any(tasks for tasks in incomplete_tasks.values()):
395
+ has_incomplete_tasks = False
396
+ break
397
+ if not added:
398
+ break
399
+
400
+ # Step 3: Use remaining time for additional regular tasks if available
401
+ if day_time < target_time:
402
+ while day_time < target_time:
403
+ added = False
404
+ for subject in day["subjects"]:
405
+ subject_name = subject["name"]
406
+ if not subject_all_tasks[subject_name]:
407
+ continue
408
+
409
+ next_task = subject_all_tasks[subject_name][0]
410
+ task_time = get_task_time(next_task)
411
+
412
+ if day_time + task_time <= max_hours_per_day:
413
+ pending_regular_tasks[day_index][subject_name].append(subject_all_tasks[subject_name].pop(0))
414
+ day_time += task_time
415
+ added = True
416
+ if day_time > target_time:
417
+ break
418
+ if not added:
419
+ break
420
+
421
+ extra_day_tasks = []
422
+ if extra_distribution:
423
+ if incomplete_tasks_by_subject:
424
+ for subject, tasks in incomplete_tasks_by_subject.items():
425
+ incomplete_tasks[subject].extend(tasks)
426
+ extra_day_tasks = add_tasks_for_extra_days(subject_all_tasks,
427
+ incomplete_tasks,
428
+ extra_day_tasks,
429
+ extra_distribution,
430
+ (80, 20),
431
+ max_hours_per_day)
432
+ # Final appending of tasks
433
+ for day_index, day in enumerate(roadmap[1:], 1):
434
+ for subject in day["subjects"]:
435
+ subject_name = subject["name"]
436
+ subject["tasks"] = (
437
+ pending_regular_tasks[day_index][subject_name] +
438
+ pending_incomplete_tasks[day_index][subject_name]
439
+ )
440
+ else:
441
+ for day_index, day in enumerate(roadmap[1:], 1):
442
+ if day_index == len(roadmap) - 1:
443
+ for subject in day["subjects"]:
444
+ subject_name = subject["name"]
445
+
446
+ # Add remaining regular tasks
447
+ while subject_all_tasks[subject_name]:
448
+ task = subject_all_tasks[subject_name].pop(0)
449
+ pending_regular_tasks[day_index][subject_name].append(task)
450
+
451
+ # Add remaining incomplete tasks
452
+ while incomplete_tasks[subject_name]:
453
+ task = incomplete_tasks[subject_name].pop(0)
454
+ pending_incomplete_tasks[day_index][subject_name].append(task)
455
+
456
+ # Final appending of tasks
457
+ for subject in day["subjects"]:
458
+ subject_name = subject["name"]
459
+ subject["tasks"] = (
460
+ pending_regular_tasks[day_index][subject_name] +
461
+ pending_incomplete_tasks[day_index][subject_name]
462
+ )
463
+ return roadmap, extra_day_tasks
464
+
465
+ def update_roadmap(current_roadmap, current_dayNumber, max_hours_per_day, dependencies, no_of_revision_days = 2):
466
+ if current_dayNumber == 1:
467
+ return current_roadmap
468
+ current_roadmap = copy.deepcopy(current_roadmap)
469
+ day_index = current_dayNumber-2
470
+ test_index = None
471
+
472
+ # Check if a test exists in any specified day
473
+ for day in current_roadmap['schedule']:
474
+ if 'test_portion' in day:
475
+ test_index = current_roadmap['schedule'].index(day)
476
+ if test_index > (current_dayNumber-1):
477
+ time_to_test = test_index - (current_dayNumber-1)
478
+ test_portions = day['test_portion']
479
+ break
480
+ else:
481
+ test_index = None
482
+ break
483
+
484
+
485
+ extra_rev_days = max(no_of_revision_days - 2, 0)
486
+
487
+ # Determine scheduling strategy based on time to test
488
+ if test_index is not None:
489
+ if 30 >= time_to_test > 25:
490
+ # Far from test: Normal scheduling with backlog reduction
491
+ before_checkpoint = current_roadmap['schedule'][day_index:day_index+(time_to_test-25)]
492
+ after_checkpoint = current_roadmap['schedule'][day_index+(time_to_test-25):]
493
+ max_hours_per_day = 16
494
+ ratio = (80, 20)
495
+ test_portions = None
496
+ dependencies = None
497
+ elif 25 >= time_to_test > (10 + extra_rev_days):
498
+ # Mid-range: focus on current coursework
499
+ before_checkpoint = current_roadmap['schedule'][day_index:day_index+(time_to_test-(10+extra_rev_days))]
500
+ after_checkpoint = current_roadmap['schedule'][day_index+(time_to_test-(10+extra_rev_days)):]
501
+ max_hours_per_day = 16
502
+ ratio = (80, 20)
503
+ test_portions = None
504
+ dependencies = None
505
+ elif (10 + extra_rev_days) >= time_to_test > no_of_revision_days:
506
+ # Approaching test: Balance current work with test preparation
507
+ before_checkpoint = current_roadmap['schedule'][day_index:day_index+(time_to_test-no_of_revision_days)]
508
+ after_checkpoint = current_roadmap['schedule'][day_index+(time_to_test-no_of_revision_days):]
509
+ max_hours_per_day = 16
510
+ ratio = (50, 50)
511
+ elif 0 < time_to_test <= no_of_revision_days:
512
+ # Final revision period: Focus entirely on test preparation
513
+ before_checkpoint = current_roadmap['schedule'][day_index:test_index]
514
+ after_checkpoint = current_roadmap['schedule'][test_index:]
515
+ max_hours_per_day = 16
516
+ ratio = (0, 100)
517
+ else:
518
+ # No upcoming test: Normal scheduling
519
+ if day_index + 4 <= len(current_roadmap['schedule']):
520
+ before_checkpoint = current_roadmap['schedule'][day_index:day_index+4]
521
+ after_checkpoint = current_roadmap['schedule'][day_index+4:]
522
+ else:
523
+ print("Helloo")
524
+ before_checkpoint = current_roadmap['schedule'][day_index:]
525
+ after_checkpoint = []
526
+ ratio = (80, 20)
527
+ test_portions = None
528
+ dependencies = None
529
+ new_roadmap, extra_day_tasks = shift_the_roadmap(before_checkpoint,
530
+ max_hours_per_day,
531
+ ratio,
532
+ dependencies,
533
+ test_portions)
534
+ for day in new_roadmap:
535
+ new_date = day["date"]
536
+
537
+ for idx, existing_day in enumerate(current_roadmap['schedule']):
538
+ if existing_day['date'] == new_date:
539
+ current_roadmap['schedule'][idx] = day
540
+ ckp_idx = idx
541
+ break
542
+ if extra_day_tasks:
543
+ for day in extra_day_tasks:
544
+ for subject in day["subjects"]:
545
+ for task in subject['tasks']:
546
+ task["Critical_Notification"] = "Unable to schedule - Too many backlogs"
547
+
548
+ num_extra_days = len(extra_day_tasks)
549
+ if test_index is not None:
550
+ if 30 >= time_to_test > (10 + extra_rev_days):
551
+ new_checkpoint = copy.deepcopy(after_checkpoint)
552
+ day = copy.deepcopy(after_checkpoint[0])
553
+ for subject in day['subjects']:
554
+ sub_name = subject["name"]
555
+ subject['tasks'] = [
556
+ task for day in extra_day_tasks
557
+ for subj in day["subjects"]
558
+ if subj["name"] == sub_name
559
+ for task in subj["tasks"]
560
+ ]
561
+ day["dayNumber"] = new_checkpoint[0]["dayNumber"] - 1
562
+ day["date"] = (datetime.strptime(new_checkpoint[0]["date"], "%Y-%m-%d")
563
+ - timedelta(days=1)).strftime("%Y-%m-%d")
564
+ new_checkpoint.insert(0, day)
565
+ curr_roadmap, extra_days = shift_the_roadmap(roadmap=new_checkpoint,
566
+ max_hours_per_day = max_hours_per_day,
567
+ ratio = ratio,
568
+ dependencies = dependencies,
569
+ test_portions = test_portions)
570
+ new_roadmap = current_roadmap['schedule'][:ckp_idx+1]
571
+ new_roadmap.extend(curr_roadmap[1:])
572
+ current_roadmap['schedule'] = new_roadmap
573
+ elif 0 < time_to_test <= (10 + extra_rev_days):
574
+ # Step 1: Add empty days at the end
575
+ last_day = current_roadmap['schedule'][-1]
576
+ last_date = datetime.strptime(last_day["date"], "%Y-%m-%d")
577
+ last_day_number = last_day["dayNumber"]
578
+ for i in range(num_extra_days):
579
+ new_day = {
580
+ "dayNumber": last_day_number + i + 1,
581
+ "date": (last_date + timedelta(days=i + 1)).strftime("%Y-%m-%d"),
582
+ "subjects": []
583
  }
584
+ current_roadmap['schedule'].append(new_day)
585
+
586
+ # Step 2: Shift 'subject' key from test_index to end in reverse order
587
+ total_days = len(current_roadmap['schedule'])
588
+ for i in range(total_days - num_extra_days - 1, test_index - 1, -1):
589
+ from_day = current_roadmap['schedule'][i]
590
+ to_day = current_roadmap['schedule'][i + num_extra_days]
591
+
592
+ to_day["subjects"] = from_day["subjects"]
593
+
594
+ # Step 3: Insert the extra_day_tasks into the cleared slots starting at test_index
595
+ for i, new_task_day in enumerate(extra_day_tasks):
596
+ target_day = current_roadmap['schedule'][test_index + i]
597
+ target_day["subjects"] = new_task_day["subjects"]
598
+
599
+ else:
600
+ if day_index + 4 <= len(current_roadmap['schedule']):
601
+ new_checkpoint = copy.deepcopy(after_checkpoint)
602
+ day = copy.deepcopy(after_checkpoint[0])
603
+ for subject in day['subjects']:
604
+ sub_name = subject["name"]
605
+ subject['tasks'] = [
606
+ task for day in extra_day_tasks
607
+ for subj in day["subjects"]
608
+ if subj["name"] == sub_name
609
+ for task in subj["tasks"]
610
+ ]
611
 
612
+ day["dayNumber"] = new_checkpoint[0]["dayNumber"] - 1
613
+ day["date"] = (datetime.strptime(new_checkpoint[0]["date"], "%Y-%m-%d")
614
+ - timedelta(days=1)).strftime("%Y-%m-%d")
615
+ new_checkpoint.insert(0, day)
616
+ curr_roadmap, extra_days = shift_the_roadmap(roadmap=new_checkpoint,
617
+ max_hours_per_day = max_hours_per_day,
618
+ ratio = ratio,
619
+ dependencies = dependencies,
620
+ test_portions = test_portions)
621
+ new_roadmap = current_roadmap['schedule'][:ckp_idx+1]
622
+ new_roadmap.extend(curr_roadmap[1:])
623
+ current_roadmap['schedule'] = new_roadmap
624
+ else:
625
+ for tasks in extra_day_tasks:
626
+ day = copy.deepcopy(new_roadmap[-1])
627
+ day["dayNumber"] = current_roadmap['schedule'][-1]["dayNumber"] + 1
628
+ day["date"] = (datetime.strptime(current_roadmap['schedule'][-1]["date"], "%Y-%m-%d")
629
+ + timedelta(days=1)).strftime("%Y-%m-%d")
630
+ day['subjects'] = tasks['subjects']
631
+ current_roadmap['schedule'].append(day)
632
+ st.session_state.updated_roadmap = current_roadmap
633
+
634
+ # AGENT 2
635
  def generate_sql_for_report(llm, prompt):
636
  table_struct = """
637
  CREATE TABLE IF NOT EXISTS roadmap (
 
654
  which helps students for their JEE Preparation. Now your job is to analyze the user's prompt and
655
  create an SQL query to extract the related Information from an sqlite3 database with the table
656
  structure: {table_struct}.
 
657
  Note: For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
658
  so on, it tells the amount of time required to complete that specific task. So make sure
659
  to create queries that compare just the numbers within the text. For the task_type column,
660
  the data is either of these (Concept Understanding, Question Practice, Revision or Test)
 
661
  You will also make sure multiple times that you give an SQL
662
  Query that adheres to the given table structure, and you output just the SQL query.
663
  Do not include anything else like new line statements, ```sql or any other text. Your output
 
973
 
974
  st.session_state.final_report = state["final_report"]
975
 
976
+ # AGENT 3
977
+ def initialize_roadmap_db():
978
+ if not os.path.exists("jee_roadmap.db"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  try:
980
+ with open("full_roadmap.json") as f:
981
+ roadmap_data = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
982
 
983
+ conn = sqlite3.connect("jee_roadmap.db")
984
+ cursor = conn.cursor()
985
+
986
+ cursor.execute("""
987
+ CREATE TABLE IF NOT EXISTS roadmap (
988
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
989
+ day_num INTEGER,
990
+ date TEXT,
991
+ subject TEXT,
992
+ chapter_name TEXT,
993
+ task_type TEXT,
994
+ time TEXT,
995
+ subtopic TEXT
996
+ )
997
+ """)
998
+
999
+ for day in roadmap_data["schedule"]:
1000
+ date = day["date"]
1001
+ day_num = day["dayNumber"]
1002
+ for subj in day["subjects"]:
1003
+ subject = subj["name"]
1004
+ for task in subj["tasks"]:
1005
+ cursor.execute("""
1006
+ INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic)
1007
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1008
+ """, (
1009
+ day_num,
1010
+ date,
1011
+ subject,
1012
+ task["ChapterName"],
1013
+ task["type"],
1014
+ task["time"],
1015
+ task["subtopic"]
1016
+ ))
1017
+
1018
+ conn.commit()
1019
+ conn.close()
1020
+ print(" Database created and data inserted successfully.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  except Exception as e:
1022
+ print(f"⚠️ Error initializing database: {e}")
1023
+
1024
+ def get_chapters_and_subtopics():
1025
+ with open("full_roadmap.json", "r") as f:
1026
+ data = json.load(f)
1027
+
1028
+ ch_subt = {
1029
+ "Physics": {},
1030
+ "Chemistry": {},
1031
+ "Maths": {}
1032
+ }
1033
+
1034
+ for day in data["schedule"]:
1035
+ for subject in day['subjects']:
1036
+ sub = ch_subt[subject['name']]
1037
+ for task in subject['tasks']:
1038
+ sub[task['ChapterName']] = []
1039
+
1040
+ for day in data["schedule"]:
1041
+ for subject in day['subjects']:
1042
+ sub = ch_subt[subject['name']]
1043
+ for task in subject['tasks']:
1044
+ if task['subtopic'] not in sub[task['ChapterName']]:
1045
+ sub[task['ChapterName']].append(task['subtopic'])
1046
+
1047
+ return ch_subt
1048
+
1049
+ # Function to convert NL query to SQL
1050
+ def generate_sql_from_nl(prompt):
1051
+ table_struct = """CREATE TABLE IF NOT EXISTS roadmap (
1052
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1053
+ day_num INTEGER,
1054
+ date TEXT, -- [yyyy-mm-dd]
1055
+ subject TEXT, -- [Physics, Chemistry or Maths]
1056
+ chapter_name TEXT,
1057
+ task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test)
1058
+ time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on
1059
+ subtopic TEXT,
1060
+ )"""
1061
+
1062
+ ch_subt = get_chapters_and_subtopics()
1063
+ response = llm.invoke(
1064
+ [
1065
+ SystemMessage(
1066
+ content=f"""You are an helper who runs in the background of an AI agent,
1067
+ which helps students for their JEE Preparation. Now your Job is to analyze the users prompt and
1068
+ create an SQL query to extract the related Information from an sqlite3 database with the table
1069
+ structure: {table_struct}.
1070
+ Note:
1071
+ - For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
1072
+ so on. So make sure to create queries that compare just the numbers within the text.
1073
+ - If the student mention about any chapters or subtopics, browse through this json file {ch_subt},
1074
+ find the one with the closest match to the users query and use only those exact names of Chapers
1075
+ and Subtopics present in this file to create SQL the query.
1076
+ - For date related queries, refer today's date {datetime.now().date()}
1077
+
1078
+ You will also make sure multiple times that you give an SQL
1079
+ Query that adheres to the given table structure, and you Output just the SQL query.
1080
+ Do not include anyting else like new line statements, ```sql or any other text. Your output
1081
+ is going to be directly fed into a Python script to extract the required information. So,
1082
+ please follow all the given Instructions.
1083
+ """
1084
+ ),
1085
+ HumanMessage(
1086
+ content=f"""Keeping the table structure in mind: {table_struct},
1087
+ Convert this prompt to an SQL query for the given table: {prompt}. Make sure your
1088
+ output is just the SQL query, which can directly be used to extract required content"""
1089
+ ),
1090
+ ]
1091
+ )
1092
+
1093
+ # Return completed section
1094
+ return response.content.strip()
1095
+
1096
+ # Function to fetch data from SQLite
1097
+ def fetch_data_from_sql(sql_query):
1098
+ conn = sqlite3.connect("jee_roadmap.db")
1099
+ cursor = conn.cursor()
1100
+ cursor.execute(sql_query)
1101
+ columns = [desc[0] for desc in cursor.description]
1102
+ rows = cursor.fetchall()
1103
+ data = {
1104
+ "query": sql_query,
1105
+ "columns": columns,
1106
+ "rows": rows
1107
+ }
1108
+ conn.close()
1109
+ return data
1110
+
1111
+ # Function to convert SQL output to natural language
1112
+ def generate_nl_from_sql_output(prompt, data):
1113
+ response = llm.invoke(
1114
+ [
1115
+ SystemMessage(
1116
+ content=f"""You are an helpful AI chatbot working under the roadmap
1117
+ section of an AI Agent, whose role is to aid students in their preparation for the JEE examination.
1118
+ You are going to play a very crucial role of a Roadmap Assistant, who helps the student out with whatever query
1119
+ they have related to their roadmap, the data required to answer the users query is already extracted
1120
+ from the Roadmap table of a SQLite3 database and given to you here {data}. Analyse the users query deeply and
1121
+ reply to it with the relevant information from the given data in a supportive manner. If you get empty data
1122
+ as an input, deeply analyze the user's prompt and the sql query and give a suitable reply."""
1123
+ ),
1124
+ HumanMessage(
1125
+ content=f"""Answer to this users query using the data given to you, while keeping
1126
+ your role in mind: {prompt}"""
1127
+ ),
1128
+ ]
1129
+ )
1130
+
1131
+ # Return completed section
1132
+ return response.content.strip()
1133
+
1134
+ # Main function for chatbot
1135
+ def answer_user_query(prompt):
1136
+ initialize_roadmap_db()
1137
+ query = generate_sql_from_nl(prompt)
1138
+ data = fetch_data_from_sql(query)
1139
+ return generate_nl_from_sql_output(prompt, data)
1140
+
1141
 
1142
  # ---- HOME PAGE ----
1143
  if page == "Home":
 
1155
  Get started by loading your roadmap data and following the step-by-step process.
1156
  """)
1157
 
 
 
 
 
 
 
 
 
 
 
1158
  st.info("Navigate using the sidebar to access different features of the app.")
1159
 
1160
  # Initial data loading
 
1164
  st.session_state.first_load = True
1165
 
1166
  # ---- ROADMAP MANAGER PAGE ----
1167
+ elif page == "Roadmap Manager": # AGENT 2
1168
  st.title("🗓️ Roadmap Manager")
1169
 
1170
  if st.session_state.data is None:
1171
  st.warning("Please load roadmap data first from the Home page.")
1172
  else:
1173
  st.markdown("### Roadmap Management Steps")
 
 
 
 
 
 
 
 
 
 
 
1174
 
1175
+ st.subheader("Step 1: Process Tasks")
1176
+ if st.button("1️⃣ Mark Tasks as Incomplete"):
1177
+ process_task_completion_data()
 
1178
 
1179
+ st.subheader("Step 2: Reschedule Tasks")
1180
+ if st.button("2️⃣ Optimize Task Distribution"):
1181
+ update_roadmap(current_roadmap = st.session_state.data,
1182
+ current_dayNumber = 2,
1183
+ max_hours_per_day = 9,
1184
+ dependencies = st.session_state.dependencies,
1185
+ no_of_revision_days = 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1186
 
1187
  # Display original and updated roadmaps side by side
1188
+ if st.session_state.data and st.session_state.updated_roadmap:
1189
  st.subheader("Roadmap Comparison")
1190
  col1, col2 = st.columns(2)
1191
 
1192
  with col1:
1193
  st.markdown("#### Original Roadmap")
1194
  with st.expander("View Original Roadmap"):
1195
+ st.json(st.session_state.data)
1196
 
1197
  with col2:
1198
  st.markdown("#### Updated Roadmap")
1199
  with st.expander("View Updated Roadmap"):
1200
  st.json(st.session_state.updated_roadmap)
1201
+ for day in st.session_state.updated_roadmap['schedule']:
1202
+ st.write(f"Day: {day['dayNumber']} -> Total Time: {check_tot_time(day)} Hours")
1203
 
1204
  # ---- TASK ANALYSIS PAGE ----
1205
+ elif page == "Task Analysis": # AGENT 1
1206
  st.title("📊 Task Analysis")
1207
 
1208
  choice = st.selectbox("Choose the roadmap to use for building report", ["Four Day Roadmap", "Full Roadmap"])
 
1262
  with col2:
1263
  st.subheader("Task Type Distribution")
1264
  st.bar_chart(type_counts)
1265
+ # ---- ROADMAP CHATBOT PAGE ---- # AGENT 3
1266
  elif page == "Roadmap Chatbot":
1267
  st.title("🤖 Roadmap Chatbot Assistant")
1268