Files changed (1) hide show
  1. app.py +386 -550
app.py CHANGED
@@ -4,12 +4,14 @@ 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,8 +21,8 @@ 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:
@@ -35,180 +37,23 @@ 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
@@ -225,60 +70,204 @@ def process_task_completion_data():
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 +290,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 +609,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 +791,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 +800,44 @@ 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 +897,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 collections import defaultdict
10
  from langchain_openai import ChatOpenAI
11
  from langchain_core.messages import HumanMessage, SystemMessage
12
  from typing import Annotated, List
13
  from pydantic import BaseModel, Field
14
+ from typing_extensions import TypedDict
15
  from langgraph.graph import StateGraph, START, END
16
  from langgraph.constants import Send
17
 
 
21
  # Initialize session state variables
22
  if "data" not in st.session_state:
23
  st.session_state.data = None
24
+ if "full_roadmap" not in st.session_state:
25
+ st.session_state.full_roadmap = None
26
  if "report_data" not in st.session_state:
27
  st.session_state.report_data = None
28
  if "incomplete_tasks" not in st.session_state:
 
37
  st.session_state.available_dates = []
38
  if "updated_roadmap" not in st.session_state:
39
  st.session_state.updated_roadmap = None
 
 
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
+ # AGENT 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def load_initial_data():
47
  with st.spinner("Loading roadmap data..."):
48
  try:
49
  with open('fourdayRoadmap.json', 'r') as file:
50
  data = json.load(file)
51
  st.session_state.data = data
52
+ with open("full_roadmap.json", 'r') as file:
53
+ data = json.load(file)
54
+ st.session_state.full_roadmap = data
55
+ st.success("Data loaded successfully!")
56
+ return True
57
  except Exception as e:
58
  st.error(f"Error loading data: {e}")
59
  return False
 
70
  st.session_state.data = data
71
  st.success("Task completion data processed!")
72
 
73
+ def check_tot_time(day, max_hours_per_day):
74
+ tot_time = 0
75
+ for subject in day:
76
+ for task in subject["tasks"]:
77
+ tot_time += float(task['time'].split(" ")[0])
78
+
79
+ if tot_time > max_hours_per_day:
80
+ return tot_time, True
81
+ else:
82
+ return tot_time, False
83
+
84
+ def shift_roadmap(roadmap, max_hours_per_day):
85
+ roadmap = copy.deepcopy(roadmap)
86
+ incomplete_tasks_by_subject = defaultdict(list)
87
+
88
+ prev_day = roadmap[0]
89
+ for subject in prev_day["subjects"]:
90
+ subject_name = subject["name"]
91
+ tasks = subject["tasks"]
92
+
93
+ # Separate completed and incomplete tasks
94
+ incomplete_tasks = [task for task in tasks if task['task_completed'] == False]
95
+ completed_tasks = [task for task in tasks if task['task_completed'] == True]
96
+
97
+ # Store incomplete tasks per subject
98
+ if incomplete_tasks:
99
+ incomplete_tasks_by_subject[subject_name].extend(incomplete_tasks)
100
+
101
+ # Keep only completed tasks in the previous day
102
+ subject["tasks"] = completed_tasks
103
+
104
+
105
+ # Step 2: Redistribute tasks across the next 3 days
106
+ for i, next_day in enumerate(roadmap[1:]): # Next 3 days (Day 2, Day 3, Day 4)
107
+ for subject in next_day["subjects"]:
108
+ subject_name = subject["name"]
109
+ if subject_name in incomplete_tasks_by_subject and incomplete_tasks_by_subject[subject_name]:
110
+ total_tasks = len(incomplete_tasks_by_subject[subject_name])
111
+
112
+ # Task distribution based on 1/6, 2/6, and remaining
113
+ if i == 0: # First day gets 1/6 of total
114
+ tasks_to_add = ceil(total_tasks * (1 / 6))
115
+ elif i == 1: # Second day gets 2/6 of total
116
+ tasks_to_add = ceil(total_tasks * (2 / 6))
117
+ else: # Remaining tasks on the last day
118
+ tasks_to_add = len(incomplete_tasks_by_subject[subject_name])
119
+
120
+ # Append tasks to the current day's subject
121
+ subject["tasks"].extend(incomplete_tasks_by_subject[subject_name][:tasks_to_add])
122
+
123
+ # Remove assigned tasks from backlog
124
+ incomplete_tasks_by_subject[subject_name] = incomplete_tasks_by_subject[subject_name][tasks_to_add:]
125
+
126
+ # Make sure the time limit doesn't exceed for any day
127
+ _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day)
128
+ while check_time:
129
+ for subject in next_day["subjects"]:
130
+ subject_name = subject["name"]
131
+ if subject["tasks"]:
132
+ task_to_add = subject["tasks"].pop()
133
+ incomplete_tasks_by_subject[subject_name].append(task_to_add)
134
+ _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day)
135
+ if not check_time:
136
+ break
137
 
138
+ return roadmap, incomplete_tasks_by_subject
139
+
140
+ def get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day):
141
+ day_index = dayNumber-1
142
+ if day_index+4 <= len(roadmap['schedule']):
143
+ shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:day_index+4], max_hours_per_day)
144
+ else:
145
+ shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:], max_hours_per_day)
146
+ for day in shifted_roadmap:
147
+ new_date = day["date"]
148
+
149
+ for idx, existing_day in enumerate(roadmap['schedule']):
150
+ if existing_day['date'] == new_date:
151
+ roadmap['schedule'][idx] = day
152
+ break
153
+
154
+ if any(len(v) != 0 for v in incomplete_tasks_by_subject.values()):
155
+ next_date = (datetime.strptime(roadmap['schedule'][-1]['date'], "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
156
+ next_day = roadmap['schedule'][-1]['dayNumber'] + 1
157
+ subjects = [{"name": subject_name, "tasks": tasks} for subject_name, tasks in incomplete_tasks_by_subject.items()]
158
+ roadmap['schedule'].append({
159
+ "dayNumber": next_day,
160
+ "date": next_date,
161
+ "subjects": subjects
162
+ })
163
+ return roadmap
164
+
165
+ # Step 0: Get Subjectwise Chapter and Topic order ready
166
+ def get_subjectwise_tasks(roadmap):
167
+ sub_tasks = {
168
+ "Physics": defaultdict(list),
169
+ "Chemistry": defaultdict(list),
170
+ "Maths": defaultdict(list)
171
+ }
172
+
173
+ for day in roadmap["schedule"]:
174
+ for subject in day['subjects']:
175
+ sub = sub_tasks[subject['name']]
176
+ for task in subject['tasks']:
177
+ if task['subtopic'] not in sub[task['ChapterName']]:
178
+ sub[task['ChapterName']].append(task['subtopic'])
179
+
180
+ return sub_tasks
181
+
182
+ # Step 1: Extract all tasks per subject and track time allocation per day
183
+ def extract_tasks(roadmap):
184
+ subjectwise_tasks = defaultdict(list)
185
+ daily_subjectwise_time_allocation = defaultdict(lambda: defaultdict(float))
186
+
187
+ for day_index, day in enumerate(roadmap['schedule']):
188
+ for subject in day["subjects"]:
189
+ subject_name = subject["name"]
190
+ total_time = sum(float(task['time'].split(" ")[0]) for task in subject["tasks"])
191
+
192
+ daily_subjectwise_time_allocation[day_index][subject_name] = total_time
193
+ subjectwise_tasks[subject_name].extend(subject["tasks"])
194
+
195
+ return subjectwise_tasks, daily_subjectwise_time_allocation
196
+
197
+ # Step 2: Sort all tasks for each subject
198
+ def sort_tasks(tasks, reference):
199
+ task_type_priority = {
200
+ "Concept Understanding": 0,
201
+ "Question Practice": 1,
202
+ "Revision": 2,
203
+ "Test": 3
204
+ }
205
+ chapter_order = list(reference.keys())
206
+
207
+ def task_sort_key(task):
208
+ chapter = task["ChapterName"]
209
+ subtopic = task["subtopic"]
210
+ type_priority = task_type_priority.get(task["type"], 99)
211
+
212
+ chapter_idx = chapter_order.index(chapter) if chapter in reference else float('inf')
213
+ subtopic_idx = reference[chapter].index(subtopic) if subtopic in reference.get(chapter, []) else float('inf')
214
+
215
+ return (chapter_idx, subtopic_idx, type_priority)
216
+
217
+ return sorted(tasks, key=task_sort_key)
218
+
219
+ # Helper function to get task time in hours
220
+ def get_task_time(task):
221
+ return float(task['time'].split(" ")[0])
222
+
223
+ # Step 3: Sort the roadmap by arranging the sorted tasks, preserving original time allocation
224
+ def shift_and_sort_the_roadmap(full_roadmap, roadmap, dayNumber, max_hours_per_day):
225
+ roadmap = copy.deepcopy(roadmap)
226
+ roadmap = get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day)
227
+ subject_refs = get_subjectwise_tasks(full_roadmap) # Load the full roadmap to obtain the correct chapter orders
228
+ subject_all_tasks, subject_day_time_allocation = extract_tasks(roadmap)
229
+
230
+ # Sort all tasks for each subject
231
+ for subject in subject_all_tasks:
232
+ subject_all_tasks[subject] = sort_tasks(subject_all_tasks[subject], subject_refs[subject])
233
+
234
+ # Redistribute tasks based on time allocation, strictly maintaining sequence
235
+ for day_index, day in enumerate(roadmap['schedule']):
236
+ day_time = 0
237
+ for subject in day["subjects"]:
238
+ subject_name = subject["name"]
239
+ target_time = subject_day_time_allocation[day_index][subject_name]
240
+
241
+ selected_tasks = []
242
+ current_time = 0
243
+ tasks = subject_all_tasks[subject_name]
244
+ while tasks and current_time < target_time:
245
+ next_task = tasks[0] # Take the next task from the sorted sequence
246
+ task_time = get_task_time(next_task)
247
+
248
+ if day_time + task_time <= max_hours_per_day: # Allow if its under the max limit
249
+ selected_tasks.append(tasks.pop(0))
250
+ current_time += task_time
251
+ day_time += task_time
252
+ else:
253
+ # If Task doesn't fit, save for next day
254
+ break
255
+
256
+ if day_index == len(roadmap['schedule']) - 1:
257
+ if tasks:
258
+ for task in tasks:
259
+ selected_tasks.append(task)
260
+
261
+ # Update the subject's tasks
262
+ subject["tasks"] = selected_tasks
263
+ subject_all_tasks[subject_name] = tasks
264
 
265
+ with open("current_roadmap.json", "w") as f:
266
+ json.dump(roadmap, f, indent=4)
267
+
268
+ st.session_state.updated_roadmap = roadmap
269
+
270
+ # AGENT 2
271
  def generate_sql_for_report(llm, prompt):
272
  table_struct = """
273
  CREATE TABLE IF NOT EXISTS roadmap (
 
290
  which helps students for their JEE Preparation. Now your job is to analyze the user's prompt and
291
  create an SQL query to extract the related Information from an sqlite3 database with the table
292
  structure: {table_struct}.
 
293
  Note: For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
294
  so on, it tells the amount of time required to complete that specific task. So make sure
295
  to create queries that compare just the numbers within the text. For the task_type column,
296
  the data is either of these (Concept Understanding, Question Practice, Revision or Test)
 
297
  You will also make sure multiple times that you give an SQL
298
  Query that adheres to the given table structure, and you output just the SQL query.
299
  Do not include anything else like new line statements, ```sql or any other text. Your output
 
609
 
610
  st.session_state.final_report = state["final_report"]
611
 
612
+ # AGENT 3
613
+ def initialize_roadmap_db():
614
+ if not os.path.exists("jee_roadmap.db"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  try:
616
+ with open("full_roadmap.json") as f:
617
+ roadmap_data = json.load(f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
+ conn = sqlite3.connect("jee_roadmap.db")
620
+ cursor = conn.cursor()
621
+
622
+ cursor.execute("""
623
+ CREATE TABLE IF NOT EXISTS roadmap (
624
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
625
+ day_num INTEGER,
626
+ date TEXT,
627
+ subject TEXT,
628
+ chapter_name TEXT,
629
+ task_type TEXT,
630
+ time TEXT,
631
+ subtopic TEXT
632
+ )
633
+ """)
634
+
635
+ for day in roadmap_data["schedule"]:
636
+ date = day["date"]
637
+ day_num = day["dayNumber"]
638
+ for subj in day["subjects"]:
639
+ subject = subj["name"]
640
+ for task in subj["tasks"]:
641
+ cursor.execute("""
642
+ INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic)
643
+ VALUES (?, ?, ?, ?, ?, ?, ?)
644
+ """, (
645
+ day_num,
646
+ date,
647
+ subject,
648
+ task["ChapterName"],
649
+ task["type"],
650
+ task["time"],
651
+ task["subtopic"]
652
+ ))
653
+
654
+ conn.commit()
655
+ conn.close()
656
+ print(" Database created and data inserted successfully.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  except Exception as e:
658
+ print(f"⚠️ Error initializing database: {e}")
659
+
660
+ def get_chapters_and_subtopics():
661
+ with open("full_roadmap.json", "r") as f:
662
+ data = json.load(f)
663
+
664
+ ch_subt = {
665
+ "Physics": {},
666
+ "Chemistry": {},
667
+ "Maths": {}
668
+ }
669
+
670
+ for day in data["schedule"]:
671
+ for subject in day['subjects']:
672
+ sub = ch_subt[subject['name']]
673
+ for task in subject['tasks']:
674
+ sub[task['ChapterName']] = []
675
+
676
+ for day in data["schedule"]:
677
+ for subject in day['subjects']:
678
+ sub = ch_subt[subject['name']]
679
+ for task in subject['tasks']:
680
+ if task['subtopic'] not in sub[task['ChapterName']]:
681
+ sub[task['ChapterName']].append(task['subtopic'])
682
+
683
+ return ch_subt
684
+
685
+ # Function to convert NL query to SQL
686
+ def generate_sql_from_nl(prompt):
687
+ table_struct = """CREATE TABLE IF NOT EXISTS roadmap (
688
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
689
+ day_num INTEGER,
690
+ date TEXT, -- [yyyy-mm-dd]
691
+ subject TEXT, -- [Physics, Chemistry or Maths]
692
+ chapter_name TEXT,
693
+ task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test)
694
+ time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on
695
+ subtopic TEXT,
696
+ )"""
697
+
698
+ ch_subt = get_chapters_and_subtopics()
699
+ response = llm.invoke(
700
+ [
701
+ SystemMessage(
702
+ content=f"""You are an helper who runs in the background of an AI agent,
703
+ which helps students for their JEE Preparation. Now your Job is to analyze the users prompt and
704
+ create an SQL query to extract the related Information from an sqlite3 database with the table
705
+ structure: {table_struct}.
706
+ Note:
707
+ - For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
708
+ so on. So make sure to create queries that compare just the numbers within the text.
709
+ - If the student mention about any chapters or subtopics, browse through this json file {ch_subt},
710
+ find the one with the closest match to the users query and use only those exact names of Chapers
711
+ and Subtopics present in this file to create SQL the query.
712
+ - For date related queries, refer today's date {datetime.now().date()}
713
+
714
+ You will also make sure multiple times that you give an SQL
715
+ Query that adheres to the given table structure, and you Output just the SQL query.
716
+ Do not include anyting else like new line statements, ```sql or any other text. Your output
717
+ is going to be directly fed into a Python script to extract the required information. So,
718
+ please follow all the given Instructions.
719
+ """
720
+ ),
721
+ HumanMessage(
722
+ content=f"""Keeping the table structure in mind: {table_struct},
723
+ Convert this prompt to an SQL query for the given table: {prompt}. Make sure your
724
+ output is just the SQL query, which can directly be used to extract required content"""
725
+ ),
726
+ ]
727
+ )
728
+
729
+ # Return completed section
730
+ return response.content.strip()
731
+
732
+ # Function to fetch data from SQLite
733
+ def fetch_data_from_sql(sql_query):
734
+ conn = sqlite3.connect("jee_roadmap.db")
735
+ cursor = conn.cursor()
736
+ cursor.execute(sql_query)
737
+ columns = [desc[0] for desc in cursor.description]
738
+ rows = cursor.fetchall()
739
+ data = {
740
+ "query": sql_query,
741
+ "columns": columns,
742
+ "rows": rows
743
+ }
744
+ conn.close()
745
+ return data
746
+
747
+ # Function to convert SQL output to natural language
748
+ def generate_nl_from_sql_output(prompt, data):
749
+ response = llm.invoke(
750
+ [
751
+ SystemMessage(
752
+ content=f"""You are an helpful AI chatbot working under the roadmap
753
+ section of an AI Agent, whose role is to aid students in their preparation for the JEE examination.
754
+ You are going to play a very crucial role of a Roadmap Assistant, who helps the student out with whatever query
755
+ they have related to their roadmap, the data required to answer the users query is already extracted
756
+ from the Roadmap table of a SQLite3 database and given to you here {data}. Analyse the users query deeply and
757
+ reply to it with the relevant information from the given data in a supportive manner. If you get empty data
758
+ as an input, deeply analyze the user's prompt and the sql query and give a suitable reply."""
759
+ ),
760
+ HumanMessage(
761
+ content=f"""Answer to this users query using the data given to you, while keeping
762
+ your role in mind: {prompt}"""
763
+ ),
764
+ ]
765
+ )
766
+
767
+ # Return completed section
768
+ return response.content.strip()
769
+
770
+ # Main function for chatbot
771
+ def answer_user_query(prompt):
772
+ initialize_roadmap_db()
773
+ query = generate_sql_from_nl(prompt)
774
+ data = fetch_data_from_sql(query)
775
+ return generate_nl_from_sql_output(prompt, data)
776
+
777
 
778
  # ---- HOME PAGE ----
779
  if page == "Home":
 
791
  Get started by loading your roadmap data and following the step-by-step process.
792
  """)
793
 
 
 
 
 
 
 
 
 
 
 
794
  st.info("Navigate using the sidebar to access different features of the app.")
795
 
796
  # Initial data loading
 
800
  st.session_state.first_load = True
801
 
802
  # ---- ROADMAP MANAGER PAGE ----
803
+ elif page == "Roadmap Manager": # AGENT 2
804
  st.title("🗓️ Roadmap Manager")
805
 
806
  if st.session_state.data is None:
807
  st.warning("Please load roadmap data first from the Home page.")
808
  else:
809
  st.markdown("### Roadmap Management Steps")
 
 
 
 
 
 
 
 
 
 
 
810
 
811
+ st.subheader("Step 1: Process Tasks")
812
+ if st.button("1️⃣ Mark Tasks as Incomplete"):
813
+ process_task_completion_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
 
815
+ st.subheader("Step 2: Reschedule Tasks")
816
+ if st.button("2️⃣ Optimize Task Distribution"):
817
+ shift_and_sort_the_roadmap(st.session_state.full_roadmap,
818
+ st.session_state.data,
819
+ dayNumber = 1,
820
+ max_hours_per_day = 8)
821
 
822
  # Display original and updated roadmaps side by side
823
+ if st.session_state.data and st.session_state.updated_roadmap:
824
  st.subheader("Roadmap Comparison")
825
  col1, col2 = st.columns(2)
826
 
827
  with col1:
828
  st.markdown("#### Original Roadmap")
829
  with st.expander("View Original Roadmap"):
830
+ st.json(st.session_state.data)
831
 
832
  with col2:
833
  st.markdown("#### Updated Roadmap")
834
  with st.expander("View Updated Roadmap"):
835
  st.json(st.session_state.updated_roadmap)
836
+ for day in st.session_state.updated_roadmap['schedule']:
837
+ st.write(f"Day: {day['dayNumber']} -> Total Time: {check_tot_time(day['subjects'], 8)[0]} Hours")
838
 
839
  # ---- TASK ANALYSIS PAGE ----
840
+ elif page == "Task Analysis": # AGENT 1
841
  st.title("📊 Task Analysis")
842
 
843
  choice = st.selectbox("Choose the roadmap to use for building report", ["Four Day Roadmap", "Full Roadmap"])
 
897
  with col2:
898
  st.subheader("Task Type Distribution")
899
  st.bar_chart(type_counts)
900
+ # ---- ROADMAP CHATBOT PAGE ---- # AGENT 3
901
  elif page == "Roadmap Chatbot":
902
  st.title("🤖 Roadmap Chatbot Assistant")
903