memory-lucia 2.0.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.
- package/LICENSE +21 -0
- package/NPM-PUBLISH-GUIDE.md +169 -0
- package/PUBLISH-STEPS.md +102 -0
- package/README.md +64 -0
- package/SKILL.md +120 -0
- package/api/index.js +255 -0
- package/database/init.js +137 -0
- package/modules/decision.js +267 -0
- package/modules/evolution.js +286 -0
- package/modules/learning.js +267 -0
- package/modules/priority.js +174 -0
- package/modules/version.js +286 -0
- package/package.json +35 -0
- package/publish-with-otp.bat +13 -0
- package/publish.bat +17 -0
- package/publish.sh +20 -0
- package/scripts/init-memory.js +30 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evolution Module
|
|
3
|
+
* Track skill proficiency and growth
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class EvolutionModule {
|
|
7
|
+
constructor(db) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.levelNames = {
|
|
10
|
+
1: 'Novice',
|
|
11
|
+
2: 'Beginner',
|
|
12
|
+
3: 'Intermediate',
|
|
13
|
+
4: 'Advanced',
|
|
14
|
+
5: 'Expert',
|
|
15
|
+
6: 'Master'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
this.levelThresholds = {
|
|
19
|
+
1: 0,
|
|
20
|
+
2: 100,
|
|
21
|
+
3: 300,
|
|
22
|
+
4: 600,
|
|
23
|
+
5: 1000,
|
|
24
|
+
6: 1500
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Record skill usage
|
|
30
|
+
*/
|
|
31
|
+
async recordUsage(skillName, skillCategory, result = 'success', metrics = {}) {
|
|
32
|
+
// Check if skill exists
|
|
33
|
+
let skill = await this.db.get(
|
|
34
|
+
'SELECT * FROM memory_evolution WHERE skill_name = ?',
|
|
35
|
+
[skillName]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!skill) {
|
|
39
|
+
// Create new skill record
|
|
40
|
+
const result = await this.db.run(
|
|
41
|
+
`INSERT INTO memory_evolution (skill_name, skill_category, first_used_at)
|
|
42
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
|
43
|
+
[skillName, skillCategory]
|
|
44
|
+
);
|
|
45
|
+
skill = { id: result.id, proficiency_level: 1, experience_points: 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Calculate XP gain
|
|
49
|
+
const xpGain = this.calculateXP(result, metrics);
|
|
50
|
+
const newXP = skill.experience_points + xpGain;
|
|
51
|
+
const newLevel = this.calculateLevel(newXP);
|
|
52
|
+
const oldLevel = skill.proficiency_level;
|
|
53
|
+
|
|
54
|
+
// Update skill record
|
|
55
|
+
await this.db.run(
|
|
56
|
+
`UPDATE memory_evolution
|
|
57
|
+
SET usage_count = usage_count + 1,
|
|
58
|
+
${result === 'success' ? 'success_count = success_count + 1' : 'failure_count = failure_count + 1'},
|
|
59
|
+
experience_points = ?,
|
|
60
|
+
proficiency_level = ?,
|
|
61
|
+
last_used_at = CURRENT_TIMESTAMP,
|
|
62
|
+
performance_metrics = ?
|
|
63
|
+
WHERE id = ?`,
|
|
64
|
+
[newXP, newLevel, JSON.stringify(metrics), skill.id]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Record level up if applicable
|
|
68
|
+
if (newLevel > oldLevel) {
|
|
69
|
+
await this.db.run(
|
|
70
|
+
`INSERT INTO memory_evolution_history (skill_id, old_level, new_level, reason)
|
|
71
|
+
VALUES (?, ?, ?, ?)`,
|
|
72
|
+
[skill.id, oldLevel, newLevel, `XP reached ${newXP}`]
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
skillId: skill.id,
|
|
79
|
+
xpGained: xpGain,
|
|
80
|
+
totalXP: newXP,
|
|
81
|
+
level: newLevel,
|
|
82
|
+
leveledUp: newLevel > oldLevel
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Calculate XP gain
|
|
88
|
+
*/
|
|
89
|
+
calculateXP(result, metrics) {
|
|
90
|
+
let baseXP = 10;
|
|
91
|
+
|
|
92
|
+
if (result === 'success') {
|
|
93
|
+
baseXP += 5;
|
|
94
|
+
} else if (result === 'failure') {
|
|
95
|
+
baseXP += 2; // Learning from failure
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Bonus for complexity
|
|
99
|
+
if (metrics.complexity) {
|
|
100
|
+
baseXP += metrics.complexity * 2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Bonus for time efficiency
|
|
104
|
+
if (metrics.timeEfficiency) {
|
|
105
|
+
baseXP += Math.floor(metrics.timeEfficiency / 10);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return baseXP;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculate level from XP
|
|
113
|
+
*/
|
|
114
|
+
calculateLevel(xp) {
|
|
115
|
+
for (let level = 6; level >= 1; level--) {
|
|
116
|
+
if (xp >= this.levelThresholds[level]) {
|
|
117
|
+
return level;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get skill details
|
|
125
|
+
*/
|
|
126
|
+
async getSkill(skillName) {
|
|
127
|
+
const sql = `SELECT * FROM v_skill_summary WHERE skill_name = ?`;
|
|
128
|
+
return await this.db.get(sql, [skillName]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get all skills
|
|
133
|
+
*/
|
|
134
|
+
async getAllSkills(category = null, limit = 50) {
|
|
135
|
+
let sql = `SELECT * FROM v_skill_summary`;
|
|
136
|
+
const params = [];
|
|
137
|
+
|
|
138
|
+
if (category) {
|
|
139
|
+
sql += ` WHERE skill_category = ?`;
|
|
140
|
+
params.push(category);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
sql += ` ORDER BY proficiency_level DESC, experience_points DESC LIMIT ?`;
|
|
144
|
+
params.push(limit);
|
|
145
|
+
|
|
146
|
+
return await this.db.all(sql, params);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get skills by category
|
|
151
|
+
*/
|
|
152
|
+
async getByCategory(category) {
|
|
153
|
+
const sql = `
|
|
154
|
+
SELECT * FROM memory_evolution
|
|
155
|
+
WHERE skill_category = ?
|
|
156
|
+
ORDER BY proficiency_level DESC, experience_points DESC
|
|
157
|
+
`;
|
|
158
|
+
return await this.db.all(sql, [category]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get skill history
|
|
163
|
+
*/
|
|
164
|
+
async getSkillHistory(skillId) {
|
|
165
|
+
const sql = `
|
|
166
|
+
SELECT * FROM memory_evolution_history
|
|
167
|
+
WHERE skill_id = ?
|
|
168
|
+
ORDER BY created_at DESC
|
|
169
|
+
`;
|
|
170
|
+
return await this.db.all(sql, [skillId]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get top skills
|
|
175
|
+
*/
|
|
176
|
+
async getTopSkills(limit = 10) {
|
|
177
|
+
const sql = `
|
|
178
|
+
SELECT * FROM v_skill_summary
|
|
179
|
+
ORDER BY proficiency_level DESC, experience_points DESC
|
|
180
|
+
LIMIT ?
|
|
181
|
+
`;
|
|
182
|
+
return await this.db.all(sql, [limit]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get skills needing improvement
|
|
187
|
+
*/
|
|
188
|
+
async getNeedsImprovement(threshold = 50) {
|
|
189
|
+
const sql = `
|
|
190
|
+
SELECT * FROM v_skill_summary
|
|
191
|
+
WHERE success_rate < ?
|
|
192
|
+
ORDER BY success_rate ASC
|
|
193
|
+
`;
|
|
194
|
+
return await this.db.all(sql, [threshold]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get category statistics
|
|
199
|
+
*/
|
|
200
|
+
async getCategoryStats() {
|
|
201
|
+
const sql = `
|
|
202
|
+
SELECT
|
|
203
|
+
skill_category,
|
|
204
|
+
COUNT(*) as skill_count,
|
|
205
|
+
AVG(proficiency_level) as avg_level,
|
|
206
|
+
SUM(experience_points) as total_xp,
|
|
207
|
+
AVG(CAST(success_count AS REAL) / NULLIF(usage_count, 0) * 100) as avg_success_rate
|
|
208
|
+
FROM memory_evolution
|
|
209
|
+
GROUP BY skill_category
|
|
210
|
+
`;
|
|
211
|
+
return await this.db.all(sql);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generate growth report
|
|
216
|
+
*/
|
|
217
|
+
async generateGrowthReport(days = 30) {
|
|
218
|
+
const sql = `
|
|
219
|
+
SELECT
|
|
220
|
+
skill_name,
|
|
221
|
+
proficiency_level,
|
|
222
|
+
experience_points,
|
|
223
|
+
usage_count,
|
|
224
|
+
success_count,
|
|
225
|
+
failure_count,
|
|
226
|
+
last_used_at
|
|
227
|
+
FROM memory_evolution
|
|
228
|
+
WHERE last_used_at >= datetime('now', '-${days} days')
|
|
229
|
+
ORDER BY experience_points DESC
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
const recentSkills = await this.db.all(sql);
|
|
233
|
+
const categories = await this.getCategoryStats();
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
period: `${days} days`,
|
|
237
|
+
activeSkills: recentSkills.length,
|
|
238
|
+
totalXP: recentSkills.reduce((sum, s) => sum + s.experience_points, 0),
|
|
239
|
+
avgLevel: recentSkills.length > 0
|
|
240
|
+
? recentSkills.reduce((sum, s) => sum + s.proficiency_level, 0) / recentSkills.length
|
|
241
|
+
: 0,
|
|
242
|
+
topSkills: recentSkills.slice(0, 5),
|
|
243
|
+
categoryBreakdown: categories
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get next milestone for skill
|
|
249
|
+
*/
|
|
250
|
+
getNextMilestone(skill) {
|
|
251
|
+
const currentLevel = skill.proficiency_level;
|
|
252
|
+
const currentXP = skill.experience_points;
|
|
253
|
+
|
|
254
|
+
if (currentLevel >= 6) {
|
|
255
|
+
return { reachedMax: true };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const nextLevel = currentLevel + 1;
|
|
259
|
+
const xpNeeded = this.levelThresholds[nextLevel] - currentXP;
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
currentLevel,
|
|
263
|
+
nextLevel,
|
|
264
|
+
currentXP,
|
|
265
|
+
xpNeeded,
|
|
266
|
+
levelName: this.levelNames[nextLevel]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Suggest skills to practice
|
|
272
|
+
*/
|
|
273
|
+
async suggestPractice() {
|
|
274
|
+
// Get skills that haven't been used recently
|
|
275
|
+
const sql = `
|
|
276
|
+
SELECT * FROM memory_evolution
|
|
277
|
+
WHERE last_used_at IS NULL
|
|
278
|
+
OR last_used_at < datetime('now', '-7 days')
|
|
279
|
+
ORDER BY proficiency_level ASC
|
|
280
|
+
LIMIT 5
|
|
281
|
+
`;
|
|
282
|
+
return await this.db.all(sql);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = EvolutionModule;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Module
|
|
3
|
+
* Track learning intents, progress, and milestones
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class LearningModule {
|
|
7
|
+
constructor(db) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.learningPatterns = [
|
|
10
|
+
{ pattern: /learn|study|understand|master|grasp/i, category: 'skill' },
|
|
11
|
+
{ pattern: /read|book|article|paper|research/i, category: 'knowledge' },
|
|
12
|
+
{ pattern: /practice|exercise|train|drill/i, category: 'practice' },
|
|
13
|
+
{ pattern: /course|tutorial|lesson|lecture/i, category: 'course' },
|
|
14
|
+
{ pattern: /language|english|chinese|spanish|japanese/i, category: 'language' }
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detect learning intent in message
|
|
20
|
+
*/
|
|
21
|
+
async detectIntent(message) {
|
|
22
|
+
const content = message.toLowerCase();
|
|
23
|
+
let detectedCategory = null;
|
|
24
|
+
let confidence = 0;
|
|
25
|
+
|
|
26
|
+
for (const { pattern, category } of this.learningPatterns) {
|
|
27
|
+
if (pattern.test(content)) {
|
|
28
|
+
detectedCategory = category;
|
|
29
|
+
confidence += 0.2;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract topic using simple heuristics
|
|
34
|
+
const topic = this.extractTopic(message);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
isLearning: confidence > 0,
|
|
38
|
+
category: detectedCategory || 'general',
|
|
39
|
+
confidence: Math.min(confidence, 1),
|
|
40
|
+
topic,
|
|
41
|
+
suggestedResources: this.suggestResources(topic, detectedCategory)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract learning topic from message
|
|
47
|
+
*/
|
|
48
|
+
extractTopic(message) {
|
|
49
|
+
// Simple extraction - can be enhanced with NLP
|
|
50
|
+
const patterns = [
|
|
51
|
+
/learn(?:ing)?\s+(?:about\s+)?(.+?)(?:\.|,|;|$)/i,
|
|
52
|
+
/study(?:ing)?\s+(?:about\s+)?(.+?)(?:\.|,|;|$)/i,
|
|
53
|
+
/how\s+to\s+(.+?)(?:\.|,|;|$)/i,
|
|
54
|
+
/master(?:ing)?\s+(.+?)(?:\.|,|;|$)/i
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const pattern of patterns) {
|
|
58
|
+
const match = message.match(pattern);
|
|
59
|
+
if (match) {
|
|
60
|
+
return match[1].trim();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return 'general learning';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create new learning record
|
|
69
|
+
*/
|
|
70
|
+
async createLearning(messageId, conversationId, intent, notes = '') {
|
|
71
|
+
const sql = `
|
|
72
|
+
INSERT INTO memory_learning
|
|
73
|
+
(message_id, conversation_id, learning_topic, topic_category, progress_status, notes)
|
|
74
|
+
VALUES (?, ?, ?, ?, 'started', ?)
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const result = await this.db.run(sql, [
|
|
78
|
+
messageId,
|
|
79
|
+
conversationId,
|
|
80
|
+
intent.topic,
|
|
81
|
+
intent.category,
|
|
82
|
+
notes
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
learningId: result.id,
|
|
88
|
+
topic: intent.topic,
|
|
89
|
+
category: intent.category
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Update learning progress
|
|
95
|
+
*/
|
|
96
|
+
async updateProgress(learningId, progressData) {
|
|
97
|
+
const updates = [];
|
|
98
|
+
const values = [];
|
|
99
|
+
|
|
100
|
+
if (progressData.percentage !== undefined) {
|
|
101
|
+
updates.push('progress_percentage = ?');
|
|
102
|
+
values.push(progressData.percentage);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (progressData.status) {
|
|
106
|
+
updates.push('progress_status = ?');
|
|
107
|
+
values.push(progressData.status);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (progressData.notes) {
|
|
111
|
+
updates.push('notes = ?');
|
|
112
|
+
values.push(progressData.notes);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (progressData.completionDate) {
|
|
116
|
+
updates.push('actual_completion_date = ?');
|
|
117
|
+
values.push(progressData.completionDate);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (updates.length === 0) return { success: false, error: 'No updates provided' };
|
|
121
|
+
|
|
122
|
+
const sql = `UPDATE memory_learning SET ${updates.join(', ')} WHERE id = ?`;
|
|
123
|
+
values.push(learningId);
|
|
124
|
+
|
|
125
|
+
await this.db.run(sql, values);
|
|
126
|
+
return { success: true, learningId };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Add milestone to learning
|
|
131
|
+
*/
|
|
132
|
+
async addMilestone(learningId, milestone) {
|
|
133
|
+
const sql = `
|
|
134
|
+
INSERT INTO memory_learning_milestones
|
|
135
|
+
(learning_id, milestone_name, description, target_date)
|
|
136
|
+
VALUES (?, ?, ?, ?)
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
const result = await this.db.run(sql, [
|
|
140
|
+
learningId,
|
|
141
|
+
milestone.name,
|
|
142
|
+
milestone.description,
|
|
143
|
+
milestone.targetDate
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
// Update milestone count
|
|
147
|
+
await this.db.run(
|
|
148
|
+
`UPDATE memory_learning SET milestone_count = milestone_count + 1 WHERE id = ?`,
|
|
149
|
+
[learningId]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return { success: true, milestoneId: result.id };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Complete milestone
|
|
157
|
+
*/
|
|
158
|
+
async completeMilestone(milestoneId) {
|
|
159
|
+
const sql = `
|
|
160
|
+
UPDATE memory_learning_milestones
|
|
161
|
+
SET is_completed = 1, completed_date = CURRENT_TIMESTAMP
|
|
162
|
+
WHERE id = ?
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
await this.db.run(sql, [milestoneId]);
|
|
166
|
+
|
|
167
|
+
// Get learning ID and update completed count
|
|
168
|
+
const milestone = await this.db.get(
|
|
169
|
+
'SELECT learning_id FROM memory_learning_milestones WHERE id = ?',
|
|
170
|
+
[milestoneId]
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (milestone) {
|
|
174
|
+
await this.db.run(
|
|
175
|
+
`UPDATE memory_learning
|
|
176
|
+
SET completed_milestones = completed_milestones + 1
|
|
177
|
+
WHERE id = ?`,
|
|
178
|
+
[milestone.learning_id]
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { success: true };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get active learning items
|
|
187
|
+
*/
|
|
188
|
+
async getActiveLearning(limit = 20) {
|
|
189
|
+
const sql = `SELECT * FROM v_active_learning LIMIT ?`;
|
|
190
|
+
return await this.db.all(sql, [limit]);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get learning by topic
|
|
195
|
+
*/
|
|
196
|
+
async getByTopic(topic, limit = 10) {
|
|
197
|
+
const sql = `
|
|
198
|
+
SELECT * FROM memory_learning
|
|
199
|
+
WHERE learning_topic LIKE ?
|
|
200
|
+
ORDER BY created_at DESC
|
|
201
|
+
LIMIT ?
|
|
202
|
+
`;
|
|
203
|
+
return await this.db.all(sql, [`%${topic}%`, limit]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get learning statistics
|
|
208
|
+
*/
|
|
209
|
+
async getStats() {
|
|
210
|
+
const sql = `
|
|
211
|
+
SELECT
|
|
212
|
+
progress_status,
|
|
213
|
+
COUNT(*) as count,
|
|
214
|
+
AVG(progress_percentage) as avg_progress
|
|
215
|
+
FROM memory_learning
|
|
216
|
+
GROUP BY progress_status
|
|
217
|
+
`;
|
|
218
|
+
return await this.db.all(sql);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generate weekly report
|
|
223
|
+
*/
|
|
224
|
+
async generateWeeklyReport() {
|
|
225
|
+
const sql = `SELECT * FROM v_weekly_learning_report LIMIT 4`;
|
|
226
|
+
const weeklyData = await this.db.all(sql);
|
|
227
|
+
|
|
228
|
+
const activeLearning = await this.getActiveLearning(10);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
weeklyTrend: weeklyData,
|
|
232
|
+
activeTopics: activeLearning,
|
|
233
|
+
summary: {
|
|
234
|
+
totalActive: activeLearning.length,
|
|
235
|
+
avgProgress: activeLearning.reduce((sum, item) => sum + item.progress_percentage, 0) / activeLearning.length || 0
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Suggest resources based on topic
|
|
242
|
+
*/
|
|
243
|
+
suggestResources(topic, category) {
|
|
244
|
+
const resources = {
|
|
245
|
+
skill: [
|
|
246
|
+
{ type: 'practice', name: 'Hands-on exercises' },
|
|
247
|
+
{ type: 'project', name: 'Build a small project' }
|
|
248
|
+
],
|
|
249
|
+
knowledge: [
|
|
250
|
+
{ type: 'reading', name: 'Related articles' },
|
|
251
|
+
{ type: 'video', name: 'Tutorial videos' }
|
|
252
|
+
],
|
|
253
|
+
language: [
|
|
254
|
+
{ type: 'app', name: 'Language learning app' },
|
|
255
|
+
{ type: 'practice', name: 'Conversation practice' }
|
|
256
|
+
],
|
|
257
|
+
course: [
|
|
258
|
+
{ type: 'online', name: 'Online course platform' },
|
|
259
|
+
{ type: 'documentation', name: 'Official documentation' }
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
return resources[category] || resources.knowledge;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = LearningModule;
|