UNPKG

adpa-enterprise-framework-automation

Version:

Modular, standards-compliant Node.js/TypeScript automation framework for enterprise requirements, project, and data management. Provides CLI and API for BABOK v3, PMBOK 7th Edition, and DMBOK 2.0 (in progress). Production-ready Express.js API with TypeSpe

394 lines 15.6 kB
import { ReviewerProfileModel } from '../../models/ReviewerProfile.js'; import { logger } from '../../utils/logger.js'; export class ReviewerController { /** * Create a new reviewer profile */ static async createReviewerProfile(req, res, next) { try { const profileData = req.body; // Validate required fields if (!profileData.userId || !profileData.name || !profileData.email || !profileData.roles) { return res.status(400).json({ error: 'Missing required fields: userId, name, email, roles' }); } // Check if profile already exists const existingProfile = await ReviewerProfileModel.findOne({ userId: profileData.userId }); if (existingProfile) { return res.status(409).json({ error: 'Reviewer profile already exists for this user' }); } // Set default values for required nested objects const profileWithDefaults = { ...profileData, availability: profileData.availability || { hoursPerWeek: 20, timeZone: 'UTC', workingHours: { start: '09:00', end: '17:00' }, workingDays: [1, 2, 3, 4, 5], // Monday to Friday unavailableDates: [], maxConcurrentReviews: 3 }, preferences: profileData.preferences || { preferredDocumentTypes: [], preferredProjectTypes: [], notificationPreferences: { email: true, inApp: true, sms: false }, reminderFrequency: 'daily' }, metrics: { totalReviews: 0, completedReviews: 0, averageReviewTime: 0, averageQualityScore: 0, onTimeCompletionRate: 0, last30DaysReviews: 0, last30DaysAvgTime: 0, feedbackQualityScore: 0, thoroughnessScore: 0, lastUpdated: new Date() } }; const profile = new ReviewerProfileModel(profileWithDefaults); const savedProfile = await profile.save(); logger.info(`Reviewer profile created: ${savedProfile.userId}`); res.status(201).json(savedProfile.toJSON()); } catch (error) { logger.error('Error creating reviewer profile:', error); next(error); } } /** * Get reviewer profile by user ID */ static async getReviewerProfile(req, res, next) { try { const { userId } = req.params; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } res.json(profile.toJSON()); } catch (error) { logger.error('Error getting reviewer profile:', error); next(error); } } /** * Update reviewer profile */ static async updateReviewerProfile(req, res, next) { try { const { userId } = req.params; const updates = req.body; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } // Update allowed fields const allowedUpdates = [ 'name', 'email', 'title', 'department', 'organization', 'roles', 'expertise', 'certifications', 'experienceYears', 'availability', 'preferences', 'isActive' ]; allowedUpdates.forEach(field => { if (updates[field] !== undefined) { profile[field] = updates[field]; } }); const updatedProfile = await profile.save(); logger.info(`Reviewer profile updated: ${userId}`); res.json(updatedProfile.toJSON()); } catch (error) { logger.error('Error updating reviewer profile:', error); next(error); } } /** * Delete reviewer profile */ static async deleteReviewerProfile(req, res, next) { try { const { userId } = req.params; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } // Soft delete by setting isActive to false profile.isActive = false; await profile.save(); logger.info(`Reviewer profile deactivated: ${userId}`); res.json({ message: 'Reviewer profile deactivated successfully' }); } catch (error) { logger.error('Error deleting reviewer profile:', error); next(error); } } /** * Search reviewers with filters */ static async searchReviewers(req, res, next) { try { const filter = { isActive: true }; // Apply filters if (req.query.roles) { const roles = Array.isArray(req.query.roles) ? req.query.roles : [req.query.roles]; filter.roles = { $in: roles }; } if (req.query.expertise) { const expertise = Array.isArray(req.query.expertise) ? req.query.expertise : [req.query.expertise]; filter.expertise = { $in: expertise }; } if (req.query.department) { filter.department = req.query.department; } if (req.query.organization) { filter.organization = req.query.organization; } if (req.query.minExperience) { filter.experienceYears = { $gte: parseInt(req.query.minExperience) }; } if (req.query.minQualityScore) { filter['metrics.averageQualityScore'] = { $gte: parseFloat(req.query.minQualityScore) }; } // Sorting const sortField = req.query.sortBy || 'metrics.averageQualityScore'; const sortOrder = req.query.sortOrder === 'asc' ? 1 : -1; // Pagination const limit = req.query.limit ? parseInt(req.query.limit) : 20; const offset = req.query.offset ? parseInt(req.query.offset) : 0; const [reviewers, total] = await Promise.all([ ReviewerProfileModel.find(filter) .sort({ [sortField]: sortOrder }) .limit(limit) .skip(offset) .exec(), ReviewerProfileModel.countDocuments(filter) ]); res.json({ reviewers: reviewers.map(r => r.toJSON()), total, page: Math.floor(offset / limit) + 1, limit, hasMore: offset + reviewers.length < total }); } catch (error) { logger.error('Error searching reviewers:', error); next(error); } } /** * Get available reviewers for a specific role and document type */ static async getAvailableReviewers(req, res, next) { try { const { role, documentType } = req.query; if (!role) { return res.status(400).json({ error: 'Role is required' }); } const filter = { isActive: true, roles: role }; // Filter by document type preference if specified if (documentType) { filter.$or = [ { 'preferences.preferredDocumentTypes': documentType }, { 'preferences.preferredDocumentTypes': { $size: 0 } } // No preferences means available for all ]; } const reviewers = await ReviewerProfileModel.find(filter) .sort({ 'metrics.averageQualityScore': -1, 'metrics.onTimeCompletionRate': -1 }) .limit(20) .exec(); // Filter by actual availability (would check current workload in real implementation) const availableReviewers = reviewers.filter(reviewer => { // Type guard for canTakeReview method return typeof reviewer.canTakeReview === 'function' ? reviewer.canTakeReview() : false; }); res.json({ reviewers: availableReviewers.map(r => ({ ...r.toJSON(), availabilityStatus: r.availabilityStatus, performanceRating: r.performanceRating, currentWorkload: r.currentWorkload })) }); } catch (error) { logger.error('Error getting available reviewers:', error); next(error); } } /** * Update reviewer availability */ static async updateReviewerAvailability(req, res, next) { try { const { userId } = req.params; const availability = req.body; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } profile.availability = availability; const updatedProfile = await profile.save(); logger.info(`Reviewer availability updated: ${userId}`); res.json({ availability: updatedProfile.availability, availabilityStatus: updatedProfile.availabilityStatus }); } catch (error) { logger.error('Error updating reviewer availability:', error); next(error); } } /** * Update reviewer preferences */ static async updateReviewerPreferences(req, res, next) { try { const { userId } = req.params; const preferences = req.body; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } profile.preferences = preferences; const updatedProfile = await profile.save(); logger.info(`Reviewer preferences updated: ${userId}`); res.json({ preferences: updatedProfile.preferences }); } catch (error) { logger.error('Error updating reviewer preferences:', error); next(error); } } /** * Get reviewer performance metrics */ static async getReviewerMetrics(req, res, next) { try { const { userId } = req.params; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } res.json({ metrics: profile.metrics, performanceRating: profile.performanceRating, availabilityStatus: profile.availabilityStatus, currentWorkload: profile.currentWorkload }); } catch (error) { logger.error('Error getting reviewer metrics:', error); next(error); } } /** * Get reviewer leaderboard */ static async getReviewerLeaderboard(req, res, next) { try { const metric = req.query.metric || 'averageQualityScore'; const limit = req.query.limit ? parseInt(req.query.limit) : 10; const validMetrics = [ 'averageQualityScore', 'onTimeCompletionRate', 'completedReviews', 'feedbackQualityScore', 'thoroughnessScore' ]; if (!validMetrics.includes(metric)) { return res.status(400).json({ error: `Invalid metric. Valid options: ${validMetrics.join(', ')}` }); } const reviewers = await ReviewerProfileModel.find({ isActive: true }) .sort({ [`metrics.${metric}`]: -1 }) .limit(limit) .select('userId name title department metrics') .exec(); const leaderboard = reviewers.map((reviewer, index) => ({ rank: index + 1, userId: reviewer.userId, name: reviewer.name, title: reviewer.title, department: reviewer.department, score: reviewer.metrics[metric], performanceRating: reviewer.performanceRating })); res.json({ metric, leaderboard }); } catch (error) { logger.error('Error getting reviewer leaderboard:', error); next(error); } } /** * Get reviewer workload summary */ static async getReviewerWorkload(req, res, next) { try { const { userId } = req.params; if (!userId) { return res.status(400).json({ error: 'User ID is required' }); } const profile = await ReviewerProfileModel.findOne({ userId }); if (!profile) { return res.status(404).json({ error: 'Reviewer profile not found' }); } // In a real implementation, this would query the DocumentReview collection // to get actual current workload const workload = { currentReviews: 0, // Would be calculated from active reviews maxConcurrentReviews: profile.availability.maxConcurrentReviews, hoursPerWeek: profile.availability.hoursPerWeek, utilizationRate: 0, // Would be calculated based on current workload availabilityStatus: profile.availabilityStatus, upcomingDeadlines: [], // Would be populated from active reviews estimatedHoursThisWeek: 0 // Would be calculated from active reviews }; res.json(workload); } catch (error) { logger.error('Error getting reviewer workload:', error); next(error); } } } //# sourceMappingURL=ReviewerController.js.map