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
435 lines • 16.2 kB
JavaScript
// Feedback Controller
// filepath: src/api/controllers/FeedbackController.ts
import { DocumentFeedback } from '../../models/DocumentFeedback.js';
import { Project } from '../../models/Project.js';
export class FeedbackController {
/**
* Submit new feedback for a document
*/
static async submitFeedback(req, res, next) {
try {
const { projectId, documentType, documentPath, feedbackType, rating, title, description, suggestedImprovement, priority, tags, category, submittedBy, submittedByName } = req.body;
// Validate project exists
const project = await Project.findById(projectId);
if (!project) {
res.status(404).json({
success: false,
error: 'Project not found'
});
return;
}
// Create feedback
const feedback = new DocumentFeedback({
projectId,
documentType,
documentPath,
feedbackType,
rating,
title,
description,
suggestedImprovement,
priority: priority || 'medium',
tags: tags || [],
category,
submittedBy,
submittedByName,
status: 'open'
});
await feedback.save();
res.status(201).json({
success: true,
data: feedback,
message: 'Feedback submitted successfully'
});
}
catch (error) {
console.error('Error submitting feedback:', error);
res.status(500).json({
success: false,
error: 'Failed to submit feedback',
details: error.message
});
}
}
/**
* Get feedback for a specific project
*/
static async getProjectFeedback(req, res, next) {
try {
const { projectId } = req.params;
const { status, feedbackType, priority, page = 1, limit = 20, sortBy = 'submittedAt', sortOrder = 'desc' } = req.query;
// Build filter
const filter = { projectId };
if (status)
filter.status = status;
if (feedbackType)
filter.feedbackType = feedbackType;
if (priority)
filter.priority = priority;
// Build sort
const sort = {};
sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
// Execute query with pagination
const skip = (Number(page) - 1) * Number(limit);
const [feedback, total] = await Promise.all([
DocumentFeedback.find(filter)
.sort(sort)
.skip(skip)
.limit(Number(limit))
.lean(),
DocumentFeedback.countDocuments(filter)
]);
res.json({
success: true,
data: {
feedback,
pagination: {
page: Number(page),
limit: Number(limit),
total,
pages: Math.ceil(total / Number(limit))
}
}
});
}
catch (error) {
console.error('Error getting project feedback:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve feedback',
details: error.message
});
}
}
/**
* Get feedback for a specific document
*/
static async getDocumentFeedback(req, res, next) {
try {
const { projectId, documentType } = req.params;
const feedback = await DocumentFeedback.find({
projectId,
documentType
}).sort({ submittedAt: -1 });
// Calculate summary statistics
const stats = {
total: feedback.length,
averageRating: feedback.length > 0
? feedback.reduce((sum, f) => sum + f.rating, 0) / feedback.length
: 0,
byStatus: feedback.reduce((acc, f) => {
acc[f.status] = (acc[f.status] || 0) + 1;
return acc;
}, {}),
byType: feedback.reduce((acc, f) => {
acc[f.feedbackType] = (acc[f.feedbackType] || 0) + 1;
return acc;
}, {}),
byPriority: feedback.reduce((acc, f) => {
acc[f.priority] = (acc[f.priority] || 0) + 1;
return acc;
}, {})
};
res.json({
success: true,
data: {
feedback,
stats
}
});
}
catch (error) {
console.error('Error getting document feedback:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve document feedback',
details: error.message
});
}
}
/**
* Update feedback status
*/
static async updateFeedbackStatus(req, res, next) {
try {
const { feedbackId } = req.params;
const { status, reviewedBy, notes } = req.body;
const feedback = await DocumentFeedback.findById(feedbackId);
if (!feedback) {
res.status(404).json({
success: false,
error: 'Feedback not found'
});
return;
}
feedback.status = status;
if (reviewedBy) {
feedback.reviewedBy = reviewedBy;
}
await feedback.save();
res.json({
success: true,
data: feedback,
message: 'Feedback status updated successfully'
});
}
catch (error) {
console.error('Error updating feedback status:', error);
res.status(500).json({
success: false,
error: 'Failed to update feedback status',
details: error.message
});
}
}
/**
* Get feedback analytics for a project
*/
static async getFeedbackAnalytics(req, res, next) {
try {
const { projectId } = req.params;
const { days = 30 } = req.query;
// Get project feedback stats
const projectStats = await DocumentFeedback.getProjectFeedbackStats(projectId);
// Get feedback trends
const trends = await DocumentFeedback.getFeedbackTrends(Number(days));
// Get top issues by document type
const topIssues = await DocumentFeedback.aggregate([
{ $match: { projectId, rating: { $lte: 2 } } },
{
$group: {
_id: '$documentType',
count: { $sum: 1 },
averageRating: { $avg: '$rating' },
issues: { $push: { title: '$title', rating: '$rating' } }
}
},
{ $sort: { count: -1 } },
{ $limit: 10 }
]);
// Get improvement suggestions
const suggestions = await DocumentFeedback.find({
projectId,
suggestedImprovement: { $exists: true, $ne: '' },
status: { $in: ['open', 'in-review'] }
})
.select('documentType title suggestedImprovement priority rating')
.sort({ priority: 1, rating: 1 })
.limit(20);
// Calculate quality improvement metrics
const qualityMetrics = await DocumentFeedback.aggregate([
{
$match: {
projectId,
'qualityMetrics.improvementMeasured': true
}
},
{
$group: {
_id: null,
totalImproved: { $sum: 1 },
averageImprovement: {
$avg: {
$subtract: ['$qualityMetrics.afterScore', '$qualityMetrics.beforeScore']
}
}
}
}
]);
res.json({
success: true,
data: {
projectStats: projectStats[0] || {},
trends,
topIssues,
suggestions,
qualityMetrics: qualityMetrics[0] || { totalImproved: 0, averageImprovement: 0 }
}
});
}
catch (error) {
console.error('Error getting feedback analytics:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve feedback analytics',
details: error.message
});
}
}
/**
* Get feedback summary for dashboard
*/
static async getFeedbackSummary(req, res, next) {
try {
const { userId } = req.query;
// Get overall feedback stats
const overallStats = await DocumentFeedback.aggregate([
{
$group: {
_id: null,
totalFeedback: { $sum: 1 },
averageRating: { $avg: '$rating' },
openFeedback: {
$sum: { $cond: [{ $eq: ['$status', 'open'] }, 1, 0] }
},
criticalFeedback: {
$sum: { $cond: [{ $eq: ['$priority', 'critical'] }, 1, 0] }
}
}
}
]);
// Get recent feedback
const recentFeedback = await DocumentFeedback.find()
.sort({ submittedAt: -1 })
.limit(10)
.select('title documentType rating status submittedAt submittedByName');
// Get user's feedback if userId provided
let userFeedback = null;
if (userId) {
userFeedback = await DocumentFeedback.find({ submittedBy: userId })
.sort({ submittedAt: -1 })
.limit(5)
.select('title documentType rating status submittedAt');
}
res.json({
success: true,
data: {
overallStats: overallStats[0] || {
totalFeedback: 0,
averageRating: 0,
openFeedback: 0,
criticalFeedback: 0
},
recentFeedback,
userFeedback
}
});
}
catch (error) {
console.error('Error getting feedback summary:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve feedback summary',
details: error.message
});
}
}
/**
* Search feedback across projects
*/
static async searchFeedback(req, res, next) {
try {
const { query, projectId, documentType, feedbackType, status, priority, page = 1, limit = 20 } = req.query;
// Build search filter
const filter = {};
if (projectId)
filter.projectId = projectId;
if (documentType)
filter.documentType = documentType;
if (feedbackType)
filter.feedbackType = feedbackType;
if (status)
filter.status = status;
if (priority)
filter.priority = priority;
// Add text search if query provided
if (query) {
filter.$text = { $search: query };
}
// Execute search with pagination
const skip = (Number(page) - 1) * Number(limit);
const [feedback, total] = await Promise.all([
DocumentFeedback.find(filter)
.sort(query ? { score: { $meta: 'textScore' } } : { submittedAt: -1 })
.skip(skip)
.limit(Number(limit))
.populate('relatedFeedback', 'title documentType rating')
.lean(),
DocumentFeedback.countDocuments(filter)
]);
res.json({
success: true,
data: {
feedback,
pagination: {
page: Number(page),
limit: Number(limit),
total,
pages: Math.ceil(total / Number(limit))
}
}
});
}
catch (error) {
console.error('Error searching feedback:', error);
res.status(500).json({
success: false,
error: 'Failed to search feedback',
details: error.message
});
}
}
/**
* Get feedback insights for AI prompt improvement
*/
static async getFeedbackInsights(req, res, next) {
try {
const { documentType, category } = req.query;
// Build filter for insights
const filter = {
rating: { $lte: 3 }, // Focus on lower-rated feedback
status: { $in: ['open', 'in-review'] }
};
if (documentType)
filter.documentType = documentType;
if (category)
filter.category = category;
// Get common issues and suggestions
const insights = await DocumentFeedback.aggregate([
{ $match: filter },
{
$group: {
_id: {
documentType: '$documentType',
feedbackType: '$feedbackType'
},
count: { $sum: 1 },
averageRating: { $avg: '$rating' },
commonIssues: { $push: '$title' },
suggestions: { $push: '$suggestedImprovement' },
affectedPrompts: { $push: '$aiPromptImpact.affectedPrompts' }
}
},
{ $sort: { count: -1 } }
]);
// Get prompt improvement suggestions
const promptSuggestions = await DocumentFeedback.find({
...filter,
'aiPromptImpact.suggestedPromptChanges': { $exists: true, $ne: [] }
})
.select('documentType aiPromptImpact.suggestedPromptChanges rating')
.sort({ rating: 1 });
res.json({
success: true,
data: {
insights,
promptSuggestions,
summary: {
totalLowRatedFeedback: insights.reduce((sum, item) => sum + item.count, 0),
mostProblematicDocuments: insights.slice(0, 5),
promptImprovementOpportunities: promptSuggestions.length
}
}
});
}
catch (error) {
console.error('Error getting feedback insights:', error);
res.status(500).json({
success: false,
error: 'Failed to retrieve feedback insights',
details: error.message
});
}
}
}
//# sourceMappingURL=FeedbackController.js.map