/* ============================================================ data.js — Realistic dummy dataset + generators Exposes global `DB` ============================================================ */ (function () { 'use strict'; // ---------- seeded pseudo-random for stable data ---------- let seed = 88123; function rand() { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; } function pick(arr) { return arr[Math.floor(rand() * arr.length)]; } function pickN(arr, n) { const copy = arr.slice(), out = []; for (let i = 0; i < n && copy.length; i++) out.push(copy.splice(Math.floor(rand() * copy.length), 1)[0]); return out; } function int(min, max) { return Math.floor(rand() * (max - min + 1)) + min; } // ---------- reference lists ---------- const departments = ['Engineering', 'Product', 'Design', 'Marketing', 'Sales', 'Finance', 'People Ops', 'Customer Success', 'Data & Analytics', 'Operations']; const businessUnits = ['Core Platform', 'Growth', 'Enterprise', 'Consumer', 'Corporate']; const locations = ['San Francisco, CA', 'New York, NY', 'Austin, TX', 'London, UK', 'Remote — US', 'Remote — EU', 'Berlin, DE', 'Toronto, CA', 'Singapore', 'Seattle, WA']; const empTypes = ['Full-time', 'Part-time', 'Contract', 'Internship']; const grades = ['L2', 'L3', 'L4', 'L5', 'L6', 'L7']; const jobStatuses = ['Open', 'On Hold', 'Closed', 'Draft']; const educationLevels = ["Bachelor's Degree", "Master's Degree", "PhD", "Associate Degree", "High School"]; const stages = ['Applied', 'Screening', 'Assessment', 'Interview', 'Offer', 'Hired', 'Rejected']; const sources = ['LinkedIn', 'Company Site', 'Referral', 'Indeed', 'Job Fair', 'Agency', 'GitHub', 'AngelList']; const companies = ['Stripe', 'Airbnb', 'Datadog', 'Notion', 'Figma', 'Shopify', 'Snowflake', 'Twilio', 'Coinbase', 'Atlassian', 'Asana', 'Ramp', 'Brex', 'Vercel', 'Retool', 'Amplitude', 'Segment', 'MongoDB', 'HashiCorp', 'Cloudflare']; const firstNames = ['Olivia', 'Liam', 'Emma', 'Noah', 'Ava', 'Ethan', 'Sophia', 'Mason', 'Isabella', 'Lucas', 'Mia', 'Aiden', 'Amelia', 'James', 'Harper', 'Benjamin', 'Evelyn', 'Elijah', 'Abigail', 'Daniel', 'Priya', 'Wei', 'Diego', 'Fatima', 'Yuki', 'Omar', 'Ingrid', 'Kwame', 'Sofia', 'Arjun', 'Chloe', 'Marcus', 'Nadia', 'Tariq', 'Lena', 'Hassan', 'Grace', 'Felix', 'Zara', 'Kai']; const lastNames = ['Chen', 'Patel', 'Kim', 'Rodriguez', 'Nguyen', 'Johnson', 'Williams', 'Garcia', 'Müller', 'Rossi', 'Silva', 'Okafor', 'Anderson', 'Ahmed', 'Cohen', 'Novak', 'Yamamoto', 'Brown', 'Dubois', 'Schmidt', 'Ivanov', 'Lopez', 'Martins', 'Andersson', 'Reyes', 'Khan', 'Tremblay', 'Fischer', 'Costa', 'Walsh']; const jobTitlesByDept = { 'Engineering': ['Senior Backend Engineer', 'Staff Frontend Engineer', 'Platform Engineer', 'Engineering Manager', 'Site Reliability Engineer', 'Mobile Engineer (iOS)', 'Full-Stack Engineer'], 'Product': ['Senior Product Manager', 'Group Product Manager', 'Technical Product Manager', 'Product Operations Lead'], 'Design': ['Senior Product Designer', 'UX Researcher', 'Brand Designer', 'Design Systems Lead'], 'Marketing': ['Growth Marketing Manager', 'Content Strategist', 'Demand Generation Lead', 'SEO Specialist'], 'Sales': ['Enterprise Account Executive', 'Sales Development Rep', 'Solutions Engineer', 'Sales Manager'], 'Finance': ['Financial Analyst', 'Controller', 'FP&A Manager', 'Accounting Lead'], 'People Ops': ['HR Business Partner', 'Recruiting Coordinator', 'People Operations Manager', 'Compensation Analyst'], 'Customer Success': ['Customer Success Manager', 'Support Engineer', 'Onboarding Specialist', 'CS Team Lead'], 'Data & Analytics': ['Senior Data Scientist', 'Analytics Engineer', 'Data Platform Engineer', 'ML Engineer'], 'Operations': ['Operations Manager', 'Business Operations Analyst', 'Procurement Lead', 'Program Manager'] }; const skillsPool = ['JavaScript', 'TypeScript', 'React', 'Node.js', 'Python', 'Go', 'AWS', 'Kubernetes', 'GraphQL', 'PostgreSQL', 'System Design', 'Figma', 'Product Strategy', 'SQL', 'Data Modeling', 'Leadership', 'A/B Testing', 'Salesforce', 'Communication', 'Roadmapping']; const benefitsPool = ['Equity', '401(k) match', 'Unlimited PTO', 'Health & Dental', 'Remote stipend', 'Learning budget', 'Parental leave', 'Wellness program']; function fullName() { return pick(firstNames) + ' ' + pick(lastNames); } function initials(name) { return name.split(' ').map(p => p[0]).slice(0, 2).join('').toUpperCase(); } function email(name) { return name.toLowerCase().replace(/[^a-z ]/g, '').replace(/ /g, '.') + '@' + pick(['gmail.com', 'outlook.com', 'proton.me', 'icloud.com']); } function phone() { return '+1 (' + int(200, 989) + ') ' + int(200, 999) + '-' + int(1000, 9999); } function daysAgo(n) { const d = new Date('2026-07-09T09:00:00'); d.setDate(d.getDate() - n); return d; } function fmtDate(d) { return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } function fmtShort(d) { return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } const avatarColors = ['var(--av-1)', 'var(--av-2)', 'var(--av-3)', 'var(--av-4)', 'var(--av-5)', 'var(--av-6)', 'var(--av-7)', 'var(--av-8)']; function avatarColor(name) { let s = 0; for (const c of name) s += c.charCodeAt(0); return avatarColors[s % avatarColors.length]; } // ---------- Recruiters ---------- const recruiters = []; for (let i = 0; i < 15; i++) { const name = fullName(); recruiters.push({ id: 'REC-' + (101 + i), name, initials: initials(name), email: email(name), color: avatarColor(name), hires: int(4, 34), openReqs: int(2, 12), avgTimeToHire: int(18, 46), offerAccept: int(72, 98), rating: (rand() * 1.4 + 3.5).toFixed(1) }); } // ---------- Hiring Managers ---------- const managers = []; for (let i = 0; i < 12; i++) { const name = fullName(); const dept = departments[i % departments.length]; managers.push({ id: 'HM-' + (201 + i), name, initials: initials(name), email: email(name).replace(/@.*/, '@utopiabrands.com'), color: avatarColor(name), department: dept, title: 'Director, ' + dept, openReqs: int(1, 6), teamSize: int(6, 40) }); } // ---------- Jobs ---------- const jobs = []; for (let i = 0; i < 26; i++) { const dept = pick(departments); const title = pick(jobTitlesByDept[dept]); const mgr = pick(managers); const rec = pick(recruiters); const status = i < 16 ? 'Open' : pick(jobStatuses); const created = daysAgo(int(3, 120)); const apps = status === 'Draft' ? 0 : int(12, 220); jobs.push({ id: 'JOB-' + (1001 + i), title, department: dept, businessUnit: pick(businessUnits), grade: pick(grades), manager: mgr.name, managerId: mgr.id, recruiter: rec.name, recruiterId: rec.id, location: pick(locations), type: pick(empTypes), vacancies: int(1, 5), applications: apps, status, created, deadline: daysAgo(-int(5, 60)), salaryMin: int(80, 160) * 1000, salaryMax: 0, experience: int(2, 10) + '+ years', education: pick(educationLevels), skills: pickN(skillsPool, int(4, 7)), benefits: pickN(benefitsPool, int(3, 5)), description: 'We are looking for a ' + title + ' to join our ' + dept + ' team. You will drive high-impact initiatives, collaborate cross-functionally, and help shape the future of our product.', responsibilities: ['Own and deliver key projects end-to-end', 'Collaborate with cross-functional partners', 'Mentor peers and raise the quality bar', 'Contribute to strategy and roadmap planning'], progress: status === 'Closed' ? 100 : int(10, 92) }); jobs[i].salaryMax = jobs[i].salaryMin + int(20, 60) * 1000; } // ---------- Candidates ---------- const openJobs = jobs.filter(j => j.status === 'Open'); const candidates = []; const stageWeights = ['Applied', 'Applied', 'Applied', 'Screening', 'Screening', 'Assessment', 'Interview', 'Interview', 'Offer', 'Hired', 'Rejected', 'Rejected']; for (let i = 0; i < 100; i++) { const name = fullName(); const job = pick(openJobs.length ? openJobs : jobs); const stage = pick(stageWeights); const applied = daysAgo(int(1, 75)); candidates.push({ id: 'CAN-' + (5001 + i), name, initials: initials(name), color: avatarColor(name), email: email(name), phone: phone(), jobId: job.id, jobTitle: job.title, department: job.department, experience: int(1, 14), currentCompany: pick(companies), currentTitle: pick(jobTitlesByDept[job.department] || ['Specialist']), location: pick(locations), stage, status: stage, aiScore: int(52, 98), source: pick(sources), recruiter: job.recruiter, recruiterId: job.recruiterId, applied, education: pick(educationLevels), skills: pickN(skillsPool, int(3, 6)), rating: (rand() * 2 + 3).toFixed(1), salary: int(90, 190) * 1000 }); } // ---------- Interviews ---------- const interviewTypes = ['Phone Screen', 'Technical', 'System Design', 'Onsite Loop', 'Hiring Manager', 'Culture Fit', 'Final Round']; const meetingTypes = ['Video Call', 'On-site', 'Phone']; const interviewStatuses = ['Scheduled', 'Completed', 'Cancelled', 'No Show']; const interviews = []; const interviewPool = candidates.filter(c => ['Screening', 'Assessment', 'Interview', 'Offer'].includes(c.stage)); for (let i = 0; i < 40; i++) { const cand = pick(interviewPool.length ? interviewPool : candidates); const offset = int(-20, 14); const when = daysAgo(-offset); when.setHours(int(9, 17), pick([0, 30]), 0, 0); const status = offset > 0 ? 'Scheduled' : pick(['Completed', 'Completed', 'Completed', 'Cancelled', 'No Show']); interviews.push({ id: 'INT-' + (7001 + i), candidate: cand.name, candidateId: cand.id, candInitials: cand.initials, color: cand.color, jobTitle: cand.jobTitle, type: pick(interviewTypes), meeting: pick(meetingTypes), when, status, duration: pick([30, 45, 60, 90]), interviewers: pickN([...recruiters, ...managers].map(r => r.name), int(1, 3)), feedback: status === 'Completed' ? pick(['Strong Hire', 'Hire', 'Lean Hire', 'No Hire']) : null, score: status === 'Completed' ? int(2, 5) : null }); } interviews.sort((a, b) => a.when - b.when); // ---------- Assessments ---------- const assessTypes = ['Coding Challenge', 'Take-home Project', 'Cognitive Test', 'Personality Assessment', 'SQL Test', 'Case Study']; const assessments = []; for (let i = 0; i < 24; i++) { const cand = pick(candidates); const status = pick(['Completed', 'Completed', 'In Progress', 'Pending', 'Expired']); assessments.push({ id: 'ASM-' + (8001 + i), candidate: cand.name, candidateId: cand.id, initials: cand.initials, color: cand.color, jobTitle: cand.jobTitle, type: pick(assessTypes), status, score: status === 'Completed' ? int(45, 99) : null, assigned: daysAgo(int(1, 30)), due: daysAgo(-int(1, 10)), duration: pick(['45 min', '60 min', '90 min', '2 hours', '3 days']) }); } // ---------- Offers ---------- const offerStatuses = ['Sent', 'Accepted', 'Negotiating', 'Declined', 'Draft', 'Expired']; const offers = []; const offerPool = candidates.filter(c => ['Offer', 'Hired'].includes(c.stage)); for (let i = 0; i < 22; i++) { const cand = offerPool[i % offerPool.length] || pick(candidates); const status = cand.stage === 'Hired' ? 'Accepted' : pick(offerStatuses); const base = int(95, 210) * 1000; offers.push({ id: 'OFR-' + (9001 + i), candidate: cand.name, candidateId: cand.id, initials: cand.initials, color: cand.color, jobTitle: cand.jobTitle, department: cand.department, status, base, equity: int(0, 40) + 'k RSU', bonus: int(5, 25) + '%', sent: daysAgo(int(1, 25)), expires: daysAgo(-int(3, 14)), recruiter: cand.recruiter }); } // ---------- Activity feed ---------- const activityTemplates = [ { icon: 'user-plus', color: 'i-green', text: (n, j) => `${n} applied for ${j}` }, { icon: 'calendar', color: 'i-blue', text: (n, j) => `Interview scheduled with ${n} for ${j}` }, { icon: 'check', color: 'i-teal', text: (n, j) => `${n} moved to Offer stage` }, { icon: 'file', color: 'i-purple', text: (n, j) => `Offer sent to ${n} for ${j}` }, { icon: 'star', color: 'i-amber', text: (n, j) => `${n} completed an assessment` }, { icon: 'x', color: 'i-red', text: (n, j) => `${n} was rejected for ${j}` } ]; const activity = []; for (let i = 0; i < 18; i++) { const t = pick(activityTemplates); const cand = pick(candidates); activity.push({ icon: t.icon, color: t.color, html: t.text(cand.name, cand.jobTitle), time: int(1, 300), candidateId: cand.id }); } activity.sort((a, b) => a.time - b.time); function relTime(mins) { if (mins < 60) return mins + 'm ago'; if (mins < 1440) return Math.floor(mins / 60) + 'h ago'; return Math.floor(mins / 1440) + 'd ago'; } // ---------- Notifications ---------- const notifications = [ { icon: 'user-plus', color: 'i-green', title: 'New application', text: candidates[0].name + ' applied for ' + candidates[0].jobTitle, time: '8m ago', unread: true }, { icon: 'calendar', color: 'i-blue', title: 'Interview reminder', text: 'Technical interview at 2:00 PM today', time: '25m ago', unread: true }, { icon: 'check', color: 'i-teal', title: 'Offer accepted', text: offers[0].candidate + ' accepted the offer 🎉', time: '1h ago', unread: true }, { icon: 'message', color: 'i-purple', title: 'New message', text: 'Hiring manager left feedback on a candidate', time: '2h ago', unread: true }, { icon: 'star', color: 'i-amber', title: 'Assessment completed', text: assessments[0].candidate + ' scored 87% on Coding Challenge', time: '4h ago', unread: false }, { icon: 'file', color: 'i-indigo', title: 'Approval needed', text: 'Job requisition JOB-1004 awaits your approval', time: '6h ago', unread: false }, { icon: 'x', color: 'i-red', title: 'Interview cancelled', text: 'Culture fit interview was cancelled by candidate', time: '1d ago', unread: false } ]; // ---------- Messages ---------- const messages = [ { name: managers[0].name, initials: managers[0].initials, color: managers[0].color, text: 'Can we move the onsite to Thursday?', time: '10m', unread: true }, { name: recruiters[2].name, initials: recruiters[2].initials, color: recruiters[2].color, text: 'Shortlist is ready for your review.', time: '40m', unread: true }, { name: candidates[5].name, initials: candidates[5].initials, color: candidates[5].color, text: 'Thank you! Looking forward to it.', time: '2h', unread: false }, { name: managers[3].name, initials: managers[3].initials, color: managers[3].color, text: 'Approved the offer — go ahead.', time: '5h', unread: false } ]; // ---------- Aggregate analytics ---------- const analytics = { hiringTrend: { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], hires: [12, 18, 15, 22, 19, 26, 24], applications: [180, 240, 210, 320, 290, 380, 410] }, pipeline: stages.filter(s => s !== 'Rejected').map(s => ({ stage: s, count: candidates.filter(c => c.stage === s).length })), sources: sources.map(s => ({ source: s, count: candidates.filter(c => c.source === s).length })), departments: departments.map(d => ({ dept: d, open: jobs.filter(j => j.department === d && j.status === 'Open').length, apps: candidates.filter(c => c.department === d).length })), offerAcceptance: { accepted: offers.filter(o => o.status === 'Accepted').length, declined: offers.filter(o => o.status === 'Declined').length, pending: offers.filter(o => ['Sent', 'Negotiating'].includes(o.status)).length }, timeToHire: [32, 28, 35, 30, 26, 29, 27], timeToFill: [45, 42, 48, 40, 38, 41, 39] }; // ---------- KPIs ---------- const today = new Date('2026-07-09'); const kpis = { openJobs: jobs.filter(j => j.status === 'Open').length, closedJobs: jobs.filter(j => j.status === 'Closed').length, totalCandidates: candidates.length, interviewsToday: interviews.filter(iv => iv.when.toDateString() === today.toDateString()).length || 5, offersSent: offers.filter(o => o.status !== 'Draft').length, offersAccepted: offers.filter(o => o.status === 'Accepted').length, timeToHire: 27, timeToFill: 39, costPerHire: 4280, hires: candidates.filter(c => c.stage === 'Hired').length }; // ---------- Roles / users for settings ---------- const roles = [ { name: 'Administrator', users: 3, desc: 'Full access to all settings and data', perms: 'All permissions' }, { name: 'Recruiter', users: 15, desc: 'Manage jobs, candidates, and interviews', perms: '18 permissions' }, { name: 'Hiring Manager', users: 12, desc: 'Review candidates and provide feedback', perms: '9 permissions' }, { name: 'Interviewer', users: 24, desc: 'View assigned interviews and submit scorecards', perms: '5 permissions' }, { name: 'Coordinator', users: 6, desc: 'Schedule interviews and manage logistics', perms: '11 permissions' } ]; const users = recruiters.slice(0, 8).map((r, i) => ({ name: r.name, initials: r.initials, color: r.color, email: r.email, role: pick(['Administrator', 'Recruiter', 'Hiring Manager', 'Coordinator']), status: i % 5 === 0 ? 'Invited' : 'Active', lastActive: pick(['2m ago', '1h ago', 'Yesterday', '3d ago']) })); // ============================================================ // ENTERPRISE DATA (phase 2) // ============================================================ // ---------- Candidate sources w/ metadata ---------- const sourceMeta = { 'Microsoft Outlook': { icon: 'mail', color: '#0078d4', channel: 'Email' }, 'Career Portal': { icon: 'briefcase', color: 'var(--c1)', channel: 'Portal' }, 'Manual CV Upload': { icon: 'upload', color: 'var(--c2)', channel: 'Upload' }, 'LinkedIn': { icon: 'linkedin', color: '#0a66c2', channel: 'Job Board' }, 'Indeed': { icon: 'briefcase', color: '#2164f3', channel: 'Job Board' }, 'Rozee': { icon: 'briefcase', color: '#e5322d', channel: 'Job Board' }, 'Mustakbil': { icon: 'briefcase', color: '#00a651', channel: 'Job Board' }, 'Employee Referral': { icon: 'users', color: 'var(--c3)', channel: 'Referral' }, 'Recruitment Agency': { icon: 'briefcase', color: 'var(--c6)', channel: 'Agency' }, 'Campus Hiring': { icon: 'award', color: 'var(--c5)', channel: 'Campus' }, 'Walk-in': { icon: 'user-plus', color: 'var(--c6)', channel: 'Direct' } }; const inboxSources = Object.keys(sourceMeta); const processingStatuses = ['Unread', 'Imported', 'Processed', 'Rejected', 'Duplicate']; const resumeStatuses = ['Parsed', 'Pending', 'Parsing', 'Failed']; // ---------- Recruitment Inbox items ---------- const inbox = []; for (let i = 0; i < 48; i++) { const name = fullName(); const job = pick(openJobs.length ? openJobs : jobs); const src = pick(inboxSources); const proc = i < 14 ? 'Unread' : pick(processingStatuses); const rs = proc === 'Unread' ? pick(['Pending', 'Parsing']) : pick(['Parsed', 'Parsed', 'Parsed', 'Failed']); const received = daysAgo(int(0, 10)); received.setHours(int(7, 20), int(0, 59), 0, 0); inbox.push({ id: 'APP-' + (30001 + i), name, initials: initials(name), color: avatarColor(name), position: job.title, jobId: job.id, source: src, sourceMeta: sourceMeta[src], resumeStatus: rs, atsScore: int(48, 97), recruiter: pick(recruiters).name, received, processing: proc, unread: proc === 'Unread', email: email(name), phone: phone(), experience: int(1, 12), attachment: name.split(' ')[0] + '_Resume.pdf', duplicate: proc === 'Duplicate' }); } inbox.sort((a, b) => b.received - a.received); // ---------- Outlook Emails ---------- const emailSubjects = [ 'Application for {job}', 'Interested in the {job} role', 'CV — {job} position', 'Referral: strong candidate for {job}', 'Following up on my application', 'Resume attached — {job}' ]; const emails = []; for (let i = 0; i < 20; i++) { const name = fullName(); const job = pick(openJobs.length ? openJobs : jobs); const when = daysAgo(int(0, 6)); when.setHours(int(7, 20), int(0, 59), 0, 0); emails.push({ id: 'EML-' + (40001 + i), from: name, fromEmail: email(name), initials: initials(name), color: avatarColor(name), subject: pick(emailSubjects).replace('{job}', job.title), jobTitle: job.title, jobId: job.id, preview: `Dear Hiring Team, I am writing to express my strong interest in the ${job.title} position. With ${int(3, 12)} years of experience, I believe I would be a great fit…`, body: `Dear Hiring Team,\n\nI am writing to express my strong interest in the ${job.title} position at Utopia Brands. With over ${int(3, 12)} years of experience in ${job.department.toLowerCase()}, I have developed a strong foundation in delivering high-impact work.\n\nIn my current role at ${pick(companies)}, I have led cross-functional initiatives and consistently exceeded targets. I am confident my background aligns well with your requirements.\n\nPlease find my resume attached for your review. I would welcome the opportunity to discuss how I can contribute to your team.\n\nBest regards,\n${name}\n${phone()}`, attachment: name.split(' ')[0] + '_CV.pdf', attachmentSize: int(120, 480) + ' KB', when, unread: i < 8, imported: i >= 14, atsScore: int(50, 95) }); } emails.sort((a, b) => b.when - a.when); // ---------- Publishing platforms ---------- const publishPlatforms = [ { name: 'Career Portal', icon: 'briefcase', color: 'var(--c1)', connected: true, cost: 'Free' }, { name: 'LinkedIn', icon: 'linkedin', color: '#0a66c2', connected: true, cost: '$$$' }, { name: 'Indeed', icon: 'briefcase', color: '#2164f3', connected: true, cost: '$$' }, { name: 'Rozee', icon: 'briefcase', color: '#e5322d', connected: true, cost: '$' }, { name: 'Mustakbil', icon: 'briefcase', color: '#00a651', connected: true, cost: '$' }, { name: 'Google Jobs', icon: 'search', color: '#ea4335', connected: true, cost: 'Free' }, { name: 'Facebook Jobs', icon: 'users', color: '#1877f2', connected: false, cost: '$$' }, { name: 'Glassdoor', icon: 'briefcase', color: '#0caa41', connected: false, cost: '$$' } ]; // per-job publishing records const publishings = []; openJobs.slice(0, 14).forEach(job => { const plats = pickN(publishPlatforms.filter(p => p.connected).map(p => p.name), int(2, 5)); plats.forEach(pl => { const views = int(120, 4200), clicks = Math.round(views * (rand() * 0.3 + 0.08)), apps = Math.round(clicks * (rand() * 0.25 + 0.05)); publishings.push({ jobId: job.id, jobTitle: job.title, platform: pl, status: pick(['Live', 'Live', 'Live', 'Pending', 'Paused']), views, clicks, apps, published: daysAgo(int(2, 40)) }); }); }); // ---------- Extend recruiters with enterprise metrics ---------- recruiters.forEach((r, idx) => { r.openPositions = int(3, 12); r.closedPositions = int(4, 28); r.avgTimeToFill = r.avgTimeToHire + int(6, 16); r.workload = int(28, 96); r.interviewsToday = int(0, 5); r.offersPending = int(0, 6); r.jobsAwaitingApproval = int(0, 4); r.jobsOverdue = int(0, 3); r.efficiency = int(62, 97); r.conversionRate = int(8, 26); r.interviewCompletion = int(72, 99); r.avgResponseTime = (rand() * 6 + 1).toFixed(1); r.sla = pick(['On Track', 'On Track', 'On Track', 'At Risk', 'Breached']); r.tat = int(78, 99); r.monthlyTrend = Array.from({ length: 7 }, () => int(2, 9)); r.heatmap = Array.from({ length: 7 }, () => Array.from({ length: 5 }, () => int(0, 5))); // 7 days x 5 weeks-ish r.department = departments[idx % departments.length]; }); // ---------- Extend candidates with ATS match detail ---------- candidates.forEach(c => { const job = getJobLocal(c.jobId); const required = job ? job.skills : pickN(skillsPool, 5); const matched = required.filter(s => c.skills.includes(s)); const missing = required.filter(s => !c.skills.includes(s)); c.matchedSkills = matched.length ? matched : c.skills.slice(0, 2); c.missingSkills = missing; c.recommendation = c.aiScore >= 82 ? 'Strong Match' : c.aiScore >= 65 ? 'Potential Match' : 'Weak Match'; c.subScores = { skills: Math.min(99, c.aiScore + int(-8, 8)), experience: Math.min(99, c.aiScore + int(-12, 10)), education: Math.min(99, c.aiScore + int(-10, 12)), keywords: Math.min(99, c.aiScore + int(-14, 8)), location: pick([100, 100, 80, 60]), salary: pick([100, 90, 75, 55]) }; c.noticePeriod = pick(['Immediate', '2 weeks', '1 month', '2 months', '3 months']); c.availability = pick(['Immediate', 'Immediate', '2 weeks', '1 month', 'Passive']); c.certifications = pickN(['AWS Certified', 'PMP', 'Scrum Master', 'Google Analytics', 'CFA', 'None'], int(0, 2)); c.favorite = false; c.interviewStatus = pick(['Not Scheduled', 'Scheduled', 'Completed', 'Not Scheduled']); }); function getJobLocal(id) { return jobs.find(j => j.id === id); } // ---------- Recruitment Tasks ---------- const taskTypes = ['Screen candidate', 'Schedule interview', 'Send offer', 'Review CV', 'Follow up', 'Submit scorecard', 'Approve requisition', 'Reference check']; const tasks = []; for (let i = 0; i < 16; i++) { const cand = pick(candidates); tasks.push({ id: 'TSK-' + (50001 + i), title: pick(taskTypes) + ' — ' + cand.name, candidateId: cand.id, priority: pick(['High', 'Medium', 'Medium', 'Low']), due: daysAgo(-int(-3, 7)), assignee: pick(recruiters).name, done: i % 6 === 0, type: pick(['Interview', 'Review', 'Offer', 'Admin']) }); } // ---------- Saved searches / filters ---------- const savedSearches = [ { name: 'Senior Engineers · SF', count: 24, filters: 'Engineering · L5+ · San Francisco' }, { name: 'Strong Match Designers', count: 11, filters: 'Design · ATS ≥ 85' }, { name: 'Immediate Availability', count: 38, filters: 'Availability: Immediate' }, { name: 'Referrals to Review', count: 9, filters: 'Source: Referral · Unread' } ]; // ---------- Interview evaluation templates ---------- const evalTemplates = [ { name: 'Engineering — Technical', dept: 'Engineering', criteria: ['Technical Skills', 'Problem Solving', 'System Design', 'Communication', 'Culture Fit'] }, { name: 'Product — PM Loop', dept: 'Product', criteria: ['Product Sense', 'Analytical', 'Execution', 'Leadership', 'Communication'] }, { name: 'Design — Portfolio', dept: 'Design', criteria: ['Craft', 'Process', 'Collaboration', 'Communication', 'Culture Fit'] }, { name: 'General — Behavioral', dept: 'All', criteria: ['Communication', 'Technical', 'Problem Solving', 'Leadership', 'Culture Fit'] } ]; // ---------- RBAC modules & permission types ---------- const rbacModules = ['Dashboard', 'Recruitment Inbox', 'Jobs', 'Candidates', 'Pipeline', 'Interviews', 'Assessments', 'Offers', 'Reports', 'Analytics', 'Job Board', 'Settings', 'RBAC & Users']; const permTypes = ['View', 'Create', 'Edit', 'Delete', 'Approve', 'Export', 'Manage', 'Configure']; const rbacRoles = [ { name: 'System Administrator', users: 2, color: 'var(--av-8)', desc: 'Unrestricted access to all modules and configuration', level: 'Administrator' }, { name: 'HR Administrator', users: 4, color: 'var(--av-7)', desc: 'Manage recruitment operations and team settings', level: 'Manage' }, { name: 'Recruiter', users: 15, color: 'var(--av-1)', desc: 'Full candidate and job lifecycle management', level: 'Edit' }, { name: 'Hiring Manager', users: 12, color: 'var(--av-3)', desc: 'Review candidates, interview, approve offers', level: 'Approve' }, { name: 'Department Head', users: 8, color: 'var(--av-2)', desc: 'Oversight of departmental hiring and approvals', level: 'Approve' }, { name: 'CEO', users: 1, color: 'var(--av-5)', desc: 'Executive dashboards and reporting', level: 'View' }, { name: 'Interviewer', users: 24, color: 'var(--av-6)', desc: 'Access assigned interviews and submit scorecards', level: 'View' }, { name: 'Candidate', users: 0, color: 'var(--av-4)', desc: 'Self-service portal access (external)', level: 'View' } ]; // default permission matrix per role (module -> [bool x permTypes]) function buildMatrix(level) { const idx = { 'View': 1, 'Edit': 3, 'Approve': 5, 'Manage': 7, 'Administrator': 8 }[level] || 1; const m = {}; rbacModules.forEach(mod => { m[mod] = permTypes.map((p, i) => i < idx); }); return m; } rbacRoles.forEach(r => r.matrix = buildMatrix(r.level)); // ---------- Future AI modules ---------- const aiModules = [ { name: 'Resume Ranking', desc: 'Automatically rank applicants for any job by fit', icon: 'award', cls: 'i-indigo', status: 'Beta' }, { name: 'Candidate Matching', desc: 'Match candidates to the best-fit open roles', icon: 'target', cls: 'i-teal', status: 'Beta' }, { name: 'Resume Summary', desc: 'One-click AI summary of any resume', icon: 'file', cls: 'i-blue', status: 'Beta' }, { name: 'Candidate Comparison', desc: 'Side-by-side AI comparison of shortlisted candidates', icon: 'users', cls: 'i-purple', status: 'Coming Soon' }, { name: 'JD Generator', desc: 'Generate polished job descriptions from a brief', icon: 'edit', cls: 'i-green', status: 'Beta' }, { name: 'Email Generator', desc: 'Draft candidate emails in your brand voice', icon: 'mail', cls: 'i-amber', status: 'Beta' }, { name: 'Offer Letter Generator', desc: 'Auto-generate compliant offer letters', icon: 'file', cls: 'i-red', status: 'Coming Soon' }, { name: 'Interview Question Generator', desc: 'Role-specific question banks on demand', icon: 'message', cls: 'i-indigo', status: 'Beta' }, { name: 'Candidate Recommendation', desc: 'Proactive "you should talk to…" suggestions', icon: 'star', cls: 'i-teal', status: 'Coming Soon' }, { name: 'Recruitment Analytics', desc: 'Natural-language analytics across your funnel', icon: 'trending-up', cls: 'i-blue', status: 'Beta' }, { name: 'Hiring Forecast', desc: 'Predict time-to-hire and pipeline health', icon: 'trending-up', cls: 'i-purple', status: 'Coming Soon' }, { name: 'Skill Gap Analysis', desc: 'Identify missing skills across your pipeline', icon: 'target', cls: 'i-green', status: 'Beta' }, { name: 'Recruiter Copilot', desc: 'An assistant embedded in every workflow', icon: 'message', cls: 'i-amber', status: 'Beta' }, { name: 'Hiring Insights', desc: 'Weekly AI-generated hiring insights digest', icon: 'info', cls: 'i-red', status: 'Coming Soon' }, { name: 'Natural Language Search', desc: 'Search candidates in plain English', icon: 'search', cls: 'i-indigo', status: 'Beta' } ]; // ---------- AI assistant suggested prompts ---------- const aiPrompts = [ { icon: 'award', text: 'Rank candidates', prompt: 'Rank the top candidates for the Senior Backend Engineer role.' }, { icon: 'users', text: 'Compare candidates', prompt: 'Compare the top 3 candidates in the Interview stage.' }, { icon: 'edit', text: 'Generate job description', prompt: 'Write a job description for a Staff Frontend Engineer.' }, { icon: 'message', text: 'Interview questions', prompt: 'Generate technical interview questions for a Data Scientist.' }, { icon: 'file', text: 'Summarize resume', prompt: "Summarize the resume of the last applicant." }, { icon: 'mail', text: 'Generate email', prompt: 'Draft an interview invitation email for a candidate.' }, { icon: 'file', text: 'Write offer letter', prompt: 'Write an offer letter for a Product Manager at $160k.' }, { icon: 'star', text: 'Suggest candidate', prompt: 'Suggest the best candidate for the open Design role.' }, { icon: 'target', text: 'Skill gap analysis', prompt: 'What skills are missing from my Engineering pipeline?' }, { icon: 'trending-up', text: 'Hiring recommendation', prompt: 'Give me a hiring recommendation for this week.' }, { icon: 'briefcase', text: 'Pipeline analysis', prompt: 'Analyze the health of my current pipeline.' }, { icon: 'award', text: 'Recruiter productivity', prompt: 'How productive is my recruiting team this month?' } ]; // ---------- ATS matching helper ---------- function atsRecommendationClass(rec) { return rec === 'Strong Match' ? 'b-green' : rec === 'Potential Match' ? 'b-amber' : 'b-red'; } // runtime state buckets const recentlyViewed = []; const favorites = { candidates: [], jobs: [] }; // ---------- Expose ---------- window.DB = { departments, businessUnits, locations, empTypes, grades, jobStatuses, educationLevels, stages, sources, skillsPool, benefitsPool, recruiters, managers, jobs, candidates, interviews, assessments, offers, activity, notifications, messages, analytics, kpis, roles, users, interviewTypes, meetingTypes, companies, // enterprise sourceMeta, inboxSources, processingStatuses, resumeStatuses, inbox, emails, publishPlatforms, publishings, tasks, savedSearches, evalTemplates, rbacModules, permTypes, rbacRoles, aiModules, aiPrompts, recentlyViewed, favorites, // helpers fmtDate, fmtShort, relTime, initials, avatarColor, int, pick, atsRecommendationClass, money: n => '$' + n.toLocaleString('en-US'), moneyK: n => '$' + Math.round(n / 1000) + 'k', getJob: id => jobs.find(j => j.id === id), getCandidate: id => candidates.find(c => c.id === id), getManager: id => managers.find(m => m.id === id), getRecruiter: id => recruiters.find(r => r.id === id), getRecruiterByName: n => recruiters.find(r => r.name === n) }; })();