const User = require('../models/User'); const SkillEvent = require('../models/SkillEvent'); /** * Returns a human-readable relative time string for a given ISO date string. * e.g. "2 weeks ago", "1 month ago" */ function relativeTime(isoString) { const now = Date.now(); const then = new Date(isoString).getTime(); const diffMs = now - then; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays < 1) return 'today'; if (diffDays === 1) return '1 day ago'; if (diffDays < 7) return `${diffDays} days ago`; if (diffDays < 14) return '1 week ago'; if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`; if (diffDays < 60) return '1 month ago'; return `${Math.floor(diffDays / 30)} months ago`; } const userProfileManager = { /** * Looks up a user by ID. If not found, creates a new user with that ID and returns it. * @param {string} id * @returns {object} user record */ getOrCreateUser(id) { if (!id) throw new Error('id is required'); const existing = User.findById(id); if (existing) return existing; return User.create({ id }); }, /** * Updates allowed profile fields for a user. * Allowed fields: name, os_type, vocabulary_level, comfort_level, accessibility_needs, onboarded * @param {string} id * @param {object} fields * @returns {object} updated user record */ updateProfile(id, fields) { const allowed = ['name', 'os_type', 'vocabulary_level', 'comfort_level', 'accessibility_needs', 'onboarded']; const filtered = {}; for (const key of allowed) { if (fields[key] !== undefined) { filtered[key] = fields[key]; } } return User.update(id, filtered); }, /** * Returns a formatted string for injecting into Claude's system prompt, * including user profile data and skill history. * @param {string} id * @returns {string} formatted profile string */ getProfileForPrompt(id) { const user = User.findById(id); if (!user) return 'User: unknown (no profile found)'; const skillEvents = SkillEvent.findByUserId(id); // Build name/age line const namePart = user.name || 'Unknown'; const nameLine = `User: ${namePart}`; // Device line const deviceLine = `Device: ${user.os_type || 'unknown'}`; // Vocabulary level const vocabLine = `Vocabulary level: ${user.vocabulary_level || 'basic'}`; // Comfort level const comfortLine = `Comfort level: ${user.comfort_level !== null && user.comfort_level !== undefined ? user.comfort_level : 1}/5`; // Skills completed — deduplicate by skill_name, keep most recent const skillMap = {}; for (const event of skillEvents) { if (event.status === 'completed') { if (!skillMap[event.skill_name] || event.practiced_at > skillMap[event.skill_name].practiced_at) { skillMap[event.skill_name] = event; } } } const skillList = Object.values(skillMap); let skillsLine; if (skillList.length === 0) { skillsLine = 'Skills completed: none yet'; } else { const formatted = skillList.map(e => `${e.skill_name} (${relativeTime(e.practiced_at)})`).join(', '); skillsLine = `Skills completed: [${formatted}]`; } // Accessibility needs let accessibilityNeeds = []; try { accessibilityNeeds = JSON.parse(user.accessibility_needs || '[]'); } catch (_) { accessibilityNeeds = []; } const accessibilityLine = `Accessibility: ${accessibilityNeeds.length > 0 ? accessibilityNeeds.join(', ') : 'none specified'}`; return [nameLine, deviceLine, vocabLine, comfortLine, skillsLine, accessibilityLine].join('\n'); }, }; module.exports = userProfileManager;