Files
homeschool/generate-spaced-repetition-data.js
T

223 lines
6.1 KiB
JavaScript

/**
* Generate sample spaced repetition data for the Spelling Practice App
* This script will populate the spaced repetition system with realistic learning data
*/
const fs = require('fs');
const path = require('path');
// Define paths
const DATA_DIR = path.join(__dirname, 'data');
const files = {
users: path.join(DATA_DIR, 'users.json'),
wordlists: path.join(DATA_DIR, 'wordlists.json'),
spacedRepetition: path.join(DATA_DIR, 'spacedRepetition.json')
};
// Helper functions
function readJsonSafe(filePath, defaultValue = {}) {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
}
return defaultValue;
} catch (error) {
console.error(`Error reading ${filePath}:`, error);
return defaultValue;
}
}
function writeJsonSafe(filePath, data) {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
return true;
} catch (error) {
console.error(`Error writing to ${filePath}:`, error);
return false;
}
}
// Random helpers
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomFloat(min, max, decimals = 2) {
const rand = Math.random() * (max - min) + min;
return parseFloat(rand.toFixed(decimals));
}
// Date helpers
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function subtractDays(date, days) {
return addDays(date, -days);
}
function formatDate(date) {
return date.toISOString().split('T')[0];
}
// Main function to generate spaced repetition data
async function generateSpacedRepetitionData() {
console.log('Generating sample spaced repetition data...');
// Load users and wordlists
const users = readJsonSafe(files.users, []);
const wordlists = readJsonSafe(files.wordlists, {});
if (users.length === 0) {
console.error('No users found. Please ensure users exist before running this script.');
return;
}
// Create spaced repetition data structure
const srData = readJsonSafe(files.spacedRepetition, {});
// Generate data for each user
const today = new Date();
for (const user of users) {
const username = user.username;
console.log(`Generating spaced repetition data for user: ${username}`);
// Get user's words
let userWords = [];
// Handle different wordlist structures
if (Array.isArray(wordlists[username])) {
userWords = wordlists[username];
} else if (wordlists[username] && wordlists[username].weeks && Array.isArray(wordlists[username].weeks)) {
// Extract all words from all weeks
wordlists[username].weeks.forEach(week => {
if (week.words && Array.isArray(week.words)) {
userWords = [...userWords, ...week.words];
}
});
}
if (userWords.length === 0) {
console.log(`No words found for user ${username}. Skipping...`);
continue;
}
// Initialize user's spaced repetition data if not exists
if (!srData[username]) {
srData[username] = {
settings: {
dailyLimit: 20,
reviewThreshold: 3,
newWordsPerDay: 5
},
stats: {
totalReviews: 0,
correctReviews: 0,
totalWords: 0,
masteredWords: 0
},
words: {}
};
}
// Add words with different mastery levels
let totalReviews = 0;
let correctReviews = 0;
let masteredWords = 0;
for (const word of userWords) {
// Determine mastery level
// 0: New - 20%
// 1-2: Learning - 30%
// 3-4: Reviewing - 30%
// 5+: Mastered - 20%
const rand = Math.random();
let repetitions = 0;
let interval = 0;
let easeFactor = 2.5;
if (rand < 0.2) {
// New word
repetitions = 0;
interval = 0;
easeFactor = randomFloat(2.3, 2.7, 1);
} else if (rand < 0.5) {
// Learning
repetitions = randomInt(1, 2);
interval = repetitions === 1 ? 1 : 3;
easeFactor = randomFloat(2.0, 2.5, 1);
totalReviews += repetitions;
correctReviews += repetitions - 1;
} else if (rand < 0.8) {
// Reviewing
repetitions = randomInt(3, 4);
interval = repetitions === 3 ? 7 : 15;
easeFactor = randomFloat(2.2, 2.8, 1);
totalReviews += repetitions;
correctReviews += repetitions - 1;
} else {
// Mastered
repetitions = randomInt(5, 8);
interval = Math.pow(2, repetitions - 4) * 10; // Exponential increase
easeFactor = randomFloat(2.6, 3.0, 1);
totalReviews += repetitions;
correctReviews += repetitions - 1;
masteredWords++;
}
// Calculate last and next review dates
let nextReview = null;
let lastReview = null;
if (repetitions > 0) {
// If the word has been reviewed before, set lastReview
lastReview = formatDate(subtractDays(today, randomInt(1, interval)));
// Next review might be due
const dueChance = 0.3; // 30% chance word is due
if (Math.random() < dueChance) {
// Word is due today or in the past
nextReview = formatDate(subtractDays(today, randomInt(0, 3)));
} else {
// Word is due in the future
nextReview = formatDate(addDays(today, randomInt(1, interval)));
}
} else {
// New word, not reviewed yet
nextReview = formatDate(today);
}
// Add word to user's SR data
srData[username].words[word] = {
easeFactor,
interval,
repetitions,
lastReview,
nextReview
};
}
// Update user stats
srData[username].stats = {
totalReviews,
correctReviews,
totalWords: userWords.length,
masteredWords
};
}
// Save spaced repetition data
console.log('Saving spaced repetition data...');
writeJsonSafe(files.spacedRepetition, srData);
console.log('Spaced repetition data generation complete!');
}
// Execute the script
generateSpacedRepetitionData();