Hari-Prasath-M91 commited on
Commit
095a0ee
·
1 Parent(s): 885ddf4
DockerFile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,848 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import copy
4
+ import sqlite3
5
+ import operator
6
+ from fastapi import FastAPI, Query
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, Optional
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
+ from dotenv import load_dotenv
18
+
19
+ load_dotenv()
20
+
21
+ # Initialize FastAPI app
22
+ app = FastAPI(
23
+ title="JEE Roadmap Planner API",
24
+ description="API for managing and analyzing JEE Roadmaps",
25
+ version="1.0.0"
26
+ )
27
+
28
+ # Session State replacement
29
+ session_state = {
30
+ "data": None,
31
+ "full_roadmap": None,
32
+ "report_data": None,
33
+ "final_report": None,
34
+ "updated_roadmap": None
35
+ }
36
+
37
+
38
+ # AGENT 1
39
+ def load_initial_data():
40
+ try:
41
+ with open('fourdayRoadmap.json', 'r') as file:
42
+ data = json.load(file)
43
+ session_state.data = data
44
+ with open("full_roadmap.json", 'r') as file:
45
+ data = json.load(file)
46
+ session_state.full_roadmap = data
47
+ return True
48
+ except Exception as e:
49
+ return False
50
+
51
+ # Function to mark tasks as incomplete
52
+ def process_task_completion_data():
53
+ data = session_state.data
54
+ for day in data["schedule"]:
55
+ for subject in day["subjects"]:
56
+ for task in subject["tasks"]:
57
+ task["task_completed"] = False
58
+ task["completion_timestamp"] = None
59
+ session_state.data = data
60
+
61
+ def check_tot_time(day, max_hours_per_day):
62
+ tot_time = 0
63
+ for subject in day:
64
+ for task in subject["tasks"]:
65
+ tot_time += float(task['time'].split(" ")[0])
66
+
67
+ if tot_time > max_hours_per_day:
68
+ return tot_time, True
69
+ else:
70
+ return tot_time, False
71
+
72
+ def shift_roadmap(roadmap, max_hours_per_day):
73
+ roadmap = copy.deepcopy(roadmap)
74
+ incomplete_tasks_by_subject = defaultdict(list)
75
+
76
+ prev_day = roadmap[0]
77
+ for subject in prev_day["subjects"]:
78
+ subject_name = subject["name"]
79
+ tasks = subject["tasks"]
80
+
81
+ # Separate completed and incomplete tasks
82
+ incomplete_tasks = [task for task in tasks if task['task_completed'] == False]
83
+ completed_tasks = [task for task in tasks if task['task_completed'] == True]
84
+
85
+ # Store incomplete tasks per subject
86
+ if incomplete_tasks:
87
+ incomplete_tasks_by_subject[subject_name].extend(incomplete_tasks)
88
+
89
+ # Keep only completed tasks in the previous day
90
+ subject["tasks"] = completed_tasks
91
+
92
+
93
+ # Step 2: Redistribute tasks across the next 3 days
94
+ for i, next_day in enumerate(roadmap[1:]): # Next 3 days (Day 2, Day 3, Day 4)
95
+ for subject in next_day["subjects"]:
96
+ subject_name = subject["name"]
97
+ if subject_name in incomplete_tasks_by_subject and incomplete_tasks_by_subject[subject_name]:
98
+ total_tasks = len(incomplete_tasks_by_subject[subject_name])
99
+
100
+ # Task distribution based on 1/6, 2/6, and remaining
101
+ if i == 0: # First day gets 1/6 of total
102
+ tasks_to_add = ceil(total_tasks * (1 / 6))
103
+ elif i == 1: # Second day gets 2/6 of total
104
+ tasks_to_add = ceil(total_tasks * (2 / 6))
105
+ else: # Remaining tasks on the last day
106
+ tasks_to_add = len(incomplete_tasks_by_subject[subject_name])
107
+
108
+ # Append tasks to the current day's subject
109
+ subject["tasks"].extend(incomplete_tasks_by_subject[subject_name][:tasks_to_add])
110
+
111
+ # Remove assigned tasks from backlog
112
+ incomplete_tasks_by_subject[subject_name] = incomplete_tasks_by_subject[subject_name][tasks_to_add:]
113
+
114
+ # Make sure the time limit doesn't exceed for any day
115
+ _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day)
116
+ while check_time:
117
+ for subject in next_day["subjects"]:
118
+ subject_name = subject["name"]
119
+ if subject["tasks"]:
120
+ task_to_add = subject["tasks"].pop()
121
+ incomplete_tasks_by_subject[subject_name].append(task_to_add)
122
+ _, check_time = check_tot_time(next_day["subjects"], max_hours_per_day)
123
+ if not check_time:
124
+ break
125
+
126
+ return roadmap, incomplete_tasks_by_subject
127
+
128
+ def get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day):
129
+ day_index = dayNumber-1
130
+ if day_index+4 <= len(roadmap['schedule']):
131
+ shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:day_index+4], max_hours_per_day)
132
+ else:
133
+ shifted_roadmap, incomplete_tasks_by_subject = shift_roadmap(roadmap['schedule'][day_index:], max_hours_per_day)
134
+ for day in shifted_roadmap:
135
+ new_date = day["date"]
136
+
137
+ for idx, existing_day in enumerate(roadmap['schedule']):
138
+ if existing_day['date'] == new_date:
139
+ roadmap['schedule'][idx] = day
140
+ break
141
+
142
+ if any(len(v) != 0 for v in incomplete_tasks_by_subject.values()):
143
+ next_date = (datetime.strptime(roadmap['schedule'][-1]['date'], "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
144
+ next_day = roadmap['schedule'][-1]['dayNumber'] + 1
145
+ subjects = [{"name": subject_name, "tasks": tasks} for subject_name, tasks in incomplete_tasks_by_subject.items()]
146
+ roadmap['schedule'].append({
147
+ "dayNumber": next_day,
148
+ "date": next_date,
149
+ "subjects": subjects
150
+ })
151
+ return roadmap
152
+
153
+ # Step 0: Get Subjectwise Chapter and Topic order ready
154
+ def get_subjectwise_tasks(roadmap):
155
+ sub_tasks = {
156
+ "Physics": defaultdict(list),
157
+ "Chemistry": defaultdict(list),
158
+ "Maths": defaultdict(list)
159
+ }
160
+
161
+ for day in roadmap["schedule"]:
162
+ for subject in day['subjects']:
163
+ sub = sub_tasks[subject['name']]
164
+ for task in subject['tasks']:
165
+ if task['subtopic'] not in sub[task['ChapterName']]:
166
+ sub[task['ChapterName']].append(task['subtopic'])
167
+
168
+ return sub_tasks
169
+
170
+ # Step 1: Extract all tasks per subject and track time allocation per day
171
+ def extract_tasks(roadmap):
172
+ subjectwise_tasks = defaultdict(list)
173
+ daily_subjectwise_time_allocation = defaultdict(lambda: defaultdict(float))
174
+
175
+ for day_index, day in enumerate(roadmap['schedule']):
176
+ for subject in day["subjects"]:
177
+ subject_name = subject["name"]
178
+ total_time = sum(float(task['time'].split(" ")[0]) for task in subject["tasks"])
179
+
180
+ daily_subjectwise_time_allocation[day_index][subject_name] = total_time
181
+ subjectwise_tasks[subject_name].extend(subject["tasks"])
182
+
183
+ return subjectwise_tasks, daily_subjectwise_time_allocation
184
+
185
+ # Step 2: Sort all tasks for each subject
186
+ def sort_tasks(tasks, reference):
187
+ task_type_priority = {
188
+ "Concept Understanding": 0,
189
+ "Question Practice": 1,
190
+ "Revision": 2,
191
+ "Test": 3
192
+ }
193
+ chapter_order = list(reference.keys())
194
+
195
+ def task_sort_key(task):
196
+ chapter = task["ChapterName"]
197
+ subtopic = task["subtopic"]
198
+ type_priority = task_type_priority.get(task["type"], 99)
199
+
200
+ chapter_idx = chapter_order.index(chapter) if chapter in reference else float('inf')
201
+ subtopic_idx = reference[chapter].index(subtopic) if subtopic in reference.get(chapter, []) else float('inf')
202
+
203
+ return (chapter_idx, subtopic_idx, type_priority)
204
+
205
+ return sorted(tasks, key=task_sort_key)
206
+
207
+ # Helper function to get task time in hours
208
+ def get_task_time(task):
209
+ return float(task['time'].split(" ")[0])
210
+
211
+ # Step 3: Sort the roadmap by arranging the sorted tasks, preserving original time allocation
212
+ def shift_and_sort_the_roadmap(full_roadmap, roadmap, dayNumber, max_hours_per_day):
213
+ roadmap = copy.deepcopy(roadmap)
214
+ roadmap = get_shifted_roadmap(roadmap, dayNumber, max_hours_per_day)
215
+ subject_refs = get_subjectwise_tasks(full_roadmap) # Load the full roadmap to obtain the correct chapter orders
216
+ subject_all_tasks, subject_day_time_allocation = extract_tasks(roadmap)
217
+
218
+ # Sort all tasks for each subject
219
+ for subject in subject_all_tasks:
220
+ subject_all_tasks[subject] = sort_tasks(subject_all_tasks[subject], subject_refs[subject])
221
+
222
+ # Redistribute tasks based on time allocation, strictly maintaining sequence
223
+ for day_index, day in enumerate(roadmap['schedule']):
224
+ day_time = 0
225
+ for subject in day["subjects"]:
226
+ subject_name = subject["name"]
227
+ target_time = subject_day_time_allocation[day_index][subject_name]
228
+
229
+ selected_tasks = []
230
+ current_time = 0
231
+ tasks = subject_all_tasks[subject_name]
232
+ while tasks and current_time < target_time:
233
+ next_task = tasks[0] # Take the next task from the sorted sequence
234
+ task_time = get_task_time(next_task)
235
+
236
+ if day_time + task_time <= max_hours_per_day: # Allow if its under the max limit
237
+ selected_tasks.append(tasks.pop(0))
238
+ current_time += task_time
239
+ day_time += task_time
240
+ else:
241
+ # If Task doesn't fit, save for next day
242
+ break
243
+
244
+ if day_index == len(roadmap['schedule']) - 1:
245
+ if tasks:
246
+ for task in tasks:
247
+ selected_tasks.append(task)
248
+
249
+ # Update the subject's tasks
250
+ subject["tasks"] = selected_tasks
251
+ subject_all_tasks[subject_name] = tasks
252
+
253
+ with open("current_roadmap.json", "w") as f:
254
+ json.dump(roadmap, f, indent=4)
255
+
256
+ session_state.updated_roadmap = roadmap
257
+
258
+ # AGENT 2
259
+ def generate_sql_for_report(llm, prompt):
260
+ table_struct = """
261
+ CREATE TABLE IF NOT EXISTS roadmap (
262
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
263
+ day_num INTEGER,
264
+ date TEXT,
265
+ subject TEXT,
266
+ chapter_name TEXT,
267
+ task_type TEXT,
268
+ time TEXT,
269
+ subtopic TEXT,
270
+ task_completed BOOLEAN,
271
+ completion_timestamp TEXT
272
+ )
273
+ """
274
+
275
+ response = llm.invoke(
276
+ [
277
+ SystemMessage(content=f"""You are a helper who runs in the background of an AI agent,
278
+ which helps students for their JEE Preparation. Now your job is to analyze the user's prompt and
279
+ create an SQL query to extract the related Information from an sqlite3 database with the table
280
+ structure: {table_struct}.
281
+ Note: For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
282
+ so on, it tells the amount of time required to complete that specific task. So make sure
283
+ to create queries that compare just the numbers within the text. For the task_type column,
284
+ the data is either of these (Concept Understanding, Question Practice, Revision or Test)
285
+ You will also make sure multiple times that you give an SQL
286
+ Query that adheres to the given table structure, and you output just the SQL query.
287
+ Do not include anything else like new line statements, ```sql or any other text. Your output
288
+ is going to be directly fed into a Python script to extract the required information. So,
289
+ please follow all the given instructions.
290
+ Verify multiple times that the SQL query is error free for the SQLite3 format."""),
291
+ HumanMessage(content=f"""Keeping the table structure in mind: {table_struct},
292
+ Convert this prompt to an SQL query for the given table: {prompt}. Make sure your
293
+ output is just the SQL query, which can directly be used to extract required content.""")
294
+ ]
295
+ )
296
+ return response.content.strip()
297
+
298
+ def get_sql_data_for_report(sql_query):
299
+ conn = sqlite3.connect("jee_full_roadmap.db")
300
+ cursor = conn.cursor()
301
+
302
+ results = []
303
+ queries = [q.strip() for q in sql_query.strip().split(';') if q.strip()]
304
+
305
+ for query in queries:
306
+ cursor.execute(query)
307
+ columns = [desc[0] for desc in cursor.description]
308
+ rows = cursor.fetchall()
309
+ results.append({
310
+ "query": query,
311
+ "columns": columns,
312
+ "rows": rows
313
+ })
314
+ conn.close()
315
+
316
+ return results
317
+
318
+ def create_db_for_report(roadmap_data):
319
+ try:
320
+ conn = sqlite3.connect("jee_full_roadmap.db")
321
+ cursor = conn.cursor()
322
+
323
+ cursor.execute("DROP TABLE IF EXISTS roadmap")
324
+ cursor.execute("""
325
+ CREATE TABLE roadmap (
326
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
327
+ day_num INTEGER,
328
+ date TEXT,
329
+ subject TEXT,
330
+ chapter_name TEXT,
331
+ task_type TEXT,
332
+ time TEXT,
333
+ subtopic TEXT,
334
+ task_completed BOOLEAN,
335
+ completion_timestamp TEXT
336
+ )
337
+ """)
338
+
339
+ for day in roadmap_data["schedule"]:
340
+ date = day["date"]
341
+ day_num = day["dayNumber"]
342
+ for subj in day["subjects"]:
343
+ subject = subj["name"]
344
+ for task in subj["tasks"]:
345
+ cursor.execute("""
346
+ INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic, task_completed, completion_timestamp)
347
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
348
+ """, (
349
+ day_num,
350
+ date,
351
+ subject,
352
+ task["ChapterName"],
353
+ task["type"],
354
+ task["time"],
355
+ task["subtopic"],
356
+ task["task_completed"],
357
+ task["completion_timestamp"]
358
+ ))
359
+ conn.commit()
360
+ conn.close()
361
+ print("✅ Database created and data inserted successfully.")
362
+ except Exception as e:
363
+ print(f"⚠️ Error initializing database: {e}")
364
+
365
+ # Function to generate report
366
+ llm = ChatOpenAI(model="gpt-4o-mini")
367
+ class Section(BaseModel):
368
+ name: str = Field(
369
+ description="Name for this section of the report.",
370
+ )
371
+ description: str = Field(
372
+ description="Brief overview of the main topics and concepts to be covered in this section.",
373
+ )
374
+ data_requirements: str = Field(
375
+ description="Description of the data needed from the roadmap database to write this section.",
376
+ )
377
+
378
+ class Sections(BaseModel):
379
+ sections: List[Section] = Field(
380
+ description="Sections of the report.",
381
+ )
382
+
383
+ planner = llm.with_structured_output(Sections)
384
+
385
+ class State(TypedDict):
386
+ sections: list[Section] # List of report sections
387
+ completed_sections: Annotated[list, operator.add] # All workers write to this key in parallel
388
+ final_report: str # Final report
389
+
390
+ # Combined helper-worker state
391
+ class ProcessorState(TypedDict):
392
+ section: Section
393
+ completed_sections: Annotated[list, operator.add]
394
+
395
+ def orchestrator(state: State):
396
+ """Orchestrator that generates a plan for the report with data requirements"""
397
+
398
+ schema = """CREATE TABLE IF NOT EXISTS roadmap (
399
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
400
+ day_num INTEGER,
401
+ date TEXT, -- [yyyy-mm-dd]
402
+ subject TEXT, -- (Physics, Chemistry or Maths)
403
+ chapter_name TEXT,
404
+ task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test)
405
+ time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on -- Tells the amount of time required to finish the task
406
+ subtopic TEXT,
407
+ task_completed BOOLEAN, -- 0/1 indicates task completion status
408
+ completion_timestamp TEXT
409
+ )"""
410
+
411
+ # Generate queries
412
+ report_sections = planner.invoke(
413
+ [
414
+ SystemMessage(content=f"""You are responsible for creating a structured plan for a JEE preparation analysis report.
415
+
416
+ Audience: The report is intended primarily for students, but must also be insightful to mentors and parents.
417
+ Keep the language motivational and supportive, with actionable insights backed by data.
418
+
419
+ Report Format: The report will be composed of exactly 4 concise sections. Your job is to define these sections. Each section must include:
420
+ - **Name**: A short, descriptive title
421
+ - **Description**: What the section analyzes and how it helps the student
422
+ - **Data Requirements**: A plain-English description of what fields and metrics are needed from the roadmap
423
+ database whose schema is given here: {schema}
424
+
425
+ DO NOT invent new sections or formats. Use exactly the following four section templates and fill in the
426
+ descriptions and data requirements precisely.
427
+
428
+ ---
429
+
430
+ ### Study Time Analysis
431
+
432
+ **Description**: Analyze how much total time the student planned to spend vs how much they actually completed,
433
+ across different subjects and task types. This will help the student understand where their time is really going.
434
+
435
+ **Data Requirements**:
436
+ - Fields: `subject`, `task_type`, `time`, `task_completed`
437
+ - Metrics:
438
+ - Total planned time → SUM of all `time`
439
+ - Total actual time → SUM of `time` where `task_completed = 1`
440
+ - Grouped by both `subject` and `task_type`
441
+
442
+ ---
443
+
444
+ ### Task Completion Metrics
445
+
446
+ **Description**: Measure the student’s consistency and follow-through by looking at completion rates across
447
+ subjects and task types.
448
+
449
+ **Data Requirements**:
450
+ - Fields: `subject`, `task_type`, `task_completed`
451
+ - Metrics:
452
+ - Total tasks → COUNT of all tasks
453
+ - Completed tasks → COUNT of tasks where `task_completed = 1`
454
+ - Completion percentage per subject and task type
455
+
456
+ ---
457
+
458
+ ### Study Balance Analysis
459
+
460
+ **Description**: Evaluate how the student's study time is distributed across task types (e.g., Practice, Revision, Test)
461
+ within each subject. This highlights over- or under-emphasis on any category.
462
+
463
+ **Data Requirements**:
464
+ - Fields: `subject`, `task_type`, `time`
465
+ - Metrics:
466
+ - SUM of `time` for each (subject, task_type) pair where task_completed = 1
467
+ - Relative distribution of time per subject to detect imbalance
468
+
469
+ ---
470
+
471
+ ### Strengths and Areas for Improvement
472
+
473
+ **Description**:
474
+ This section analyzes how the student's effort is distributed — not by estimating how long they spent,
475
+ but by combining how many tasks they completed and how much time those completed tasks represent.
476
+ This helps identify:
477
+ - Subjects and task types where the student is showing strong commitment
478
+ - Areas that may be neglected or inconsistently approached
479
+
480
+ **Data Requirements**:
481
+ - Fields: subject, task_type, task_completed, time
482
+ - Metrics (filtered where task_completed = 1):
483
+ - Total Number of completed tasks
484
+ - Total amount of time spent
485
+ - Grouped by subject and task_type
486
+ ---
487
+
488
+ Important Constraints:
489
+ - You must include **all the mentioned fields** in the `data_requirements` — no assumptions
490
+ - Use only **aggregate metrics** — no need for per-task or per-day analysis
491
+ - Keep descriptions student-focused, clear, and motivational
492
+ - Do not alter section names or invent new ones
493
+ - Do not output anything outside the strict format above
494
+
495
+ Your output will be passed into a structured data pipeline. Return only the filled-out section definitions as described above.
496
+ """),
497
+ HumanMessage(content="""Use the given table structure of the roadmap and decide all the sections of
498
+ the report along with what should be in it and the clearly mention all the data thats required for it
499
+ from the roadmap table"""),
500
+ ]
501
+ )
502
+
503
+ return {"sections": report_sections.sections}
504
+
505
+ def processor(state: ProcessorState):
506
+ """Combined helper and worker - gets data and writes section in one step"""
507
+
508
+ section = state['section']
509
+
510
+ # HELPER PART: Get data for this section
511
+ sql_query = generate_sql_for_report(llm, section.data_requirements)
512
+ rows = get_sql_data_for_report(sql_query)
513
+ # WORKER PART: Write the section using the data
514
+ section_result = llm.invoke(
515
+ [
516
+ SystemMessage(
517
+ content=f"""Create a concise, data-driven JEE preparation report section that provides actionable insights for students,
518
+ parents, and mentors.
519
+
520
+ Requirements:
521
+ 1. Begin directly with key metrics and insights - no introductory preamble
522
+ 2. Use specific numbers, percentages, and ratios to quantify performance
523
+ 3. Include concise tables or bullet points for clarity where appropriate
524
+ 4. Highlight patterns related to:
525
+ - Task completion rates
526
+ - Time allocation efficiency
527
+ - Subject/topic focus distribution
528
+ - Study consistency patterns
529
+ 5. For each observation, provide a brief actionable recommendation focused on student improvement.
530
+ 6. Use professional but motivational tone appropriate for academic context
531
+ 7. Strictly use Markdown for formatting all the tables and the numbers
532
+ 8. Strictly keep each section very focused and write it under 0 to 50 words
533
+ 9. Verify the formatting of all the tables multiple times to ensure the markdown is correct.
534
+ 10. Check all the numbers and calculations made by you multiple times to ensure accuracy
535
+
536
+ Base all analysis strictly on the provided data - avoid assumptions beyond what's explicitly given to you.
537
+ Don't assume anything else, even a little bit.
538
+
539
+ *Important*
540
+ If you receive an empty data input, understand that the student hasn't done tasks matching the given data description. Also,
541
+ know that this report is for the student to improve themselves, and they have no part in making sure the data is logged for
542
+ this analysis. Deeply analyze the SQL query ->{sql_query} and the data description ->{section.data_requirements} used to
543
+ extract the data and figure out why there was no data available in the roadmap, which the student went through and write
544
+ the section accordingly.
545
+ """
546
+ ),
547
+ HumanMessage(
548
+ content=f"""Here is the section name: {section.name} and description: {section.description}
549
+ Data for writing this section: {rows}"""
550
+ ),
551
+ ]
552
+ )
553
+
554
+ # Return completed section
555
+ return {"completed_sections": [section_result.content]}
556
+
557
+ def synthesizer(state: State):
558
+ """Synthesize full report from sections"""
559
+
560
+ # List of completed sections
561
+ completed_sections = state["completed_sections"]
562
+
563
+ # Format completed section to str to use as context for final sections
564
+ completed_report_sections = "\n\n---\n\n".join(completed_sections)
565
+
566
+ return {"final_report": completed_report_sections}
567
+
568
+ # Assign processors function
569
+ def assign_processors(state: State):
570
+ """Assign a processor to each section in the plan"""
571
+ return [Send("processor", {"section": s}) for s in state["sections"]]
572
+
573
+ def generate_report(full_roadmap):
574
+ # Build workflow
575
+ workflow_builder = StateGraph(State)
576
+
577
+ # Add the nodes
578
+ workflow_builder.add_node("orchestrator", orchestrator)
579
+ workflow_builder.add_node("processor", processor)
580
+ workflow_builder.add_node("synthesizer", synthesizer)
581
+
582
+ # Add edges to connect nodes
583
+ workflow_builder.add_edge(START, "orchestrator")
584
+ workflow_builder.add_conditional_edges("orchestrator", assign_processors, ["processor"])
585
+ workflow_builder.add_edge("processor", "synthesizer")
586
+ workflow_builder.add_edge("synthesizer", END)
587
+
588
+ # Compile the workflow
589
+ workflow = workflow_builder.compile()
590
+
591
+ # Initialize database
592
+ create_db_for_report(full_roadmap)
593
+
594
+ # Invoke
595
+ state = workflow.invoke({})
596
+
597
+ session_state.final_report = state["final_report"]
598
+
599
+ # AGENT 3
600
+ def initialize_roadmap_db():
601
+ if not os.path.exists("jee_roadmap.db"):
602
+ try:
603
+ with open("full_roadmap.json") as f:
604
+ roadmap_data = json.load(f)
605
+
606
+ conn = sqlite3.connect("jee_roadmap.db")
607
+ cursor = conn.cursor()
608
+
609
+ cursor.execute("""
610
+ CREATE TABLE IF NOT EXISTS roadmap (
611
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
612
+ day_num INTEGER,
613
+ date TEXT,
614
+ subject TEXT,
615
+ chapter_name TEXT,
616
+ task_type TEXT,
617
+ time TEXT,
618
+ subtopic TEXT
619
+ )
620
+ """)
621
+
622
+ for day in roadmap_data["schedule"]:
623
+ date = day["date"]
624
+ day_num = day["dayNumber"]
625
+ for subj in day["subjects"]:
626
+ subject = subj["name"]
627
+ for task in subj["tasks"]:
628
+ cursor.execute("""
629
+ INSERT INTO roadmap (day_num, date, subject, chapter_name, task_type, time, subtopic)
630
+ VALUES (?, ?, ?, ?, ?, ?, ?)
631
+ """, (
632
+ day_num,
633
+ date,
634
+ subject,
635
+ task["ChapterName"],
636
+ task["type"],
637
+ task["time"],
638
+ task["subtopic"]
639
+ ))
640
+
641
+ conn.commit()
642
+ conn.close()
643
+ print("✅ Database created and data inserted successfully.")
644
+ except Exception as e:
645
+ print(f"⚠️ Error initializing database: {e}")
646
+
647
+ def get_chapters_and_subtopics():
648
+ with open("full_roadmap.json", "r") as f:
649
+ data = json.load(f)
650
+
651
+ ch_subt = {
652
+ "Physics": {},
653
+ "Chemistry": {},
654
+ "Maths": {}
655
+ }
656
+
657
+ for day in data["schedule"]:
658
+ for subject in day['subjects']:
659
+ sub = ch_subt[subject['name']]
660
+ for task in subject['tasks']:
661
+ sub[task['ChapterName']] = []
662
+
663
+ for day in data["schedule"]:
664
+ for subject in day['subjects']:
665
+ sub = ch_subt[subject['name']]
666
+ for task in subject['tasks']:
667
+ if task['subtopic'] not in sub[task['ChapterName']]:
668
+ sub[task['ChapterName']].append(task['subtopic'])
669
+
670
+ return ch_subt
671
+
672
+ # Function to convert NL query to SQL
673
+ def generate_sql_from_nl(prompt):
674
+ table_struct = """CREATE TABLE IF NOT EXISTS roadmap (
675
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
676
+ day_num INTEGER,
677
+ date TEXT, -- [yyyy-mm-dd]
678
+ subject TEXT, -- [Physics, Chemistry or Maths]
679
+ chapter_name TEXT,
680
+ task_type TEXT, -- (Concept Understanding, Question Practice, Revision, Test)
681
+ time TEXT, -- formatted like '0.5 hour', '1 hour', '2 Hours', and so on
682
+ subtopic TEXT,
683
+ )"""
684
+
685
+ ch_subt = get_chapters_and_subtopics()
686
+ response = llm.invoke(
687
+ [
688
+ SystemMessage(
689
+ content=f"""You are an helper who runs in the background of an AI agent,
690
+ which helps students for their JEE Preparation. Now your Job is to analyze the users prompt and
691
+ create an SQL query to extract the related Information from an sqlite3 database with the table
692
+ structure: {table_struct}.
693
+ Note:
694
+ - For the time column, the data is formatted like '0.5 hour', '1 hour', '2 hours' and
695
+ so on. So make sure to create queries that compare just the numbers within the text.
696
+ - If the student mention about any chapters or subtopics, browse through this json file {ch_subt},
697
+ find the one with the closest match to the users query and use only those exact names of Chapers
698
+ and Subtopics present in this file to create SQL the query.
699
+ - For date related queries, refer today's date {datetime.now().date()}
700
+
701
+ You will also make sure multiple times that you give an SQL
702
+ Query that adheres to the given table structure, and you Output just the SQL query.
703
+ Do not include anyting else like new line statements, ```sql or any other text. Your output
704
+ is going to be directly fed into a Python script to extract the required information. So,
705
+ please follow all the given Instructions.
706
+ """
707
+ ),
708
+ HumanMessage(
709
+ content=f"""Keeping the table structure in mind: {table_struct},
710
+ Convert this prompt to an SQL query for the given table: {prompt}. Make sure your
711
+ output is just the SQL query, which can directly be used to extract required content"""
712
+ ),
713
+ ]
714
+ )
715
+
716
+ # Return completed section
717
+ return response.content.strip()
718
+
719
+ # Function to fetch data from SQLite
720
+ def fetch_data_from_sql(sql_query):
721
+ conn = sqlite3.connect("jee_roadmap.db")
722
+ cursor = conn.cursor()
723
+ cursor.execute(sql_query)
724
+ columns = [desc[0] for desc in cursor.description]
725
+ rows = cursor.fetchall()
726
+ data = {
727
+ "query": sql_query,
728
+ "columns": columns,
729
+ "rows": rows
730
+ }
731
+ conn.close()
732
+ return data
733
+
734
+ # Function to convert SQL output to natural language
735
+ def generate_nl_from_sql_output(prompt, data):
736
+ response = llm.invoke(
737
+ [
738
+ SystemMessage(
739
+ content=f"""You are an helpful AI chatbot working under the roadmap
740
+ section of an AI Agent, whose role is to aid students in their preparation for the JEE examination.
741
+ You are going to play a very crucial role of a Roadmap Assistant, who helps the student out with whatever query
742
+ they have related to their roadmap, the data required to answer the users query is already extracted
743
+ from the Roadmap table of a SQLite3 database and given to you here {data}. Analyse the users query deeply and
744
+ reply to it with the relevant information from the given data in a supportive manner. If you get empty data
745
+ as an input, deeply analyze the user's prompt and the sql query and give a suitable reply."""
746
+ ),
747
+ HumanMessage(
748
+ content=f"""Answer to this users query using the data given to you, while keeping
749
+ your role in mind: {prompt}"""
750
+ ),
751
+ ]
752
+ )
753
+
754
+ # Return completed section
755
+ return response.content.strip()
756
+
757
+ # Main function for chatbot
758
+ def answer_user_query(prompt):
759
+ initialize_roadmap_db()
760
+ query = generate_sql_from_nl(prompt)
761
+ data = fetch_data_from_sql(query)
762
+ return generate_nl_from_sql_output(prompt, data)
763
+
764
+
765
+ # --- AGENT 1: Task Analysis (Task Analysis Page) ---
766
+ @app.get("/agent1")
767
+ def agent1(choice: Optional[str] = Query("Four Day Roadmap", description="Choose roadmap: 'Four Day Roadmap' or 'Full Roadmap'")):
768
+ """
769
+ Agent 1 - Task Analysis: Builds a performance report based on selected roadmap.
770
+ """
771
+
772
+ # Handle choice of roadmap
773
+ if choice == "Four Day Roadmap":
774
+ if session_state["data"] is None:
775
+ return {"error": "Roadmap data not loaded. Load data first."}
776
+ session_state["report_data"] = session_state["data"]
777
+
778
+ elif choice == "Full Roadmap":
779
+ with open("synthesized_full_roadmap.json", "r") as f:
780
+ session_state["report_data"] = json.load(f)
781
+
782
+ # Generate performance report
783
+ if session_state["report_data"]:
784
+ generate_report(session_state["report_data"])
785
+ return {
786
+ "final_report": session_state["final_report"]
787
+ }
788
+
789
+ return {"message": "No report data available."}
790
+
791
+
792
+ # --- AGENT 2: Roadmap Manager (Roadmap Manager Page) ---
793
+ @app.get("/agent2")
794
+ def agent2(
795
+ dayNumber: int = Query(1, description="Today's day number for rescheduling tasks"),
796
+ max_hours_per_day: int = Query(8, description="Maximum number of hours per day")
797
+ ):
798
+ """
799
+ Agent 2 - Roadmap Manager: Processes tasks and optimizes the roadmap based on user input.
800
+ """
801
+
802
+ if session_state["data"] is None:
803
+ return {"error": "Roadmap data not loaded. Load data first."}
804
+
805
+ # Process tasks as incomplete
806
+ process_task_completion_data()
807
+
808
+ # Optimize task distribution with user input
809
+ shift_and_sort_the_roadmap(
810
+ session_state["full_roadmap"],
811
+ session_state["data"],
812
+ dayNumber=dayNumber,
813
+ max_hours_per_day=max_hours_per_day
814
+ )
815
+
816
+ # Return full updated roadmap
817
+ if session_state["data"] and session_state["updated_roadmap"]:
818
+ return {
819
+ "original_roadmap": session_state["data"],
820
+ "updated_roadmap": session_state["updated_roadmap"]
821
+ }
822
+
823
+ return {"message": "Optimization not completed."}
824
+
825
+
826
+ # --- AGENT 3: Roadmap Chatbot (Roadmap Chatbot Page) ---
827
+ @app.get("/agent3")
828
+ def agent3(query: str = Query(..., description="User's message to the chatbot")):
829
+ """
830
+ Agent 3 - Roadmap Chatbot Assistant: Answers user questions about the roadmap in a chat-like style.
831
+ """
832
+
833
+ if not query:
834
+ return {"error": "Please provide a query."}
835
+
836
+ try:
837
+ response = answer_user_query(query)
838
+ return {
839
+ "chat_response": {
840
+ "role": "assistant",
841
+ "message": response
842
+ }
843
+ }
844
+ except Exception as e:
845
+ return {"chat_response": {
846
+ "role": "assistant",
847
+ "message": f"Sorry, I encountered an error: {e}"
848
+ }}
fourdayRoadmap.json ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schedule": [
3
+ {
4
+ "dayNumber": 1,
5
+ "date": "2025-02-18",
6
+ "subjects": [
7
+ {
8
+ "name": "Physics",
9
+ "tasks": [
10
+ {
11
+ "ChapterName": "Physics and Measurement",
12
+ "type": "Concept Understanding",
13
+ "time": "1.0 hour",
14
+ "subtopic": "Units of measurements, System of Units, SI Units"
15
+ },
16
+ {
17
+ "ChapterName": "Physics and Measurement",
18
+ "type": "Question Practice",
19
+ "time": "1.0 hour",
20
+ "subtopic": "Fundamental and derived units"
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "name": "Chemistry",
26
+ "tasks": [
27
+ {
28
+ "ChapterName": "Some Basic Concepts in Chemistry",
29
+ "type": "Concept Understanding",
30
+ "time": "1.0 hour",
31
+ "subtopic": "Matter and its nature"
32
+ },
33
+ {
34
+ "ChapterName": "Some Basic Concepts in Chemistry",
35
+ "type": "Question Practice",
36
+ "time": "1.0 hour",
37
+ "subtopic": "Dalton's Atomic Theory"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "name": "Maths",
43
+ "tasks": [
44
+ {
45
+ "ChapterName": "Sets, Relations and Functions",
46
+ "type": "Concept Understanding",
47
+ "time": "0.5 hours",
48
+ "subtopic": "Sets and their representation"
49
+ },
50
+ {
51
+ "ChapterName": "Sets, Relations and Functions",
52
+ "type": "Question Practice",
53
+ "time": "0.5 hours",
54
+ "subtopic": "Union"
55
+ },
56
+ {
57
+ "ChapterName": "Sets, Relations and Functions",
58
+ "type": "Revision",
59
+ "time": "0.5 hours",
60
+ "subtopic": "Intersection and complement of sets and their algebraic properties"
61
+ },
62
+ {
63
+ "ChapterName": "Sets, Relations and Functions",
64
+ "type": "Test",
65
+ "time": "0.5 hours",
66
+ "subtopic": "Power set"
67
+ }
68
+ ]
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "dayNumber": 2,
74
+ "date": "2025-02-19",
75
+ "subjects": [
76
+ {
77
+ "name": "Physics",
78
+ "tasks": [
79
+ {
80
+ "ChapterName": "Physics and Measurement",
81
+ "type": "Revision",
82
+ "time": "1.0 hour",
83
+ "subtopic": "Least count, significant figures, Errors in measurements"
84
+ },
85
+ {
86
+ "ChapterName": "Physics and Measurement",
87
+ "type": "Test",
88
+ "time": "1.0 hour",
89
+ "subtopic": "Dimensions of Physics quantities, dimensional analysis, and its applications"
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ "name": "Chemistry",
95
+ "tasks": [
96
+ {
97
+ "ChapterName": "Some Basic Concepts in Chemistry",
98
+ "type": "Revision",
99
+ "time": "1.0 hour",
100
+ "subtopic": "Concept of atom, molecule, element, and compound"
101
+ },
102
+ {
103
+ "ChapterName": "Some Basic Concepts in Chemistry",
104
+ "type": "Test",
105
+ "time": "1.0 hour",
106
+ "subtopic": "Laws of chemical combination"
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ "name": "Maths",
112
+ "tasks": [
113
+ {
114
+ "ChapterName": "Sets, Relations and Functions",
115
+ "type": "Concept Understanding",
116
+ "time": "0.5 hours",
117
+ "subtopic": "Relation"
118
+ },
119
+ {
120
+ "ChapterName": "Sets, Relations and Functions",
121
+ "type": "Question Practice",
122
+ "time": "0.5 hours",
123
+ "subtopic": "Type of relations"
124
+ },
125
+ {
126
+ "ChapterName": "Sets, Relations and Functions",
127
+ "type": "Revision",
128
+ "time": "0.5 hours",
129
+ "subtopic": "equivalence relations"
130
+ },
131
+ {
132
+ "ChapterName": "Sets, Relations and Functions",
133
+ "type": "Test",
134
+ "time": "0.5 hours",
135
+ "subtopic": "Functions"
136
+ }
137
+ ]
138
+ }
139
+ ]
140
+ },
141
+ {
142
+ "dayNumber": 3,
143
+ "date": "2025-02-20",
144
+ "subjects": [
145
+ {
146
+ "name": "Physics",
147
+ "tasks": [
148
+ {
149
+ "ChapterName": "Kinematics",
150
+ "type": "Concept Understanding",
151
+ "time": "2 hours",
152
+ "subtopic": "Frame of reference, motion in a straight line, Position-time graph, speed and velocity"
153
+ }
154
+ ]
155
+ },
156
+ {
157
+ "name": "Chemistry",
158
+ "tasks": [
159
+ {
160
+ "ChapterName": "Some Basic Concepts in Chemistry",
161
+ "type": "Concept Understanding",
162
+ "time": "1.0 hour",
163
+ "subtopic": "Atomic and molecular masses"
164
+ },
165
+ {
166
+ "ChapterName": "Some Basic Concepts in Chemistry",
167
+ "type": "Question Practice",
168
+ "time": "1.0 hour",
169
+ "subtopic": "Mole concept"
170
+ }
171
+ ]
172
+ },
173
+ {
174
+ "name": "Maths",
175
+ "tasks": [
176
+ {
177
+ "ChapterName": "Sets, Relations and Functions",
178
+ "type": "Concept Understanding",
179
+ "time": "0.5 hours",
180
+ "subtopic": "one-one, into and onto functions"
181
+ },
182
+ {
183
+ "ChapterName": "Sets, Relations and Functions",
184
+ "type": "Question Practice",
185
+ "time": "0.5 hours",
186
+ "subtopic": "the composition of functions."
187
+ },
188
+ {
189
+ "ChapterName": "Complex Numbers and Quadratic Equations",
190
+ "type": "Revision",
191
+ "time": "1.0 hour",
192
+ "subtopic": "Complex numbers as ordered pairs of reals"
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ },
198
+ {
199
+ "dayNumber": 4,
200
+ "date": "2025-02-21",
201
+ "subjects": [
202
+ {
203
+ "name": "Physics",
204
+ "tasks": [
205
+ {
206
+ "ChapterName": "Kinematics",
207
+ "type": "Question Practice",
208
+ "time": "2 hours",
209
+ "subtopic": "Uniform and non-uniform motion, average speed and instantaneous velocity"
210
+ }
211
+ ]
212
+ },
213
+ {
214
+ "name": "Chemistry",
215
+ "tasks": [
216
+ {
217
+ "ChapterName": "Some Basic Concepts in Chemistry",
218
+ "type": "Revision",
219
+ "time": "1.0 hour",
220
+ "subtopic": "Molar mass"
221
+ },
222
+ {
223
+ "ChapterName": "Some Basic Concepts in Chemistry",
224
+ "type": "Test",
225
+ "time": "1.0 hour",
226
+ "subtopic": "Percentage composition"
227
+ }
228
+ ]
229
+ },
230
+ {
231
+ "name": "Maths",
232
+ "tasks": [
233
+ {
234
+ "ChapterName": "Complex Numbers and Quadratic Equations",
235
+ "type": "Test",
236
+ "time": "2 hours",
237
+ "subtopic": "Representation of complex numbers in the form a + ib and their representation in a plane"
238
+ }
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ ]
244
+ }
full_roadmap.json ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ langchain
4
+ langchain-openai
5
+ langgraph
6
+ pydantic
7
+ typing-extensions
synthesized_full_roadmap.json ADDED
The diff for this file is too large to render. See raw diff