Rakshitjan commited on
Commit
c0f9405
·
verified ·
1 Parent(s): 68a0df1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +568 -434
app.py CHANGED
@@ -1,447 +1,581 @@
1
  import json
2
- import operator
3
  import os
4
- import getpass
 
 
 
5
  from langchain_openai import ChatOpenAI
6
  from typing import Annotated, List
7
  from pydantic import BaseModel, Field
8
- from typing_extensions import TypedDict
9
  from langgraph.graph import StateGraph, START, END
10
- from IPython.display import Image, display
11
- from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
12
- from langgraph.graph import MessagesState
13
- from typing_extensions import Literal
14
- from langchain_core.tools import tool
15
- import json
16
- from datetime import datetime, timedelta
17
- import streamlit as st
18
- import copy
19
- from openai import OpenAI
20
- st.set_page_config(layout="wide")
21
-
22
- st.title("RoadMap Test")
23
-
24
-
25
- # # setting LLM
26
- # def _set_env(var: str):
27
- # if not os.environ.get(var):
28
- # os.environ[var] = getpass.getpass(f"{var}: ")
29
-
30
-
31
- # _set_env("ANTHROPIC_API_KEY")
32
-
33
- os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
34
-
35
- llm = ChatOpenAI(model="gpt-4o-mini")
36
-
37
 
38
- with open('fourdayRoadmap.json', 'r') as file:
39
- data = json.load(file) # Load JSON content into a Python dictionary -->display 1
40
-
41
-
42
- data_old = copy.deepcopy(data) # Added for stramlit app,not the part of actual code
43
-
44
- #Intermediate Step : Add variables task_completed as false and completion_timestamp as null
45
- for day in data["schedule"]:
46
- for subject in day["subjects"]:
47
- for task in subject["tasks"]:
48
- task["task_completed"] = False
49
- task["completion_timestamp"] = None
50
-
51
- # Step 2 : Extract The previous day from the extracted roadmap
52
- previous_day = data["schedule"][0]
53
- previous_day_roadmap_str = str(previous_day)
54
-
55
- #Step 3 : Extract the incompleted tasks
56
- # Extract incomplete tasks
57
- incomplete_tasks = {
58
- "dayNumber": previous_day["dayNumber"],
59
- "date": previous_day["date"],
60
- "subjects": []
61
- }
62
-
63
- for subject in previous_day["subjects"]:
64
- incomplete_subject_tasks = [
65
- {
66
- "ChapterName": task["ChapterName"],
67
- "type": task["type"],
68
- "subtopic": task["subtopic"],
69
- "time": task["time"],
70
- "task_completed": False,
71
- "completion_timestamp": None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
- for task in subject["tasks"] if not task["task_completed"]
74
- ]
75
-
76
- if incomplete_subject_tasks:
77
- incomplete_tasks["subjects"].append({
78
- "name": subject["name"],
79
- "tasks": incomplete_subject_tasks
80
- })
81
-
82
- # Convert to JSON format
83
- incomplete_tasks_json = json.dumps(incomplete_tasks, indent=4)
84
 
85
- #Generate a incomplete_tasks_list for the the agent 2
86
- # Extract incomplete tasks
87
- incomplete_task_list = []
88
- incomplete_task_list_json = json.loads(incomplete_tasks_json)
89
- for subject in incomplete_task_list_json["subjects"]:
90
- for task in subject["tasks"]:
91
- if not task["task_completed"]:
92
- incomplete_task = {
93
- "subject": subject["name"],
94
- "ChapterName": task["ChapterName"],
95
- "type": task["type"],
96
- "subtopic": task["subtopic"],
97
- "time": task["time"],
98
- "task_completed": task["task_completed"],
99
- "completion_timestamp": task["completion_timestamp"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  }
101
- incomplete_task_list.append(incomplete_task)
102
-
103
- #Now we have to make an Orchestrator - worker workflow which generates a report on the roadmap data and defining sections which are most relevant and so giving a report
104
-
105
-
106
- # Schema for structured output to use in planning
107
-
108
- # class Section(BaseModel): # This part defines the structure of a single section of the report, saying that it should return a field name and a description
109
- # name: str = Field(
110
- # description="Name for this section of the report.",
111
- # )
112
- # description: str = Field(
113
- # description="""Very short overview of the main topics and concepts to be covered in this section.""",
114
- # )
115
-
116
-
117
- # class Sections(BaseModel): # This part defines what has to be the output of the llm
118
- # sections: List[Section] = Field(
119
- # description="Sections of the report.",
120
- # )
121
-
122
-
123
- # # Augment the LLM with schema for structured output
124
- # planner = llm.with_structured_output(Sections)
125
-
126
- from langgraph.constants import Send
127
-
128
- # This is a state which defines all params that we need to collect in this WF
129
- # Graph state
130
- # class State(TypedDict):
131
- # previous_day_roadmap: str # Report topic
132
- # sections: list[Section] # List of report sections
133
- # completed_sections: Annotated[
134
- # list, operator.add
135
- # ] # All workers write to this key in parallel
136
- # final_report: str # Final report
137
-
138
-
139
- # # Worker state
140
- # class WorkerState(TypedDict):
141
- # section: Section
142
- # completed_sections: Annotated[list, operator.add]
143
-
144
-
145
- # # Nodes
146
- # def orchestrator(state: State):
147
- # """Orchestrator that generates a plan for the report"""
148
-
149
- # # Generate queries
150
- # report_sections = planner.invoke(
151
- # [
152
- # SystemMessage(content="""Generate a plan for the report. The report is a review and analysis output, which tells us about
153
- # the study pattern, studying hours, non studying hours and future action plan for the user based on
154
- # performance of task completion on a day of the user. We only want to understand about how the student studies nothing apart from that
155
- # Dont halucinate the data, just keep focus on provided data, no need for data out of the box.
156
- # I want a very short report to understand how many tasks student completed and then what is the studying pattern of the user
157
- # **Do not generate more than 5 sections**
158
- # """),
159
- # HumanMessage(content=f"Here is the Previous day roadmap of the user: {state['previous_day_roadmap']}"),
160
- # ]
161
- # )
162
-
163
- # return {"sections": report_sections.sections}
164
-
165
-
166
- # def llm_call(state: WorkerState):
167
- # """Worker writes a section of the report"""
168
-
169
- # # Generate section
170
- # section = llm.invoke(
171
- # [
172
- # SystemMessage(
173
- # content="""Write a report section following the provided name and description. Make this report userful for parents, teachers
174
- # and students himself, also try to motivate the student.This report is for a Joint Entrance Examination aspirant.
175
- # Keep the report very short but will all clear parameter and make sure the report is not so big but contains all the necessary
176
- # details of the student's day. Keep the report very short and avoid texts and focus on numbers
177
- # Include no preamble for each section.Make sure that a task is completed only when the "task_completed" key is true and the "time" key tells about how much
178
- # tentative that task can take time
179
- # Use markdown formatting."""
180
- # ),
181
- # HumanMessage(
182
- # content=f"Here is the section name: {state['section'].name} and description: {state['section'].description}"
183
- # ),
184
- # ]
185
- # )
186
-
187
- # # Write the updated section to completed sections
188
- # return {"completed_sections": [section.content]}
189
-
190
-
191
- # def synthesizer(state: State):
192
- # """Synthesize full report from sections"""
193
-
194
- # # List of completed sections
195
- # completed_sections = state["completed_sections"]
196
-
197
- # # Format completed section to str to use as context for final sections
198
- # completed_report_sections = "\n\n---\n\n".join(completed_sections)
199
-
200
- # return {"final_report": completed_report_sections}
201
-
202
-
203
- # # Conditional edge function to create llm_call workers that each write a section of the report
204
- # def assign_workers(state: State):
205
- # """Assign a worker to each section in the plan"""
206
-
207
- # # Kick off section writing in parallel via Send() API
208
- # return [Send("llm_call", {"section": s}) for s in state["sections"]]
209
-
210
-
211
- # # Build workflow
212
- # orchestrator_worker_builder = StateGraph(State)
213
-
214
- # # Add the nodes
215
- # orchestrator_worker_builder.add_node("orchestrator", orchestrator)
216
- # orchestrator_worker_builder.add_node("llm_call", llm_call)
217
- # orchestrator_worker_builder.add_node("synthesizer", synthesizer)
218
-
219
- # # Add edges to connect nodes
220
- # orchestrator_worker_builder.add_edge(START, "orchestrator")
221
- # orchestrator_worker_builder.add_conditional_edges(
222
- # "orchestrator", assign_workers, ["llm_call"]
223
- # )
224
- # orchestrator_worker_builder.add_edge("llm_call", "synthesizer")
225
- # orchestrator_worker_builder.add_edge("synthesizer", END)
226
-
227
- # # Compile the workflow
228
- # orchestrator_worker = orchestrator_worker_builder.compile()
229
-
230
- # # # Show the workflow
231
- # # display(Image(orchestrator_worker.get_graph().draw_mermaid_png()))
232
-
233
- # # Invoke
234
- # state = orchestrator_worker.invoke({"previous_day_roadmap": f"{previous_day_roadmap_str}"})
235
-
236
- # from IPython.display import Markdown
237
- client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
238
-
239
- response = client.chat.completions.create(
240
- model="gpt-4o-mini",
241
- messages=[
242
- {"role": "system", "content": """You will be given a JEE student's previous_day_roadmap and then you have to create
243
- a completely interactive and useful report for the user.
244
- The report should include a table for task completion rates and data
245
- The student's study pattern
246
- The student's weaknesses and tips to improve.
247
- Make sure that a task is completed only when the "task_completed" key is true and the "time" key tells about how much
248
- tentative that task can take time
249
- Use markdown formatting.
250
- """},
251
- {"role": "user", "content": f"""Here is the user's previous day roadmap in json : {previous_day_roadmap_str}"""}
252
- ]
253
- )
254
- output = response.choices[0].message.content
255
- final_report = output #-->display 2
256
-
257
- #Evaluator-optimizer approach
258
- def remove_the_first_day(roadmap):
259
- new_roadmap={
260
- "schedule":[
261
-
262
- ]
263
- }
264
- for day in roadmap['schedule']:
265
- if day['dayNumber']!=1:
266
- new_roadmap['schedule'].append(day)
267
- return new_roadmap
268
-
269
- roadmap = remove_the_first_day(data)
270
-
271
- available_dates = []
272
- for day in roadmap['schedule']:
273
- available_dates.append(day['date'])
274
- print(available_dates)
275
-
276
- # Graph state
277
- class State(TypedDict):
278
- roadmap: dict
279
- available_dates: dict
280
- incomplete_task_list: list
281
- feedback: str
282
- added_or_not: str
283
-
284
-
285
- # Schema for structured output to use in evaluation
286
- class Feedback(BaseModel):
287
- grade: Literal["added", "not added"] = Field(
288
- description="Check if all the incomplete tasks are added to the roadmap or not",
289
- )
290
- feedback: str = Field(
291
- description="If some tasks are not not added, give feedback to add those tasks also",
292
- )
293
-
294
-
295
- # Augment the LLM with schema for structured output
296
- evaluator = llm.with_structured_output(Feedback)
297
-
298
-
299
- # Nodes
300
- def llm_call_generator(state: State):
301
- """The function which addes the roadmap and incomplete tasks list to the state"""
302
- roadmap_str = """
303
- {
304
- 'roadmap':[
305
- 'date':'YYYY-MM-DD',
306
- 'tasks':[
307
- <List of tasks>
308
- ]
309
- ]
310
- }
311
- """
312
- if state.get("feedback"):
313
- msg = llm.invoke(
314
- f"""Add the following incomplete_tasks {state['incomplete_task_list']} to the roadmap key and take into account the feedback {state['feedback']}
315
- and make sure that we dyanamically add the tasks not increasing the load on just one day, also we have to add the tasks on
316
- following dates only {state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure {roadmap_str}
317
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  else:
320
- ## In this case we have to just add the roadmap and incomplete tasks list to the state
321
- msg = llm.invoke(f"""Add the following incomplete_tasks {state['incomplete_task_list']} to the roadmap key
322
- and make sure that we dyanamically add the tasks not increasing the load on just one day, also we have to add the tasks on
323
- following dates only {state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure {roadmap_str}
324
- """)
325
- return {"roadmap": msg.content}
326
-
327
- def llm_call_evaluator(state: State):
328
- """This the evaluator that checks where all the tasks are added to the roadmap or not"""
329
- grade = evaluator.invoke(f"Grade the roadmap {state['roadmap']} by checking the {state['incomplete_task_list']} as all added or not and make sure that we dyanamically add the tasks not increasing the load on just one day, also we need to make sure that we return an roadmap with proper json format as the roadmap was")
330
- return {"added_or_not": grade.grade, "feedback": grade.feedback}
331
-
332
-
333
- # Conditional edge function to route back to joke generator or end based upon feedback from the evaluator
334
- def route_roadmap(state: State):
335
- """Route back to the generator if roadmap does not have all the incomplete tasks"""
336
- if state["added_or_not"] == "added":
337
- return "Accepted"
338
- elif state["added_or_not"] == "not added":
339
- return "Rejected + Feedback"
340
-
341
-
342
- # Build workflow
343
- optimizer_builder = StateGraph(State)
344
-
345
- # Add the nodes
346
- optimizer_builder.add_node("llm_call_generator", llm_call_generator)
347
- optimizer_builder.add_node("llm_call_evaluator", llm_call_evaluator)
348
-
349
- # Add edges to connect nodes
350
- optimizer_builder.add_edge(START, "llm_call_generator")
351
- optimizer_builder.add_edge("llm_call_generator", "llm_call_evaluator")
352
- optimizer_builder.add_conditional_edges(
353
- "llm_call_evaluator",
354
- route_roadmap,
355
- { # Name returned by route_roadmap : Name of next node to visit
356
- "Accepted": END,
357
- "Rejected + Feedback": "llm_call_generator",
358
- },
359
- )
360
-
361
- # Compile the workflow
362
- optimizer_workflow = optimizer_builder.compile()
363
-
364
- # # Show the workflow
365
- # display(Image(optimizer_workflow.get_graph().draw_mermaid_png()))
366
-
367
- # Invoke
368
- state = optimizer_workflow.invoke({"available_dates": available_dates, "incomplete_task_list":incomplete_task_list})
369
- shifted_tasks_roadmap = state["roadmap"]
370
-
371
-
372
-
373
- #Now we merge the shifted roadmap
374
- def add_task(roadmap, task, date_task_to_be_added):
375
- subject_name = task["subject"] # Extract subject
376
- chapter_name = task["ChapterName"]
377
- topic_name = task["subtopic"]
378
- type_name = task["type"]
379
-
380
- # Check if the date exists
381
- for day in roadmap['schedule']:
382
- if day['date'] == date_task_to_be_added:
383
- for subject in day['subjects']:
384
- if subject['name'] == subject_name:
385
- # Check if task already exists
386
- for existing_task in subject['tasks']:
387
- if existing_task['ChapterName'] == chapter_name and existing_task['type'] == type_name and existing_task['subtopic'] == topic_name:
388
- return roadmap # Task already exists, return unchanged roadmap
389
-
390
- # Add task
391
- temp_task = {
392
- "ChapterName": chapter_name,
393
- "type": type_name,
394
- "topic": topic_name,
395
- "time": task["time"],
396
- "task_completed": False,
397
- "completion_timestamp": None
398
- }
399
- subject['tasks'].append(temp_task)
400
- return roadmap # Return updated roadmap
401
 
402
- shifted_tasks_roadmap = shifted_tasks_roadmap.split("```json")[1].split("```")[0]
403
- for day in json.loads(shifted_tasks_roadmap)["roadmap"]:
404
- date = day['date']
405
- tasks = day['tasks']
406
- for task in tasks:
407
- add_task(data,task,date)
408
-
409
-
410
-
411
-
412
- #stramlit section
413
-
414
- # Initialize session state for page navigation if it doesn't exist
415
- if "page" not in st.session_state:
416
- st.session_state["page"] = 1
417
-
418
- def show_page_1():
419
- st.title("Roadmaps")
420
-
421
- col1, col2 = st.columns([1, 1])
422
-
423
- with col1:
424
- st.subheader("Original Roadmap")
425
- st.json(data_old)
426
-
427
- with col2:
428
- st.subheader("Roadmap with Shifted Task")
429
- st.json(data)
430
-
431
-
432
- def show_page_2():
433
- st.title("Report")
434
- st.markdown(final_report)
435
-
436
- # Navigation
437
- st.sidebar.title("Navigation")
438
- if st.sidebar.button("Roadmaps Page"):
439
- st.session_state["page"] = 1
440
- if st.sidebar.button("Report Page"):
441
- st.session_state["page"] = 2
442
-
443
- # Display content based on the current page
444
- if st.session_state["page"] == 1:
445
- show_page_1()
446
- elif st.session_state["page"] == 2:
447
- show_page_2()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
 
2
  import os
3
+ import copy
4
+ import streamlit as st
5
+ from openai import OpenAI
6
+ from datetime import datetime
7
  from langchain_openai import ChatOpenAI
8
  from typing import Annotated, List
9
  from pydantic import BaseModel, Field
10
+ from typing_extensions import TypedDict, Literal
11
  from langgraph.graph import StateGraph, START, END
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # Page configuration
14
+ st.set_page_config(layout="wide", page_title="JEE Roadmap Planner")
15
+
16
+ # Initialize session state variables
17
+ if "data" not in st.session_state:
18
+ st.session_state.data = None
19
+ if "data_old" not in st.session_state:
20
+ st.session_state.data_old = None
21
+ if "incomplete_tasks" not in st.session_state:
22
+ st.session_state.incomplete_tasks = None
23
+ if "incomplete_task_list" not in st.session_state:
24
+ st.session_state.incomplete_task_list = None
25
+ if "final_report" not in st.session_state:
26
+ st.session_state.final_report = None
27
+ if "shifted_roadmap" not in st.session_state:
28
+ st.session_state.shifted_roadmap = None
29
+ if "available_dates" not in st.session_state:
30
+ st.session_state.available_dates = []
31
+ if "updated_roadmap" not in st.session_state:
32
+ st.session_state.updated_roadmap = None
33
+ if "max_optimizer_iterations" not in st.session_state:
34
+ st.session_state.max_optimizer_iterations = 3 # Limit optimizer to 3 iterations
35
+
36
+ # Navigation sidebar setup
37
+ st.sidebar.title("JEE Roadmap Planner")
38
+ page = st.sidebar.radio("Navigation", ["Home", "Roadmap Manager", "Task Analysis"])
39
+
40
+ # Function to load initial data
41
+ def load_initial_data():
42
+ with st.spinner("Loading roadmap data..."):
43
+ try:
44
+ with open('fourdayRoadmap.json', 'r') as file:
45
+ data = json.load(file)
46
+ st.session_state.data = data
47
+ st.session_state.data_old = copy.deepcopy(data)
48
+ st.success("Data loaded successfully!")
49
+ return True
50
+ except Exception as e:
51
+ st.error(f"Error loading data: {e}")
52
+ return False
53
+
54
+ # Function to mark tasks as incomplete
55
+ def process_task_completion_data():
56
+ with st.spinner("Processing task completion data..."):
57
+ data = st.session_state.data
58
+ for day in data["schedule"]:
59
+ for subject in day["subjects"]:
60
+ for task in subject["tasks"]:
61
+ task["task_completed"] = False
62
+ task["completion_timestamp"] = None
63
+ st.session_state.data = data
64
+ st.success("Task completion data processed!")
65
+
66
+ # Function to extract incomplete tasks
67
+ def extract_incomplete_tasks():
68
+ with st.spinner("Extracting incomplete tasks..."):
69
+ data = st.session_state.data
70
+ previous_day = data["schedule"][0]
71
+
72
+ incomplete_tasks = {
73
+ "dayNumber": previous_day["dayNumber"],
74
+ "date": previous_day["date"],
75
+ "subjects": []
76
  }
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ for subject in previous_day["subjects"]:
79
+ incomplete_subject_tasks = [
80
+ {
81
+ "ChapterName": task["ChapterName"],
82
+ "type": task["type"],
83
+ "subtopic": task["subtopic"],
84
+ "time": task["time"],
85
+ "task_completed": False,
86
+ "completion_timestamp": None
87
+ }
88
+ for task in subject["tasks"] if not task["task_completed"]
89
+ ]
90
+
91
+ if incomplete_subject_tasks:
92
+ incomplete_tasks["subjects"].append({
93
+ "name": subject["name"],
94
+ "tasks": incomplete_subject_tasks
95
+ })
96
+
97
+ # Convert to JSON format
98
+ incomplete_tasks_json = json.dumps(incomplete_tasks, indent=4)
99
+ st.session_state.incomplete_tasks = incomplete_tasks
100
+
101
+ # Generate a list of incomplete tasks for the agent
102
+ incomplete_task_list = []
103
+ for subject in incomplete_tasks["subjects"]:
104
+ for task in subject["tasks"]:
105
+ if not task["task_completed"]:
106
+ incomplete_task = {
107
+ "subject": subject["name"],
108
+ "ChapterName": task["ChapterName"],
109
+ "type": task["type"],
110
+ "subtopic": task["subtopic"],
111
+ "time": task["time"],
112
+ "task_completed": task["task_completed"],
113
+ "completion_timestamp": task["completion_timestamp"]
114
+ }
115
+ incomplete_task_list.append(incomplete_task)
116
+
117
+ st.session_state.incomplete_task_list = incomplete_task_list
118
+ st.success("Incomplete tasks extracted!")
119
+
120
+ # Function to generate report
121
+ def generate_report():
122
+ with st.spinner("Generating performance report using AI..."):
123
+ previous_day = st.session_state.data["schedule"][0]
124
+ previous_day_roadmap_str = str(previous_day)
125
+
126
+ try:
127
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
128
+ response = client.chat.completions.create(
129
+ model="gpt-4o-mini",
130
+ messages=[
131
+ {"role": "system", "content": """You will be given a JEE student's previous_day_roadmap and then you have to create
132
+ a completely interactive and useful report for the user.
133
+ The report should include a table for task completion rates and data
134
+ The student's study pattern
135
+ The student's weaknesses and tips to improve.
136
+ Make sure that a task is completed only when the "task_completed" key is true and the "time" key tells about how much
137
+ tentative that task can take time
138
+ Use markdown formatting.
139
+ """},
140
+ {"role": "user", "content": f"""Here is the user's previous day roadmap in json : {previous_day_roadmap_str}"""}
141
+ ]
142
+ )
143
+ output = response.choices[0].message.content
144
+ st.session_state.final_report = output
145
+ st.success("Report generated successfully!")
146
+ except Exception as e:
147
+ st.error(f"Error generating report: {e}")
148
+
149
+ # Function to extract available dates
150
+ def extract_available_dates():
151
+ with st.spinner("Extracting available dates for rescheduling..."):
152
+ data = st.session_state.data
153
+
154
+ def remove_the_first_day(roadmap):
155
+ new_roadmap = {
156
+ "schedule": []
157
  }
158
+ for day in roadmap['schedule']:
159
+ if day['dayNumber'] != 1:
160
+ new_roadmap['schedule'].append(day)
161
+ return new_roadmap
162
+
163
+ roadmap = remove_the_first_day(data)
164
+
165
+ available_dates = []
166
+ for day in roadmap['schedule']:
167
+ available_dates.append(day['date'])
168
+
169
+ st.session_state.available_dates = available_dates
170
+ st.success(f"Found {len(available_dates)} available dates for rescheduling!")
171
+
172
+ # Function to shift incomplete tasks using the evaluator-optimizer approach
173
+ def shift_incomplete_tasks():
174
+ with st.spinner("Optimizing task distribution using evaluator-optimizer approach..."):
175
+ try:
176
+ # Initialize needed components
177
+ llm = ChatOpenAI(model="gpt-4o-mini")
178
+
179
+ # Schema for structured output to use in evaluation
180
+ class Feedback(BaseModel):
181
+ grade: Literal["added", "not added"] = Field(
182
+ description="Check if all the incomplete tasks are added to the roadmap or not",
183
+ )
184
+ feedback: str = Field(
185
+ description="If some tasks are not added, give feedback to add those tasks also",
186
+ )
187
+
188
+ # Augment the LLM with schema for structured output
189
+ evaluator = llm.with_structured_output(Feedback)
190
+
191
+ # Graph state
192
+ class State(TypedDict):
193
+ roadmap: dict
194
+ available_dates: list
195
+ incomplete_task_list: list
196
+ feedback: str
197
+ added_or_not: str
198
+ iteration_count: int
199
+
200
+ # Initialize state
201
+ current_state = {
202
+ "roadmap": {},
203
+ "available_dates": st.session_state.available_dates,
204
+ "incomplete_task_list": st.session_state.incomplete_task_list,
205
+ "feedback": "",
206
+ "added_or_not": "",
207
+ "iteration_count": 0
208
+ }
209
+
210
+ # Progress bar for iterations
211
+ progress_bar = st.progress(0)
212
+ iteration_status = st.empty()
213
+
214
+ # First call to generator
215
+ iteration_status.write("Iteration 1: Generating initial task distribution...")
216
+ if current_state.get("feedback"):
217
+ msg = llm.invoke(
218
+ f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key and take into account the feedback {current_state['feedback']}
219
+ 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
220
+ following dates only {current_state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
221
+ ```json
222
+ {{
223
+ "roadmap": [
224
+ {{
225
+ "date": "YYYY-MM-DD",
226
+ "tasks": [
227
+ {{
228
+ "subject": "Subject Name",
229
+ "ChapterName": "Chapter Name",
230
+ "type": "Type of Task",
231
+ "subtopic": "Subtopic Name",
232
+ "time": "estimated time"
233
+ }}
234
+ ]
235
+ }}
236
+ ]
237
+ }}
238
+ ```
239
+ """
240
+ )
241
+ else:
242
+ msg = llm.invoke(
243
+ f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key
244
+ 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
245
+ following dates only {current_state['available_dates']} Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
246
+ ```json
247
+ {{
248
+ "roadmap": [
249
+ {{
250
+ "date": "YYYY-MM-DD",
251
+ "tasks": [
252
+ {{
253
+ "subject": "Subject Name",
254
+ "ChapterName": "Chapter Name",
255
+ "type": "Type of Task",
256
+ "subtopic": "Subtopic Name",
257
+ "time": "estimated time"
258
+ }}
259
+ ]
260
+ }}
261
+ ]
262
+ }}
263
+ ```
264
+ """
265
+ )
266
+
267
+ current_state["roadmap"] = msg.content
268
+ progress_bar.progress(1/6)
269
+
270
+ # Enter optimization loop
271
+ max_iterations = st.session_state.max_optimizer_iterations
272
+ current_iteration = 1
273
+
274
+ while current_iteration <= max_iterations:
275
+ # Evaluate the current roadmap
276
+ iteration_status.write(f"Iteration {current_iteration}: Evaluating task distribution...")
277
+
278
+ grade = evaluator.invoke(
279
+ 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"
280
+ )
281
+
282
+ current_state["added_or_not"] = grade.grade
283
+ current_state["feedback"] = grade.feedback
284
+ current_state["iteration_count"] += 1
285
+
286
+ progress_bar.progress((current_iteration * 2 - 1)/6)
287
+
288
+ # Check if we're done or need another iteration
289
+ if current_state["added_or_not"] == "added":
290
+ iteration_status.write(f"✅ Success! All tasks distributed after {current_iteration} iterations.")
291
+ break
292
+
293
+ if current_iteration == max_iterations:
294
+ iteration_status.write(f"⚠️ Reached maximum iterations ({max_iterations}). Using best result so far.")
295
+ break
296
+
297
+ # Generate an improved roadmap based on feedback
298
+ iteration_status.write(f"Iteration {current_iteration + 1}: Improving task distribution based on feedback...")
299
+
300
+ msg = llm.invoke(
301
+ f"""Add the following incomplete_tasks {current_state['incomplete_task_list']} to the roadmap key
302
+ and take into account the feedback: {current_state['feedback']}
303
+ Make sure that we dynamically add the tasks not increasing the load on just one day.
304
+ Only add tasks on these available dates: {current_state['available_dates']}
305
+ Make sure you only give roadmap json as output and nothing else, strictly follow the output structure:
306
+ ```json
307
+ {{
308
+ "roadmap": [
309
+ {{
310
+ "date": "YYYY-MM-DD",
311
+ "tasks": [
312
+ {{
313
+ "subject": "Subject Name",
314
+ "ChapterName": "Chapter Name",
315
+ "type": "Type of Task",
316
+ "subtopic": "Subtopic Name",
317
+ "time": "estimated time"
318
+ }}
319
+ ]
320
+ }}
321
+ ]
322
+ }}
323
+ ```
324
+ """
325
+ )
326
+
327
+ current_state["roadmap"] = msg.content
328
+ current_iteration += 1
329
+ progress_bar.progress((current_iteration * 2 - 2)/6)
330
+
331
+ # Process the final roadmap content
332
+ shifted_tasks_roadmap = current_state["roadmap"]
333
+
334
+ # Extract JSON part from the response
335
+ if "```json" in shifted_tasks_roadmap:
336
+ shifted_tasks_roadmap = shifted_tasks_roadmap.split("```json")[1].split("```")[0]
337
+ elif "```" in shifted_tasks_roadmap:
338
+ shifted_tasks_roadmap = shifted_tasks_roadmap.split("```")[1].split("```")[0]
339
+
340
+ st.session_state.shifted_roadmap = shifted_tasks_roadmap
341
+ st.success(f"Tasks rescheduled successfully after {current_iteration} iterations!")
342
+
343
+ progress_bar.progress(1.0)
344
+
345
+ except Exception as e:
346
+ st.error(f"Error in optimization process: {e}")
347
+
348
+ # Function to merge shifted tasks into main roadmap
349
+ def merge_shifted_tasks():
350
+ with st.spinner("Merging rescheduled tasks into main roadmap..."):
351
+ try:
352
+ data = st.session_state.data
353
+ shifted_roadmap = json.loads(st.session_state.shifted_roadmap)
354
+
355
+ def add_task(roadmap, task, date_task_to_be_added):
356
+ subject_name = task["subject"]
357
+ chapter_name = task["ChapterName"]
358
+ topic_name = task["subtopic"]
359
+ type_name = task["type"]
360
+
361
+ # Check if the date exists
362
+ for day in roadmap['schedule']:
363
+ if day['date'] == date_task_to_be_added:
364
+ # Find or create subject
365
+ subject_exists = False
366
+ for subject in day['subjects']:
367
+ if subject['name'] == subject_name:
368
+ subject_exists = True
369
+ # Check if task already exists
370
+ task_exists = False
371
+ for existing_task in subject['tasks']:
372
+ if (existing_task.get('ChapterName') == chapter_name and
373
+ existing_task.get('type') == type_name and
374
+ (existing_task.get('subtopic', '') == topic_name or
375
+ existing_task.get('topic', '') == topic_name)):
376
+ task_exists = True
377
+ break
378
+
379
+ if not task_exists:
380
+ # Add task
381
+ temp_task = {
382
+ "ChapterName": chapter_name,
383
+ "type": type_name,
384
+ "subtopic": topic_name,
385
+ "time": task["time"],
386
+ "task_completed": False,
387
+ "completion_timestamp": None
388
+ }
389
+ subject['tasks'].append(temp_task)
390
+ break
391
+
392
+ # If subject doesn't exist, create it
393
+ if not subject_exists:
394
+ new_subject = {
395
+ "name": subject_name,
396
+ "tasks": [{
397
+ "ChapterName": chapter_name,
398
+ "type": type_name,
399
+ "subtopic": topic_name,
400
+ "time": task["time"],
401
+ "task_completed": False,
402
+ "completion_timestamp": None
403
+ }]
404
+ }
405
+ day['subjects'].append(new_subject)
406
+ break
407
+
408
+ return roadmap
409
+
410
+ # Process each task in the shifted roadmap
411
+ for day in shifted_roadmap["roadmap"]:
412
+ date = day['date']
413
+ tasks = day['tasks']
414
+ for task in tasks:
415
+ data = add_task(data, task, date)
416
+
417
+ st.session_state.updated_roadmap = data
418
+ st.success("Tasks merged into roadmap successfully!")
419
+
420
+ except Exception as e:
421
+ st.error(f"Error merging tasks: {e}")
422
+
423
+ # ---- HOME PAGE ----
424
+ if page == "Home":
425
+ st.title("📚 JEE Roadmap Planner")
426
+
427
+ st.markdown("""
428
+ ### Welcome to your JEE Study Roadmap Planner!
429
+
430
+ This tool helps you manage your JEE preparation schedule by:
431
+
432
+ 1. 📊 **Analyzing your study performance**
433
+ 2. 🔄 **Redistributing incomplete tasks**
434
+ 3. 📝 **Providing personalized feedback**
435
+
436
+ Get started by loading your roadmap data and following the step-by-step process.
437
+ """)
438
+
439
+ # Settings section
440
+ with st.expander("⚙️ Advanced Settings"):
441
+ st.session_state.max_optimizer_iterations = st.slider(
442
+ "Maximum Optimizer Iterations",
443
+ min_value=1,
444
+ max_value=5,
445
+ value=st.session_state.max_optimizer_iterations,
446
+ help="Limit how many times the optimizer will try to improve task distribution"
447
  )
448
+
449
+ st.info("Navigate using the sidebar to access different features of the app.")
450
+
451
+ # Initial data loading
452
+ if st.button("📂 Load Roadmap Data"):
453
+ success = load_initial_data()
454
+ if success:
455
+ st.session_state.first_load = True
456
+
457
+ # ---- ROADMAP MANAGER PAGE ----
458
+ elif page == "Roadmap Manager":
459
+ st.title("🗓️ Roadmap Manager")
460
+
461
+ if st.session_state.data is None:
462
+ st.warning("Please load roadmap data first from the Home page.")
463
  else:
464
+ st.markdown("### Roadmap Management Steps")
465
+
466
+ col1, col2 = st.columns(2)
467
+
468
+ with col1:
469
+ st.subheader("Step 1: Process Tasks")
470
+ if st.button("1️⃣ Mark Tasks as Incomplete"):
471
+ process_task_completion_data()
472
+
473
+ st.subheader("Step 2: Extract Tasks")
474
+ if st.button("2️⃣ Extract Incomplete Tasks"):
475
+ extract_incomplete_tasks()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
 
477
+ if st.session_state.incomplete_task_list:
478
+ st.write(f"Found {len(st.session_state.incomplete_task_list)} incomplete tasks")
479
+ with st.expander("View Incomplete Tasks"):
480
+ st.json(st.session_state.incomplete_task_list)
481
+
482
+ with col2:
483
+ st.subheader("Step 3: Prepare for Rescheduling")
484
+ if st.button("3️⃣ Extract Available Dates"):
485
+ extract_available_dates()
486
+
487
+ if st.session_state.available_dates:
488
+ with st.expander("View Available Dates"):
489
+ st.write(st.session_state.available_dates)
490
+
491
+ st.subheader("Step 4: Reschedule Tasks")
492
+ if st.button("4️⃣ Optimize Task Distribution"):
493
+ if not st.session_state.incomplete_task_list or not st.session_state.available_dates:
494
+ st.error("Please complete steps 2 and 3 first!")
495
+ else:
496
+ shift_incomplete_tasks()
497
+
498
+ if st.session_state.shifted_roadmap:
499
+ with st.expander("View Task Distribution Plan"):
500
+ try:
501
+ st.json(json.loads(st.session_state.shifted_roadmap))
502
+ except:
503
+ st.text(st.session_state.shifted_roadmap)
504
+
505
+ st.subheader("Step 5: Update Roadmap")
506
+ if st.button("5️⃣ Merge Tasks into Main Roadmap"):
507
+ if not st.session_state.shifted_roadmap:
508
+ st.error("Please complete step 4 first!")
509
+ else:
510
+ merge_shifted_tasks()
511
+
512
+ # Display original and updated roadmaps side by side
513
+ if st.session_state.data_old and st.session_state.updated_roadmap:
514
+ st.subheader("Roadmap Comparison")
515
+ col1, col2 = st.columns(2)
516
+
517
+ with col1:
518
+ st.markdown("#### Original Roadmap")
519
+ with st.expander("View Original Roadmap"):
520
+ st.json(st.session_state.data_old)
521
+
522
+ with col2:
523
+ st.markdown("#### Updated Roadmap")
524
+ with st.expander("View Updated Roadmap"):
525
+ st.json(st.session_state.updated_roadmap)
526
+
527
+ # ---- TASK ANALYSIS PAGE ----
528
+ elif page == "Task Analysis":
529
+ st.title("📊 Task Analysis")
530
+
531
+ if st.session_state.data is None:
532
+ st.warning("Please load roadmap data first from the Home page.")
533
+ else:
534
+ st.markdown("### Performance Report")
535
+
536
+ if st.button("🔍 Generate Performance Report"):
537
+ generate_report()
538
+
539
+ if st.session_state.final_report:
540
+ st.markdown(st.session_state.final_report)
541
+ else:
542
+ st.info("Click the button above to generate your performance report.")
543
+
544
+ # Add visualization options
545
+ if st.session_state.data:
546
+ st.subheader("Task Breakdown")
547
+
548
+ # Simple task statistics
549
+ if st.checkbox("Show Task Statistics"):
550
+ task_count = 0
551
+ subject_counts = {}
552
+ type_counts = {}
553
+
554
+ for day in st.session_state.data["schedule"]:
555
+ for subject in day["subjects"]:
556
+ subject_name = subject["name"]
557
+ if subject_name not in subject_counts:
558
+ subject_counts[subject_name] = 0
559
+
560
+ for task in subject["tasks"]:
561
+ subject_counts[subject_name] += 1
562
+ task_count += 1
563
+
564
+ # Count by task type
565
+ task_type = task.get("type", "Unknown")
566
+ if task_type not in type_counts:
567
+ type_counts[task_type] = 0
568
+ type_counts[task_type] += 1
569
+
570
+ st.write(f"Total tasks: {task_count}")
571
+
572
+ # Create charts for data visualization
573
+ col1, col2 = st.columns(2)
574
+
575
+ with col1:
576
+ st.subheader("Subject Distribution")
577
+ st.bar_chart(subject_counts)
578
+
579
+ with col2:
580
+ st.subheader("Task Type Distribution")
581
+ st.bar_chart(type_counts)