@gonzih/exam-prep-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ export interface ReadinessReport {
2
+ score: number;
3
+ strongTopics: TopicScore[];
4
+ weakTopics: TopicScore[];
5
+ coveragePercent: number;
6
+ trend: 'improving' | 'declining' | 'stable';
7
+ recommendation: string;
8
+ estimatedGrade: string;
9
+ }
10
+ export interface TopicScore {
11
+ topic: string;
12
+ accuracy: number;
13
+ attempts: number;
14
+ }
15
+ export declare function calculateReadiness(profileId: string, examType: string, examDate: string): ReadinessReport;
@@ -0,0 +1,272 @@
1
+ import { getTopicStats, getRecentSessions } from './db.js';
2
+ import { EXAM_TOPICS } from './study-plan.js';
3
+ import { QUESTION_BANK } from './questions.js';
4
+ // Grade mapping for different exam types
5
+ const GRADE_MAPPINGS = {
6
+ AP_Biology: (s) => {
7
+ if (s >= 85)
8
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
9
+ if (s >= 70)
10
+ return 'AP Score: 4/5 (Well Qualified)';
11
+ if (s >= 55)
12
+ return 'AP Score: 3/5 (Qualified)';
13
+ if (s >= 40)
14
+ return 'AP Score: 2/5 (Possibly Qualified)';
15
+ return 'AP Score: 1/5 (No Recommendation)';
16
+ },
17
+ AP_Chemistry: (s) => {
18
+ if (s >= 85)
19
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
20
+ if (s >= 70)
21
+ return 'AP Score: 4/5 (Well Qualified)';
22
+ if (s >= 55)
23
+ return 'AP Score: 3/5 (Qualified)';
24
+ if (s >= 40)
25
+ return 'AP Score: 2/5 (Possibly Qualified)';
26
+ return 'AP Score: 1/5 (No Recommendation)';
27
+ },
28
+ AP_US_History: (s) => {
29
+ if (s >= 85)
30
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
31
+ if (s >= 70)
32
+ return 'AP Score: 4/5 (Well Qualified)';
33
+ if (s >= 55)
34
+ return 'AP Score: 3/5 (Qualified)';
35
+ if (s >= 40)
36
+ return 'AP Score: 2/5 (Possibly Qualified)';
37
+ return 'AP Score: 1/5 (No Recommendation)';
38
+ },
39
+ AP_World_History: (s) => {
40
+ if (s >= 85)
41
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
42
+ if (s >= 70)
43
+ return 'AP Score: 4/5 (Well Qualified)';
44
+ if (s >= 55)
45
+ return 'AP Score: 3/5 (Qualified)';
46
+ if (s >= 40)
47
+ return 'AP Score: 2/5 (Possibly Qualified)';
48
+ return 'AP Score: 1/5 (No Recommendation)';
49
+ },
50
+ AP_Physics: (s) => {
51
+ if (s >= 85)
52
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
53
+ if (s >= 70)
54
+ return 'AP Score: 4/5 (Well Qualified)';
55
+ if (s >= 55)
56
+ return 'AP Score: 3/5 (Qualified)';
57
+ if (s >= 40)
58
+ return 'AP Score: 2/5 (Possibly Qualified)';
59
+ return 'AP Score: 1/5 (No Recommendation)';
60
+ },
61
+ AP_Calculus: (s) => {
62
+ if (s >= 85)
63
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
64
+ if (s >= 70)
65
+ return 'AP Score: 4/5 (Well Qualified)';
66
+ if (s >= 55)
67
+ return 'AP Score: 3/5 (Qualified)';
68
+ if (s >= 40)
69
+ return 'AP Score: 2/5 (Possibly Qualified)';
70
+ return 'AP Score: 1/5 (No Recommendation)';
71
+ },
72
+ SAT_Math: (s) => {
73
+ if (s >= 90)
74
+ return 'SAT Math Score: 750-800';
75
+ if (s >= 80)
76
+ return 'SAT Math Score: 680-750';
77
+ if (s >= 70)
78
+ return 'SAT Math Score: 600-680';
79
+ if (s >= 60)
80
+ return 'SAT Math Score: 520-600';
81
+ if (s >= 50)
82
+ return 'SAT Math Score: 440-520';
83
+ return 'SAT Math Score: Below 440';
84
+ },
85
+ SAT_Reading: (s) => {
86
+ if (s >= 90)
87
+ return 'SAT Reading Score: 35-40';
88
+ if (s >= 80)
89
+ return 'SAT Reading Score: 30-35';
90
+ if (s >= 70)
91
+ return 'SAT Reading Score: 25-30';
92
+ if (s >= 60)
93
+ return 'SAT Reading Score: 20-25';
94
+ return 'SAT Reading Score: Below 20';
95
+ },
96
+ MCAT: (s) => {
97
+ if (s >= 90)
98
+ return 'MCAT Score: 517-528 (97th+ percentile)';
99
+ if (s >= 80)
100
+ return 'MCAT Score: 511-516 (80th-96th percentile)';
101
+ if (s >= 70)
102
+ return 'MCAT Score: 505-510 (60th-79th percentile)';
103
+ if (s >= 60)
104
+ return 'MCAT Score: 499-504 (40th-59th percentile)';
105
+ return 'MCAT Score: Below 499';
106
+ },
107
+ LSAT: (s) => {
108
+ if (s >= 90)
109
+ return 'LSAT Score: 170-180 (97th+ percentile)';
110
+ if (s >= 80)
111
+ return 'LSAT Score: 163-169 (88th-96th percentile)';
112
+ if (s >= 70)
113
+ return 'LSAT Score: 156-162 (70th-87th percentile)';
114
+ if (s >= 60)
115
+ return 'LSAT Score: 149-155 (42nd-69th percentile)';
116
+ return 'LSAT Score: Below 149';
117
+ },
118
+ GRE: (s) => {
119
+ if (s >= 90)
120
+ return 'GRE Score: 165-170 Verbal, 165-170 Quant';
121
+ if (s >= 80)
122
+ return 'GRE Score: 158-164 Verbal, 158-164 Quant';
123
+ if (s >= 70)
124
+ return 'GRE Score: 152-157 Verbal, 152-157 Quant';
125
+ if (s >= 60)
126
+ return 'GRE Score: 145-151 Verbal, 145-151 Quant';
127
+ return 'GRE Score: Below 145';
128
+ },
129
+ };
130
+ function getGradeEstimate(examType, score) {
131
+ const mapper = GRADE_MAPPINGS[examType];
132
+ if (mapper)
133
+ return mapper(score);
134
+ // Generic AP mapping for unmapped types
135
+ if (examType.startsWith('AP_')) {
136
+ if (score >= 85)
137
+ return 'AP Score: 5/5 (Extremely Well Qualified)';
138
+ if (score >= 70)
139
+ return 'AP Score: 4/5 (Well Qualified)';
140
+ if (score >= 55)
141
+ return 'AP Score: 3/5 (Qualified)';
142
+ if (score >= 40)
143
+ return 'AP Score: 2/5 (Possibly Qualified)';
144
+ return 'AP Score: 1/5 (No Recommendation)';
145
+ }
146
+ return `Estimated score: ${Math.round(score)}%`;
147
+ }
148
+ function generateRecommendation(score, weakTopics, coveragePercent, trend, daysRemaining) {
149
+ const parts = [];
150
+ if (score >= 85) {
151
+ parts.push('Excellent preparation! You are performing at a high level.');
152
+ }
153
+ else if (score >= 70) {
154
+ parts.push('Good progress! You have a solid foundation.');
155
+ }
156
+ else if (score >= 55) {
157
+ parts.push('Moderate preparation. More focused study will improve your score.');
158
+ }
159
+ else {
160
+ parts.push('More intensive study is needed. Focus on building fundamentals.');
161
+ }
162
+ if (weakTopics.length > 0) {
163
+ const topWeak = weakTopics.slice(0, 3).map(t => t.topic).join(', ');
164
+ parts.push(`Prioritize these weak areas: ${topWeak}.`);
165
+ }
166
+ if (coveragePercent < 60) {
167
+ parts.push(`You have only covered ${Math.round(coveragePercent)}% of topics — work through more areas.`);
168
+ }
169
+ if (trend === 'improving') {
170
+ parts.push('Your performance is improving — keep up the momentum!');
171
+ }
172
+ else if (trend === 'declining') {
173
+ parts.push('Your recent scores are declining. Consider reviewing earlier material.');
174
+ }
175
+ if (daysRemaining > 14) {
176
+ parts.push('You have time to make significant improvements with consistent daily practice.');
177
+ }
178
+ else if (daysRemaining > 7) {
179
+ parts.push('Focus on weak areas and complete at least one full mock exam.');
180
+ }
181
+ else {
182
+ parts.push('Final stretch: review key concepts, rest well, and trust your preparation.');
183
+ }
184
+ return parts.join(' ');
185
+ }
186
+ export function calculateReadiness(profileId, examType, examDate) {
187
+ const allTopicStats = getTopicStats(profileId);
188
+ const examTopics = EXAM_TOPICS[examType] || [];
189
+ // Get exam weight data from questions
190
+ const topicWeights = {};
191
+ for (const q of QUESTION_BANK) {
192
+ if (q.subject === examType) {
193
+ if (!topicWeights[q.topic] || q.examWeight > topicWeights[q.topic]) {
194
+ topicWeights[q.topic] = q.examWeight;
195
+ }
196
+ }
197
+ }
198
+ // Calculate per-topic accuracy
199
+ const topicScores = [];
200
+ let weightedScore = 0;
201
+ let totalWeight = 0;
202
+ for (const stats of allTopicStats) {
203
+ if (stats.total_attempts === 0)
204
+ continue;
205
+ const accuracy = (stats.correct_attempts / stats.total_attempts) * 100;
206
+ const weight = topicWeights[stats.topic] || 0.5;
207
+ topicScores.push({
208
+ topic: stats.topic,
209
+ accuracy,
210
+ attempts: stats.total_attempts
211
+ });
212
+ weightedScore += accuracy * weight;
213
+ totalWeight += weight;
214
+ }
215
+ const baseScore = totalWeight > 0 ? weightedScore / totalWeight : 0;
216
+ // Coverage bonus
217
+ const practicedTopics = new Set(allTopicStats.filter(s => s.total_attempts > 0).map(s => s.topic));
218
+ const examSpecificTopics = examTopics.length > 0 ? examTopics : [...practicedTopics];
219
+ const coveragePercent = examSpecificTopics.length > 0
220
+ ? (practicedTopics.size / examSpecificTopics.length) * 100
221
+ : 0;
222
+ let coverageBonus = 0;
223
+ if (coveragePercent >= 80)
224
+ coverageBonus = 20;
225
+ else if (coveragePercent >= 60)
226
+ coverageBonus = 10;
227
+ // Trend calculation from recent sessions
228
+ const recentSessions = getRecentSessions(profileId, 20);
229
+ let trend = 'stable';
230
+ let trendModifier = 0;
231
+ if (recentSessions.length >= 6) {
232
+ const recent5 = recentSessions.slice(0, 5);
233
+ const prev5 = recentSessions.slice(5, 10);
234
+ const recentAccuracy = recent5.reduce((sum, s) => sum + (s.questions_answered > 0 ? s.correct / s.questions_answered : 0), 0) / recent5.length;
235
+ const prevAccuracy = prev5.reduce((sum, s) => sum + (s.questions_answered > 0 ? s.correct / s.questions_answered : 0), 0) / prev5.length;
236
+ const diff = recentAccuracy - prevAccuracy;
237
+ if (diff > 0.05) {
238
+ trend = 'improving';
239
+ trendModifier = 5;
240
+ }
241
+ else if (diff < -0.05) {
242
+ trend = 'declining';
243
+ trendModifier = -5;
244
+ }
245
+ }
246
+ // Time modifier
247
+ const now = new Date();
248
+ const examDateObj = new Date(examDate);
249
+ const daysRemaining = Math.max(0, Math.ceil((examDateObj.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)));
250
+ const timeModifier = daysRemaining > 30 ? 3 : daysRemaining > 14 ? 1 : 0;
251
+ // Final score (clamped 0-100)
252
+ const rawScore = baseScore + (coverageBonus * 0.3) + trendModifier + timeModifier;
253
+ const score = Math.max(0, Math.min(100, rawScore));
254
+ // Identify strong and weak topics
255
+ const strongTopics = topicScores
256
+ .filter(t => t.accuracy >= 80)
257
+ .sort((a, b) => b.accuracy - a.accuracy);
258
+ const weakTopics = topicScores
259
+ .filter(t => t.accuracy < 60)
260
+ .sort((a, b) => a.accuracy - b.accuracy);
261
+ const recommendation = generateRecommendation(score, weakTopics, coveragePercent, trend, daysRemaining);
262
+ const estimatedGrade = getGradeEstimate(examType, score);
263
+ return {
264
+ score: Math.round(score),
265
+ strongTopics,
266
+ weakTopics,
267
+ coveragePercent: Math.round(coveragePercent),
268
+ trend,
269
+ recommendation,
270
+ estimatedGrade
271
+ };
272
+ }
@@ -0,0 +1,25 @@
1
+ export interface StudyPlan {
2
+ examType: string;
3
+ examDate: string;
4
+ daysRemaining: number;
5
+ dailySchedule: DayPlan[];
6
+ topicsIdentified: string[];
7
+ weeklyGoals: string[];
8
+ }
9
+ export interface DayPlan {
10
+ date: string;
11
+ dayNumber: number;
12
+ activities: Activity[];
13
+ estimatedMinutes: number;
14
+ isMockExam: boolean;
15
+ }
16
+ export interface Activity {
17
+ topic: string;
18
+ type: 'study' | 'practice' | 'review' | 'mock_exam';
19
+ durationMinutes: number;
20
+ description: string;
21
+ }
22
+ export declare const EXAM_TOPICS: Record<string, string[]>;
23
+ export declare const EXAM_TIME_LIMITS: Record<string, number>;
24
+ export declare const MOCK_EXAM_QUESTIONS: Record<string, number>;
25
+ export declare function generateStudyPlan(examType: string, examDate: string, currentLevel: 'beginner' | 'intermediate' | 'advanced', weakTopics?: string[]): StudyPlan;
@@ -0,0 +1,313 @@
1
+ // Topic lists per exam type
2
+ export const EXAM_TOPICS = {
3
+ AP_Biology: [
4
+ 'Cell Structure',
5
+ 'DNA Replication',
6
+ 'Transcription and Translation',
7
+ 'Cell Division',
8
+ 'Mendelian Genetics',
9
+ 'Evolution and Natural Selection',
10
+ 'Ecology',
11
+ 'Photosynthesis',
12
+ 'Cellular Respiration',
13
+ 'Plant Systems',
14
+ 'Animal Systems',
15
+ 'Genetics',
16
+ ],
17
+ AP_Chemistry: [
18
+ 'Atomic Structure',
19
+ 'Chemical Bonding',
20
+ 'Stoichiometry',
21
+ 'Gas Laws',
22
+ 'Thermodynamics',
23
+ 'Chemical Equilibrium',
24
+ 'Acids and Bases',
25
+ 'Electrochemistry',
26
+ 'Kinetics',
27
+ 'Nuclear Chemistry',
28
+ ],
29
+ AP_US_History: [
30
+ 'Colonial Period',
31
+ 'American Revolution',
32
+ 'Constitutional Convention',
33
+ 'Early Republic',
34
+ 'Antebellum and Civil War',
35
+ 'Reconstruction Era',
36
+ 'Gilded Age',
37
+ 'Progressive Era',
38
+ 'World War I',
39
+ 'World War II',
40
+ 'Cold War',
41
+ 'Civil Rights Movement',
42
+ 'Modern America',
43
+ ],
44
+ AP_World_History: [
45
+ 'Ancient Civilizations',
46
+ 'Classical Period',
47
+ 'Post-Classical Period',
48
+ 'Renaissance and Reformation',
49
+ 'Early Modern Period',
50
+ 'Industrialization',
51
+ 'Imperialism',
52
+ 'World War I',
53
+ 'World War II',
54
+ 'Cold War',
55
+ 'Globalization',
56
+ ],
57
+ AP_Physics: [
58
+ 'Kinematics',
59
+ 'Newton\'s Laws',
60
+ 'Energy and Work',
61
+ 'Momentum',
62
+ 'Circular Motion',
63
+ 'Gravity',
64
+ 'Electricity',
65
+ 'Magnetism',
66
+ 'Waves and Optics',
67
+ 'Thermodynamics',
68
+ ],
69
+ AP_Calculus: [
70
+ 'Limits and Continuity',
71
+ 'Derivatives',
72
+ 'Applications of Derivatives',
73
+ 'Integration',
74
+ 'Applications of Integration',
75
+ 'Differential Equations',
76
+ 'Sequences and Series',
77
+ ],
78
+ AP_Statistics: [
79
+ 'Exploratory Data Analysis',
80
+ 'Sampling and Experiments',
81
+ 'Probability',
82
+ 'Probability Distributions',
83
+ 'Sampling Distributions',
84
+ 'Confidence Intervals',
85
+ 'Hypothesis Testing',
86
+ 'Regression',
87
+ ],
88
+ AP_English: [
89
+ 'Rhetorical Analysis',
90
+ 'Argument and Evidence',
91
+ 'Synthesis Essays',
92
+ 'Literary Analysis',
93
+ 'Close Reading',
94
+ 'Grammar and Style',
95
+ ],
96
+ AP_Economics: [
97
+ 'Supply and Demand',
98
+ 'Elasticity',
99
+ 'Consumer Theory',
100
+ 'Production and Costs',
101
+ 'Market Structures',
102
+ 'GDP and Macroeconomics',
103
+ 'Money and Banking',
104
+ 'Monetary Policy',
105
+ 'Fiscal Policy',
106
+ 'International Trade',
107
+ ],
108
+ SAT_Math: [
109
+ 'Linear Equations',
110
+ 'Linear Inequalities',
111
+ 'Systems of Equations',
112
+ 'Quadratic Equations',
113
+ 'Percentages and Proportions',
114
+ 'Statistics and Probability',
115
+ 'Geometry',
116
+ 'Word Problems',
117
+ 'Functions',
118
+ 'Data Analysis',
119
+ ],
120
+ SAT_Reading: [
121
+ 'Main Idea and Purpose',
122
+ 'Evidence and Support',
123
+ 'Vocabulary in Context',
124
+ 'Inference Questions',
125
+ 'Passage Structure',
126
+ 'Comparing Passages',
127
+ 'Data Interpretation',
128
+ ],
129
+ SAT_Writing: [
130
+ 'Grammar Rules',
131
+ 'Sentence Structure',
132
+ 'Punctuation',
133
+ 'Word Choice',
134
+ 'Organization and Transitions',
135
+ 'Evidence and Clarity',
136
+ ],
137
+ MCAT: [
138
+ 'Biology and Biochemistry',
139
+ 'Chemistry and Physics',
140
+ 'Psychology and Sociology',
141
+ 'Critical Analysis',
142
+ 'Research Methods',
143
+ 'Statistics',
144
+ ],
145
+ LSAT: [
146
+ 'Logical Reasoning',
147
+ 'Analytical Reasoning (Logic Games)',
148
+ 'Reading Comprehension',
149
+ 'Argument Structure',
150
+ 'Formal Logic',
151
+ ],
152
+ GRE: [
153
+ 'Verbal Reasoning',
154
+ 'Quantitative Reasoning',
155
+ 'Analytical Writing',
156
+ 'Algebra',
157
+ 'Geometry',
158
+ 'Data Analysis',
159
+ 'Reading Comprehension',
160
+ ],
161
+ };
162
+ // Exam time limits in minutes
163
+ export const EXAM_TIME_LIMITS = {
164
+ AP_Biology: 170,
165
+ AP_Chemistry: 190,
166
+ AP_US_History: 205,
167
+ AP_World_History: 205,
168
+ AP_Physics: 180,
169
+ AP_Calculus: 195,
170
+ AP_Statistics: 180,
171
+ AP_English: 215,
172
+ AP_Economics: 130,
173
+ SAT_Math: 70,
174
+ SAT_Reading: 65,
175
+ SAT_Writing: 35,
176
+ MCAT: 375,
177
+ LSAT: 175,
178
+ GRE: 225,
179
+ };
180
+ export const MOCK_EXAM_QUESTIONS = {
181
+ AP_Biology: 60,
182
+ AP_Chemistry: 60,
183
+ AP_US_History: 55,
184
+ AP_World_History: 55,
185
+ AP_Physics: 50,
186
+ AP_Calculus: 45,
187
+ AP_Statistics: 40,
188
+ AP_English: 45,
189
+ AP_Economics: 60,
190
+ SAT_Math: 44,
191
+ SAT_Reading: 52,
192
+ SAT_Writing: 44,
193
+ MCAT: 230,
194
+ LSAT: 100,
195
+ GRE: 80,
196
+ };
197
+ export function generateStudyPlan(examType, examDate, currentLevel, weakTopics) {
198
+ const now = new Date();
199
+ const examDateObj = new Date(examDate);
200
+ const msPerDay = 1000 * 60 * 60 * 24;
201
+ const daysRemaining = Math.max(1, Math.ceil((examDateObj.getTime() - now.getTime()) / msPerDay));
202
+ const allTopics = EXAM_TOPICS[examType] || ['General Study'];
203
+ const topicsIdentified = [...allTopics];
204
+ // Level-based settings
205
+ const minutesPerTopic = currentLevel === 'beginner' ? 60 : currentLevel === 'intermediate' ? 45 : 30;
206
+ const topicsPerDay = currentLevel === 'beginner' ? 1 : currentLevel === 'intermediate' ? 2 : 3;
207
+ // Build topic schedule with weak areas weighted 2x
208
+ const weightedTopics = [];
209
+ for (const topic of allTopics) {
210
+ const isWeak = weakTopics && weakTopics.includes(topic);
211
+ weightedTopics.push(topic);
212
+ if (isWeak) {
213
+ weightedTopics.push(topic); // Add again for 2x weight
214
+ }
215
+ }
216
+ // Build daily schedule
217
+ const dailySchedule = [];
218
+ let topicIndex = 0;
219
+ const weeklyGoals = [];
220
+ for (let day = 0; day < Math.min(daysRemaining, 90); day++) {
221
+ const date = new Date(now);
222
+ date.setDate(date.getDate() + day);
223
+ const dateStr = date.toISOString().split('T')[0];
224
+ const dayNumber = day + 1;
225
+ // Mock exams at 7 and 3 days before exam
226
+ const daysBeforeExam = daysRemaining - day;
227
+ const isMockExam = daysBeforeExam === 7 || daysBeforeExam === 3;
228
+ const activities = [];
229
+ let estimatedMinutes = 0;
230
+ if (isMockExam) {
231
+ const timeLimitMins = EXAM_TIME_LIMITS[examType] || 120;
232
+ activities.push({
233
+ topic: 'Full Mock Exam',
234
+ type: 'mock_exam',
235
+ durationMinutes: timeLimitMins + 30,
236
+ description: `Full-length ${examType} practice exam simulating real conditions. Review all answers afterward.`
237
+ });
238
+ estimatedMinutes = timeLimitMins + 30;
239
+ }
240
+ else if (daysBeforeExam === 1) {
241
+ // Day before: light review
242
+ activities.push({
243
+ topic: 'Final Review',
244
+ type: 'review',
245
+ durationMinutes: 30,
246
+ description: 'Light review of key formulas and concepts. Rest and prepare for exam day.'
247
+ });
248
+ estimatedMinutes = 30;
249
+ }
250
+ else {
251
+ // Regular study day
252
+ const dayTopics = weightedTopics.slice(topicIndex % weightedTopics.length, (topicIndex % weightedTopics.length) + topicsPerDay);
253
+ topicIndex += topicsPerDay;
254
+ // Deduplicate topics for this day
255
+ const uniqueDayTopics = [...new Set(dayTopics)];
256
+ for (const topic of uniqueDayTopics) {
257
+ const isWeak = weakTopics && weakTopics.includes(topic);
258
+ activities.push({
259
+ topic,
260
+ type: 'study',
261
+ durationMinutes: minutesPerTopic,
262
+ description: `Study ${topic} — ${isWeak ? 'focus area (weak topic)' : 'core concepts and examples'}`
263
+ });
264
+ estimatedMinutes += minutesPerTopic;
265
+ // Add practice component
266
+ activities.push({
267
+ topic,
268
+ type: 'practice',
269
+ durationMinutes: 15,
270
+ description: `Practice questions for ${topic}`
271
+ });
272
+ estimatedMinutes += 15;
273
+ }
274
+ // Weekly review on Sundays
275
+ if (date.getDay() === 0) {
276
+ activities.push({
277
+ topic: 'Weekly Review',
278
+ type: 'review',
279
+ durationMinutes: 30,
280
+ description: 'Review all topics covered this week. Identify areas needing more attention.'
281
+ });
282
+ estimatedMinutes += 30;
283
+ }
284
+ }
285
+ dailySchedule.push({
286
+ date: dateStr,
287
+ dayNumber,
288
+ activities,
289
+ estimatedMinutes,
290
+ isMockExam
291
+ });
292
+ }
293
+ // Generate weekly goals
294
+ const weeksRemaining = Math.ceil(daysRemaining / 7);
295
+ const topicsPerWeek = Math.ceil(allTopics.length / Math.max(weeksRemaining, 1));
296
+ for (let week = 0; week < Math.min(weeksRemaining, 6); week++) {
297
+ const weekTopics = allTopics.slice(week * topicsPerWeek, (week + 1) * topicsPerWeek);
298
+ if (weekTopics.length > 0) {
299
+ weeklyGoals.push(`Week ${week + 1}: Master ${weekTopics.join(', ')}`);
300
+ }
301
+ }
302
+ if (weeksRemaining > 0) {
303
+ weeklyGoals.push(`Final week: Full mock exams and comprehensive review`);
304
+ }
305
+ return {
306
+ examType,
307
+ examDate,
308
+ daysRemaining,
309
+ dailySchedule,
310
+ topicsIdentified,
311
+ weeklyGoals
312
+ };
313
+ }