/** * ModerationQueue Model * Human oversight queue for AI actions */ const { ObjectId } = require('mongodb'); const { getCollection } = require('../utils/db.util'); class ModerationQueue { /** * Add item to moderation queue */ static async create(data) { const collection = await getCollection('moderation_queue'); const item = { item_type: data.item_type, // blog_post/media_inquiry/case_study/resource item_id: new ObjectId(data.item_id), quadrant: data.quadrant, // STR/OPS/TAC/SYS/STO ai_action: { type: data.ai_action.type, // suggestion/triage/analysis confidence: data.ai_action.confidence, // 0-1 reasoning: data.ai_action.reasoning, claude_version: data.ai_action.claude_version || 'claude-sonnet-4-5' }, human_required_reason: data.human_required_reason, priority: data.priority || 'medium', // high/medium/low assigned_to: data.assigned_to, status: 'pending', // pending/reviewed/approved/rejected created_at: new Date(), reviewed_at: null, review_decision: { action: null, // approve/reject/modify/escalate notes: null, reviewer: null } }; const result = await collection.insertOne(item); return { ...item, _id: result.insertedId }; } /** * Find item by ID */ static async findById(id) { const collection = await getCollection('moderation_queue'); return await collection.findOne({ _id: new ObjectId(id) }); } /** * Find pending items */ static async findPending(options = {}) { const collection = await getCollection('moderation_queue'); const { limit = 20, skip = 0, priority } = options; const filter = { status: 'pending' }; if (priority) filter.priority = priority; return await collection .find(filter) .sort({ priority: -1, // high first created_at: 1 // oldest first }) .skip(skip) .limit(limit) .toArray(); } /** * Find by item type */ static async findByType(itemType, options = {}) { const collection = await getCollection('moderation_queue'); const { limit = 20, skip = 0 } = options; return await collection .find({ item_type: itemType, status: 'pending' }) .sort({ created_at: -1 }) .skip(skip) .limit(limit) .toArray(); } /** * Find by quadrant */ static async findByQuadrant(quadrant, options = {}) { const collection = await getCollection('moderation_queue'); const { limit = 20, skip = 0 } = options; return await collection .find({ quadrant, status: 'pending' }) .sort({ created_at: -1 }) .skip(skip) .limit(limit) .toArray(); } /** * Review item (approve/reject/modify/escalate) */ static async review(id, decision) { const collection = await getCollection('moderation_queue'); const result = await collection.updateOne( { _id: new ObjectId(id) }, { $set: { status: 'reviewed', reviewed_at: new Date(), 'review_decision.action': decision.action, 'review_decision.notes': decision.notes, 'review_decision.reviewer': decision.reviewer } } ); return result.modifiedCount > 0; } /** * Approve item */ static async approve(id, reviewerId, notes = '') { return await this.review(id, { action: 'approve', notes, reviewer: reviewerId }); } /** * Reject item */ static async reject(id, reviewerId, notes) { return await this.review(id, { action: 'reject', notes, reviewer: reviewerId }); } /** * Escalate to strategic review */ static async escalate(id, reviewerId, reason) { const collection = await getCollection('moderation_queue'); const result = await collection.updateOne( { _id: new ObjectId(id) }, { $set: { quadrant: 'STRATEGIC', priority: 'high', human_required_reason: `ESCALATED: ${reason}`, 'review_decision.action': 'escalate', 'review_decision.notes': reason, 'review_decision.reviewer': reviewerId } } ); return result.modifiedCount > 0; } /** * Count pending items */ static async countPending(filter = {}) { const collection = await getCollection('moderation_queue'); return await collection.countDocuments({ ...filter, status: 'pending' }); } /** * Get stats by quadrant */ static async getStatsByQuadrant() { const collection = await getCollection('moderation_queue'); return await collection.aggregate([ { $match: { status: 'pending' } }, { $group: { _id: '$quadrant', count: { $sum: 1 }, high_priority: { $sum: { $cond: [{ $eq: ['$priority', 'high'] }, 1, 0] } } } } ]).toArray(); } /** * Delete item */ static async delete(id) { const collection = await getCollection('moderation_queue'); const result = await collection.deleteOne({ _id: new ObjectId(id) }); return result.deletedCount > 0; } } module.exports = ModerationQueue;