diff --git a/public/admin/blog-pre-publication.html b/public/admin/blog-pre-publication.html
new file mode 100644
index 00000000..ce1b763d
--- /dev/null
+++ b/public/admin/blog-pre-publication.html
@@ -0,0 +1,207 @@
+
+
+
+
+
+ Blog Pre-Publication Analysis | Tractatus Admin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Blog Pre-Publication Analysis
+
Framework-guided content review and publication recommendations
+
+
+
+
+
+
+
+
+
+
+
Blog Post Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Publication Guidance
+
+
+
+
+
+
+
+
+
+
+
Suggested Response Templates
+
Pre-written responses for anticipated reader feedback
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/admin/comment-analysis.html b/public/admin/comment-analysis.html
new file mode 100644
index 00000000..799d9ce0
--- /dev/null
+++ b/public/admin/comment-analysis.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+ Comment & Feedback Analysis | Tractatus Admin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Comment & Feedback Analysis
+
Analyze social media comments and article feedback with framework guidance
+
+
+
+
+
+
+
+
+
+
+
Feedback Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Sentiment Analysis
+
+
+
+
+
+
+
+
+
+
+
Recommended Responses
+
+
+
+
+
+
+
+
Framework Guidance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/js/admin/blog-pre-publication.js b/public/js/admin/blog-pre-publication.js
new file mode 100644
index 00000000..0f606973
--- /dev/null
+++ b/public/js/admin/blog-pre-publication.js
@@ -0,0 +1,331 @@
+/**
+ * Blog Pre-Publication Analysis
+ * Framework-guided content review before publishing
+ */
+
+// Get auth token
+function getAuthToken() {
+ return localStorage.getItem('admin_token');
+}
+
+// Analyze blog post
+async function analyzePost() {
+ const title = document.getElementById('post-title').value.trim();
+ const content = document.getElementById('post-content').value.trim();
+ const category = document.getElementById('post-category').value;
+ const tags = document.getElementById('post-tags').value;
+
+ if (!title || !content) {
+ alert('Please enter both title and content');
+ return;
+ }
+
+ // Show loading state
+ const analyzeBtn = document.getElementById('analyze-btn');
+ analyzeBtn.disabled = true;
+ analyzeBtn.innerHTML = ' Analyzing...';
+
+ try {
+ const response = await fetch('/api/admin/blog/analyze', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ title, content, category, tags })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ displayResults(data.analysis);
+ } else {
+ alert('Analysis failed: ' + (data.error || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Analysis error:', error);
+ alert('Failed to analyze post. Please try again.');
+ } finally {
+ analyzeBtn.disabled = false;
+ analyzeBtn.innerHTML = 'Analyze with Framework';
+ }
+}
+
+// Display analysis results
+function displayResults(analysis) {
+ const resultsSection = document.getElementById('results-section');
+ resultsSection.classList.remove('hidden');
+
+ // Overall recommendation
+ const overallEl = document.getElementById('overall-recommendation');
+ const recommendationClass =
+ analysis.overall.decision === 'APPROVE' ? 'bg-green-50 border-green-300' :
+ analysis.overall.decision === 'REVIEW' ? 'bg-yellow-50 border-yellow-300' :
+ 'bg-red-50 border-red-300';
+
+ const recommendationIcon =
+ analysis.overall.decision === 'APPROVE' ? '✅' :
+ analysis.overall.decision === 'REVIEW' ? '⚠️' : '🚫';
+
+ overallEl.className = `rounded-lg p-6 border-2 ${recommendationClass}`;
+ overallEl.innerHTML = `
+
+
${recommendationIcon}
+
+
${analysis.overall.title}
+
${analysis.overall.message}
+ ${analysis.overall.action ? `
Recommended Action: ${analysis.overall.action}
` : ''}
+
+
+ `;
+
+ // Sensitivity check
+ const sensitivityEl = document.getElementById('sensitivity-result');
+ sensitivityEl.innerHTML = renderCheckResult(analysis.sensitivity);
+
+ // Compliance check
+ const complianceEl = document.getElementById('compliance-result');
+ complianceEl.innerHTML = renderCheckResult(analysis.compliance);
+
+ // Audience analysis
+ const audienceEl = document.getElementById('audience-result');
+ audienceEl.innerHTML = renderAudienceAnalysis(analysis.audience);
+
+ // Publication guidance
+ const publicationEl = document.getElementById('publication-result');
+ publicationEl.innerHTML = renderPublicationGuidance(analysis.publication);
+
+ // Response templates
+ const templatesEl = document.getElementById('response-templates');
+ templatesEl.innerHTML = renderResponseTemplates(analysis.responseTemplates);
+
+ // Apply dynamic widths using data attributes (CSP-compliant)
+ applyDynamicStyles();
+
+ // Setup template copy handlers
+ setupTemplateCopyHandlers();
+
+ // Scroll to results
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
+}
+
+// Apply dynamic styles using data attributes (CSP-compliant)
+function applyDynamicStyles() {
+ document.querySelectorAll('[data-width]').forEach(el => {
+ const width = el.getAttribute('data-width');
+ el.style.width = width + '%';
+ });
+}
+
+// Render check result
+function renderCheckResult(check) {
+ const statusIcon = check.status === 'PASS' ? '✅' : check.status === 'WARN' ? '⚠️' : '❌';
+ const statusColor = check.status === 'PASS' ? 'text-green-600' : check.status === 'WARN' ? 'text-yellow-600' : 'text-red-600';
+
+ let html = `
+
+ ${statusIcon} ${check.summary}
+
+ `;
+
+ if (check.details && check.details.length > 0) {
+ html += '';
+ check.details.forEach(detail => {
+ html += `- ${detail}
`;
+ });
+ html += '
';
+ }
+
+ if (check.recommendation) {
+ html += `
+ Recommendation: ${check.recommendation}
+
`;
+ }
+
+ return html;
+}
+
+// Render audience analysis
+function renderAudienceAnalysis(audience) {
+ let html = ``;
+
+ if (audience.engagement) {
+ html += `
+
+
Expected Engagement
+
+
+
${audience.engagement.level}%
+
+
${audience.engagement.description}
+
+ `;
+ }
+
+ if (audience.similarPosts && audience.similarPosts.length > 0) {
+ html += `
+
+
Similar Posts
+
+ ${audience.similarPosts.map(post => `• ${post.title} (${post.views} views)`).join('
')}
+
+
+ `;
+ }
+
+ html += `
`;
+ return html;
+}
+
+// Render publication guidance
+function renderPublicationGuidance(guidance) {
+ let html = ``;
+
+ if (guidance.timing) {
+ html += `
+
+
Recommended Timing
+
${guidance.timing}
+
+ `;
+ }
+
+ if (guidance.monitoring) {
+ html += `
+
+
Post-Publication Monitoring
+
${guidance.monitoring}
+
+ `;
+ }
+
+ if (guidance.actions && guidance.actions.length > 0) {
+ html += `
+
+
Recommended Actions
+
+ ${guidance.actions.map(action => `- ${action}
`).join('')}
+
+
+ `;
+ }
+
+ html += `
`;
+ return html;
+}
+
+// Render response templates
+function renderResponseTemplates(templates) {
+ if (!templates || templates.length === 0) {
+ return 'No response templates generated
';
+ }
+
+ return templates.map((template, index) => `
+
+
+
${template.scenario}
+
+
+
"${template.response}"
+
+ `).join('');
+}
+
+// Copy template to clipboard
+function setupTemplateCopyHandlers() {
+ document.querySelectorAll('.copy-template-btn').forEach(btn => {
+ btn.addEventListener('click', function(e) {
+ e.stopPropagation();
+ const card = this.closest('.template-card');
+ const text = card.querySelector('.template-text').textContent.replace(/"/g, '');
+ navigator.clipboard.writeText(text).then(() => {
+ const originalText = this.textContent;
+ this.textContent = 'Copied!';
+ this.classList.add('text-green-600');
+ setTimeout(() => {
+ this.textContent = originalText;
+ this.classList.remove('text-green-600');
+ }, 2000);
+ });
+ });
+ });
+}
+
+// Save as draft
+async function saveDraft() {
+ const title = document.getElementById('post-title').value.trim();
+ const content = document.getElementById('post-content').value.trim();
+ const category = document.getElementById('post-category').value;
+ const tags = document.getElementById('post-tags').value;
+
+ if (!title || !content) {
+ alert('Please enter both title and content');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/admin/blog/draft', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ title, content, category, tags, status: 'draft' })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ alert('Draft saved successfully!');
+ } else {
+ alert('Failed to save draft: ' + (data.error || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Save draft error:', error);
+ alert('Failed to save draft. Please try again.');
+ }
+}
+
+// Publish post
+async function publishPost() {
+ if (!confirm('Are you sure you want to publish this post?')) {
+ return;
+ }
+
+ const title = document.getElementById('post-title').value.trim();
+ const content = document.getElementById('post-content').value.trim();
+ const category = document.getElementById('post-category').value;
+ const tags = document.getElementById('post-tags').value;
+
+ try {
+ const response = await fetch('/api/admin/blog/publish', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ title, content, category, tags, status: 'published' })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ alert('Post published successfully!');
+ window.location.href = '/admin/blog-posts.html';
+ } else {
+ alert('Failed to publish: ' + (data.error || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Publish error:', error);
+ alert('Failed to publish. Please try again.');
+ }
+}
+
+// Event listeners
+document.addEventListener('DOMContentLoaded', () => {
+ document.getElementById('analyze-btn').addEventListener('click', analyzePost);
+ document.getElementById('save-draft-btn')?.addEventListener('click', saveDraft);
+ document.getElementById('publish-btn')?.addEventListener('click', publishPost);
+});
diff --git a/public/js/admin/comment-analysis.js b/public/js/admin/comment-analysis.js
new file mode 100644
index 00000000..a06cceb9
--- /dev/null
+++ b/public/js/admin/comment-analysis.js
@@ -0,0 +1,421 @@
+/**
+ * Comment & Feedback Analysis
+ * Framework-guided analysis of social media comments and article feedback
+ */
+
+// Get auth token
+function getAuthToken() {
+ return localStorage.getItem('admin_token');
+}
+
+// Analyze feedback
+async function analyzeFeedback() {
+ const source = document.getElementById('feedback-source').value;
+ const relatedPost = document.getElementById('related-post').value.trim();
+ const content = document.getElementById('feedback-content').value.trim();
+ const notes = document.getElementById('your-notes').value.trim();
+
+ if (!content) {
+ alert('Please enter feedback content');
+ return;
+ }
+
+ if (!source) {
+ alert('Please select a source platform');
+ return;
+ }
+
+ // Show loading state
+ const analyzeBtn = document.getElementById('analyze-feedback-btn');
+ analyzeBtn.disabled = true;
+ analyzeBtn.innerHTML = ' Analyzing...';
+
+ try {
+ const response = await fetch('/api/admin/feedback/analyze', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ source, relatedPost, content, notes })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ displayResults(data.analysis);
+ } else {
+ alert('Analysis failed: ' + (data.error || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Analysis error:', error);
+ alert('Failed to analyze feedback. Please try again.');
+ } finally {
+ analyzeBtn.disabled = false;
+ analyzeBtn.innerHTML = 'Analyze with Framework';
+ }
+}
+
+// Display analysis results
+function displayResults(analysis) {
+ const resultsSection = document.getElementById('results-section');
+ resultsSection.classList.remove('hidden');
+
+ // Sentiment analysis
+ const sentimentEl = document.getElementById('sentiment-result');
+ sentimentEl.innerHTML = renderSentiment(analysis.sentiment);
+
+ // Values & concerns
+ const valuesEl = document.getElementById('values-result');
+ valuesEl.innerHTML = renderValues(analysis.values);
+
+ // Risk assessment
+ const riskEl = document.getElementById('risk-result');
+ riskEl.innerHTML = renderRisk(analysis.risk);
+
+ // Recommended responses
+ const responseEl = document.getElementById('response-options');
+ responseEl.innerHTML = renderResponses(analysis.responses);
+
+ // Framework guidance
+ const guidanceEl = document.getElementById('framework-guidance');
+ guidanceEl.innerHTML = renderGuidance(analysis.guidance);
+
+ // Setup copy handlers for responses
+ setupResponseCopyHandlers();
+
+ // Scroll to results
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
+}
+
+// Render sentiment analysis
+function renderSentiment(sentiment) {
+ const sentimentColors = {
+ positive: { bg: 'bg-green-100', text: 'text-green-700', icon: '😊' },
+ neutral: { bg: 'bg-gray-100', text: 'text-gray-700', icon: '😐' },
+ negative: { bg: 'bg-red-100', text: 'text-red-700', icon: '😟' },
+ mixed: { bg: 'bg-yellow-100', text: 'text-yellow-700', icon: '🤔' }
+ };
+
+ const colors = sentimentColors[sentiment.overall] || sentimentColors.neutral;
+
+ let html = `
+
+
${colors.icon}
+
+
${sentiment.overall.charAt(0).toUpperCase() + sentiment.overall.slice(1)} Sentiment
+
${sentiment.summary}
+
+
+ `;
+
+ if (sentiment.confidence) {
+ html += `
+
+
Confidence Level
+
+
+
${sentiment.confidence}%
+
+
+ `;
+ }
+
+ if (sentiment.keyPhrases && sentiment.keyPhrases.length > 0) {
+ html += `
+
+
Key Phrases
+
+ ${sentiment.keyPhrases.map(phrase =>
+ `${phrase}`
+ ).join('')}
+
+
+ `;
+ }
+
+ return html;
+}
+
+// Render values & concerns
+function renderValues(values) {
+ let html = '';
+
+ if (values.alignedWith && values.alignedWith.length > 0) {
+ html += `
+
+
+
+ Aligned With Our Values
+
+
+ ${values.alignedWith.map(v => `- ${v}
`).join('')}
+
+
+ `;
+ }
+
+ if (values.concernsRaised && values.concernsRaised.length > 0) {
+ html += `
+
+
+
+ Concerns Raised
+
+
+ ${values.concernsRaised.map(c => `- ${c}
`).join('')}
+
+
+ `;
+ }
+
+ if (values.misunderstandings && values.misunderstandings.length > 0) {
+ html += `
+
+
+
+ Potential Misunderstandings
+
+
+ ${values.misunderstandings.map(m => `- ${m}
`).join('')}
+
+
+ `;
+ }
+
+ html += '
';
+ return html;
+}
+
+// Render risk assessment
+function renderRisk(risk) {
+ const riskLevels = {
+ low: { color: 'text-green-600', bg: 'bg-green-50', icon: '✅' },
+ medium: { color: 'text-yellow-600', bg: 'bg-yellow-50', icon: '⚠️' },
+ high: { color: 'text-red-600', bg: 'bg-red-50', icon: '🚨' }
+ };
+
+ const level = riskLevels[risk.level] || riskLevels.low;
+
+ let html = `
+
+
+ ${level.icon}
+ ${risk.level.toUpperCase()} Risk
+
+
${risk.summary}
+
+ `;
+
+ if (risk.factors && risk.factors.length > 0) {
+ html += `
+
+
Risk Factors
+
+ ${risk.factors.map(f => `- ${f}
`).join('')}
+
+
+ `;
+ }
+
+ if (risk.recommendations && risk.recommendations.length > 0) {
+ html += `
+
+
Recommended Actions
+
+ ${risk.recommendations.map(r => `- ${r}
`).join('')}
+
+
+ `;
+ }
+
+ return html;
+}
+
+// Render recommended responses
+function renderResponses(responses) {
+ if (!responses || responses.length === 0) {
+ return 'No response recommendations generated
';
+ }
+
+ return responses.map((response, index) => {
+ const priorityColors = {
+ high: 'border-red-300 bg-red-50',
+ medium: 'border-yellow-300 bg-yellow-50',
+ low: 'border-gray-300 bg-gray-50'
+ };
+
+ const borderColor = priorityColors[response.priority] || priorityColors.low;
+
+ return `
+
+
+
+
${response.approach}
+ ${response.priority ? `
Priority: ${response.priority}
` : ''}
+
+
+
+
${response.text}
+ ${response.rationale ? `
${response.rationale}
` : ''}
+
+ `;
+ }).join('');
+}
+
+// Render framework guidance
+function renderGuidance(guidance) {
+ let html = '';
+
+ if (guidance.shouldRespond !== undefined) {
+ const respondIcon = guidance.shouldRespond ? '✅' : '❌';
+ const respondText = guidance.shouldRespond ? 'Yes - Response Recommended' : 'No - Consider Not Responding';
+ const respondColor = guidance.shouldRespond ? 'text-green-700' : 'text-red-700';
+
+ html += `
+
+ ${respondIcon} ${respondText}
+
+ `;
+ }
+
+ if (guidance.keyConsiderations && guidance.keyConsiderations.length > 0) {
+ html += `
+
+
Key Considerations
+
+ ${guidance.keyConsiderations.map(k => `- ${k}
`).join('')}
+
+
+ `;
+ }
+
+ if (guidance.tone) {
+ html += `
+
+
Recommended Tone
+
${guidance.tone}
+
+ `;
+ }
+
+ return html || 'No framework guidance available
';
+}
+
+// Setup copy handlers for responses (CSP-compliant)
+function setupResponseCopyHandlers() {
+ document.querySelectorAll('.copy-response-btn').forEach(btn => {
+ btn.addEventListener('click', function(e) {
+ e.stopPropagation();
+ const card = this.closest('.response-card');
+ const text = card.querySelector('.response-text').textContent;
+ navigator.clipboard.writeText(text).then(() => {
+ const originalText = this.textContent;
+ this.textContent = 'Copied!';
+ this.classList.add('text-green-600');
+ setTimeout(() => {
+ this.textContent = originalText;
+ this.classList.remove('text-green-600');
+ }, 2000);
+ });
+ });
+ });
+}
+
+// Apply dynamic styles using data attributes (CSP-compliant)
+function applyDynamicStyles() {
+ document.querySelectorAll('[data-width]').forEach(el => {
+ const width = el.getAttribute('data-width');
+ el.style.width = width + '%';
+ });
+}
+
+// Save analysis
+async function saveAnalysis() {
+ const source = document.getElementById('feedback-source').value;
+ const relatedPost = document.getElementById('related-post').value.trim();
+ const content = document.getElementById('feedback-content').value.trim();
+ const notes = document.getElementById('your-notes').value.trim();
+
+ if (!content) {
+ alert('Please enter feedback content before saving');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/admin/feedback/save', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ source, relatedPost, content, notes })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ alert('Analysis saved successfully!');
+ } else {
+ alert('Failed to save analysis: ' + (data.error || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Save error:', error);
+ alert('Failed to save analysis. Please try again.');
+ }
+}
+
+// Export report
+async function exportReport() {
+ const source = document.getElementById('feedback-source').value;
+ const relatedPost = document.getElementById('related-post').value.trim();
+ const content = document.getElementById('feedback-content').value.trim();
+
+ if (!content) {
+ alert('Please analyze feedback before exporting');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/admin/feedback/export', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ source, relatedPost, content })
+ });
+
+ if (response.ok) {
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `feedback-analysis-${Date.now()}.pdf`;
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ } else {
+ alert('Failed to export report');
+ }
+ } catch (error) {
+ console.error('Export error:', error);
+ alert('Failed to export report. Please try again.');
+ }
+}
+
+// Event listeners
+document.addEventListener('DOMContentLoaded', () => {
+ document.getElementById('analyze-feedback-btn').addEventListener('click', analyzeFeedback);
+ document.getElementById('save-analysis-btn')?.addEventListener('click', saveAnalysis);
+ document.getElementById('export-report-btn')?.addEventListener('click', exportReport);
+});
diff --git a/src/controllers/framework-content-analysis.controller.js b/src/controllers/framework-content-analysis.controller.js
new file mode 100644
index 00000000..9c73e60f
--- /dev/null
+++ b/src/controllers/framework-content-analysis.controller.js
@@ -0,0 +1,429 @@
+/**
+ * Framework Content Analysis Controller
+ * Handles framework-guided blog pre-publication and comment/feedback analysis
+ */
+
+const PluralisticDeliberationOrchestrator = require('../services/PluralisticDeliberationOrchestrator.service');
+const BoundaryEnforcer = require('../services/BoundaryEnforcer.service');
+const logger = require('../utils/logger.util');
+
+/**
+ * Analyze blog post before publication
+ * Provides framework-guided content review with sensitivity checks, compliance validation,
+ * audience analysis, and response templates
+ *
+ * POST /api/admin/blog/analyze
+ * Body: { title, content, category, tags }
+ */
+exports.analyzeBlogPost = async (req, res) => {
+ const { title, content, category, tags } = req.body;
+
+ logger.info('[Framework Content Analysis] Blog post analysis requested', {
+ userId: req.user.id,
+ title,
+ category
+ });
+
+ try {
+ // Initialize services
+ const deliberationOrchestrator = new PluralisticDeliberationOrchestrator();
+ const boundaryEnforcer = new BoundaryEnforcer();
+
+ // 1. Sensitivity check - detect values-sensitive topics
+ const sensitivityResult = await deliberationOrchestrator.detectValuesSensitivity({
+ content: `${title}\n\n${content}`,
+ context: { category, tags }
+ });
+
+ // 2. Compliance check - ensure framework adherence
+ const complianceResult = await boundaryEnforcer.checkCompliance({
+ content,
+ title,
+ type: 'blog_post',
+ category
+ });
+
+ // 3. Audience analysis - engagement prediction
+ const audienceAnalysis = {
+ engagement: {
+ level: 70, // TODO: Implement ML-based prediction
+ description: 'Expected to generate moderate engagement based on topic relevance'
+ },
+ similarPosts: [] // TODO: Query database for similar posts
+ };
+
+ // 4. Publication guidance
+ const publicationGuidance = {
+ timing: 'Publish during business hours (9am-5pm NZT) for maximum visibility',
+ monitoring: 'Monitor comments for first 48 hours post-publication',
+ actions: [
+ 'Share on LinkedIn and Twitter',
+ 'Enable comments with moderation',
+ 'Prepare standard responses for anticipated questions'
+ ]
+ };
+
+ // 5. Generate response templates for anticipated feedback
+ const responseTemplates = [
+ {
+ scenario: 'Request for more technical details',
+ response: 'Thank you for your interest. We\'re planning a follow-up article with deeper technical implementation details. Would you like to be notified when it\'s published?'
+ },
+ {
+ scenario: 'Concern about framework overhead',
+ response: 'That\'s a valid concern. The framework is designed to be lightweight - most governance checks happen at build/deploy time rather than runtime. Performance overhead is typically <1%.'
+ },
+ {
+ scenario: 'Question about alternative approaches',
+ response: 'We\'ve evaluated several alternative approaches. The framework\'s design prioritizes transparency and human oversight. We\'d be happy to discuss specific alternatives you\'re considering.'
+ }
+ ];
+
+ // 6. Overall recommendation
+ let overallRecommendation = {
+ decision: 'APPROVE',
+ title: 'Ready for Publication',
+ message: 'This content meets all framework requirements and is ready for publication.',
+ action: 'Proceed to publish when ready'
+ };
+
+ // Adjust recommendation based on checks
+ if (sensitivityResult.requiresDeliberation || complianceResult.violations?.length > 0) {
+ overallRecommendation = {
+ decision: 'REVIEW',
+ title: 'Review Recommended',
+ message: 'This content requires human review before publication.',
+ action: 'Address flagged concerns before publishing'
+ };
+ }
+
+ // Construct analysis response
+ const analysis = {
+ overall: overallRecommendation,
+ sensitivity: {
+ status: sensitivityResult.requiresDeliberation ? 'WARN' : 'PASS',
+ summary: sensitivityResult.requiresDeliberation
+ ? 'Values-sensitive content detected - review recommended'
+ : 'No significant values-sensitivity detected',
+ details: sensitivityResult.conflicts || [],
+ recommendation: sensitivityResult.guidance
+ },
+ compliance: {
+ status: complianceResult.violations?.length > 0 ? 'FAIL' : 'PASS',
+ summary: complianceResult.violations?.length > 0
+ ? `${complianceResult.violations.length} compliance issue(s) detected`
+ : 'Passes all framework compliance checks',
+ details: complianceResult.violations || [],
+ recommendation: complianceResult.guidance
+ },
+ audience: audienceAnalysis,
+ publication: publicationGuidance,
+ responseTemplates
+ };
+
+ res.json({
+ success: true,
+ analysis
+ });
+
+ } catch (error) {
+ logger.error('[Framework Content Analysis] Blog analysis error', {
+ error: error.message,
+ stack: error.stack,
+ userId: req.user.id
+ });
+
+ res.status(500).json({
+ success: false,
+ error: 'Analysis failed. Please try again.'
+ });
+ }
+};
+
+/**
+ * Save blog post as draft
+ *
+ * POST /api/admin/blog/draft
+ * Body: { title, content, category, tags, status: 'draft' }
+ */
+exports.saveBlogDraft = async (req, res) => {
+ const { title, content, category, tags } = req.body;
+
+ logger.info('[Framework Content Analysis] Saving blog draft', {
+ userId: req.user.id,
+ title
+ });
+
+ // TODO: Implement database save logic
+ // For now, return success
+ res.json({
+ success: true,
+ message: 'Draft saved successfully',
+ draftId: 'draft_' + Date.now()
+ });
+};
+
+/**
+ * Publish blog post
+ *
+ * POST /api/admin/blog/publish
+ * Body: { title, content, category, tags, status: 'published' }
+ */
+exports.publishBlogPost = async (req, res) => {
+ const { title, content, category, tags } = req.body;
+
+ logger.info('[Framework Content Analysis] Publishing blog post', {
+ userId: req.user.id,
+ title
+ });
+
+ // TODO: Implement database save and publication logic
+ // For now, return success
+ res.json({
+ success: true,
+ message: 'Post published successfully',
+ postId: 'post_' + Date.now()
+ });
+};
+
+/**
+ * Analyze comment/feedback with framework guidance
+ * Provides sentiment analysis, values alignment check, risk assessment,
+ * and recommended responses
+ *
+ * POST /api/admin/feedback/analyze
+ * Body: { source, relatedPost, content, notes }
+ */
+exports.analyzeFeedback = async (req, res) => {
+ const { source, relatedPost, content, notes } = req.body;
+
+ logger.info('[Framework Content Analysis] Feedback analysis requested', {
+ userId: req.user.id,
+ source,
+ contentLength: content.length
+ });
+
+ try {
+ // Initialize services
+ const deliberationOrchestrator = new PluralisticDeliberationOrchestrator();
+ const boundaryEnforcer = new BoundaryEnforcer();
+
+ // 1. Sentiment analysis
+ const sentimentAnalysis = analyzeSentiment(content);
+
+ // 2. Values alignment check
+ const valuesResult = await deliberationOrchestrator.detectValuesSensitivity({
+ content,
+ context: { source, relatedPost, notes }
+ });
+
+ // 3. Risk assessment
+ const riskAssessment = await boundaryEnforcer.assessRisk({
+ content,
+ source,
+ type: 'public_feedback'
+ });
+
+ // 4. Generate recommended responses
+ const responses = generateRecommendedResponses(sentimentAnalysis, valuesResult);
+
+ // 5. Framework guidance on whether to respond
+ const guidance = {
+ shouldRespond: shouldRespondToFeedback(sentimentAnalysis, valuesResult, riskAssessment),
+ keyConsiderations: [
+ 'Response should align with Tractatus values',
+ 'Avoid defensive or dismissive language',
+ 'Acknowledge valid concerns genuinely',
+ 'Clarify misunderstandings with patience'
+ ],
+ tone: sentimentAnalysis.overall === 'negative'
+ ? 'Empathetic and understanding, addressing concerns directly'
+ : 'Appreciative and informative, building on positive feedback'
+ };
+
+ const analysis = {
+ sentiment: sentimentAnalysis,
+ values: {
+ alignedWith: valuesResult.alignedValues || [],
+ concernsRaised: valuesResult.concerns || [],
+ misunderstandings: valuesResult.misunderstandings || []
+ },
+ risk: riskAssessment,
+ responses,
+ guidance
+ };
+
+ res.json({
+ success: true,
+ analysis
+ });
+
+ } catch (error) {
+ logger.error('[Framework Content Analysis] Feedback analysis error', {
+ error: error.message,
+ stack: error.stack,
+ userId: req.user.id
+ });
+
+ res.status(500).json({
+ success: false,
+ error: 'Analysis failed. Please try again.'
+ });
+ }
+};
+
+/**
+ * Save feedback analysis
+ *
+ * POST /api/admin/feedback/save
+ * Body: { source, relatedPost, content, notes }
+ */
+exports.saveFeedbackAnalysis = async (req, res) => {
+ const { source, relatedPost, content, notes } = req.body;
+
+ logger.info('[Framework Content Analysis] Saving feedback analysis', {
+ userId: req.user.id,
+ source
+ });
+
+ // TODO: Implement database save logic
+ res.json({
+ success: true,
+ message: 'Feedback analysis saved successfully',
+ analysisId: 'feedback_' + Date.now()
+ });
+};
+
+/**
+ * Export feedback analysis report
+ *
+ * POST /api/admin/feedback/export
+ * Body: { source, relatedPost, content }
+ */
+exports.exportFeedbackReport = async (req, res) => {
+ const { source, relatedPost, content } = req.body;
+
+ logger.info('[Framework Content Analysis] Exporting feedback report', {
+ userId: req.user.id,
+ source
+ });
+
+ // TODO: Implement PDF export using Puppeteer
+ // For now, return placeholder response
+ res.status(501).json({
+ success: false,
+ error: 'Export functionality coming soon'
+ });
+};
+
+// ============================================================
+// HELPER FUNCTIONS
+// ============================================================
+
+/**
+ * Analyze sentiment of text content
+ * Basic implementation - could be enhanced with ML
+ */
+function analyzeSentiment(content) {
+ const lowerContent = content.toLowerCase();
+
+ // Positive indicators
+ const positiveWords = ['great', 'excellent', 'love', 'appreciate', 'thank', 'helpful', 'useful', 'good'];
+ const positiveCount = positiveWords.filter(word => lowerContent.includes(word)).length;
+
+ // Negative indicators
+ const negativeWords = ['bad', 'terrible', 'hate', 'disappointed', 'concerned', 'wrong', 'problem', 'issue'];
+ const negativeCount = negativeWords.filter(word => lowerContent.includes(word)).length;
+
+ // Question indicators
+ const questionWords = ['how', 'what', 'why', 'when', 'where', '?'];
+ const questionCount = questionWords.filter(word => lowerContent.includes(word)).length;
+
+ // Determine overall sentiment
+ let overall = 'neutral';
+ if (positiveCount > negativeCount + 1) overall = 'positive';
+ else if (negativeCount > positiveCount + 1) overall = 'negative';
+ else if (positiveCount > 0 && negativeCount > 0) overall = 'mixed';
+
+ // Extract key phrases (simple implementation)
+ const keyPhrases = [];
+ if (lowerContent.includes('framework')) keyPhrases.push('framework discussion');
+ if (lowerContent.includes('implementation')) keyPhrases.push('implementation questions');
+ if (lowerContent.includes('concern')) keyPhrases.push('concerns raised');
+
+ return {
+ overall,
+ confidence: Math.min(95, 60 + (Math.abs(positiveCount - negativeCount) * 10)),
+ summary: overall === 'positive'
+ ? 'Feedback is generally positive and constructive'
+ : overall === 'negative'
+ ? 'Feedback raises concerns or criticism'
+ : overall === 'mixed'
+ ? 'Feedback includes both positive and critical elements'
+ : 'Neutral tone, primarily informational or questioning',
+ keyPhrases
+ };
+}
+
+/**
+ * Generate recommended responses based on analysis
+ */
+function generateRecommendedResponses(sentiment, valuesResult) {
+ const responses = [];
+
+ if (sentiment.overall === 'positive') {
+ responses.push({
+ approach: 'Appreciative acknowledgment',
+ priority: 'medium',
+ text: 'Thank you for your thoughtful feedback. We\'re glad the framework resonates with your values and approach to AI governance.',
+ rationale: 'Reinforces positive engagement'
+ });
+ }
+
+ if (sentiment.overall === 'negative') {
+ responses.push({
+ approach: 'Empathetic concern acknowledgment',
+ priority: 'high',
+ text: 'Thank you for sharing your concerns. We take this feedback seriously and want to understand your perspective better. Could you elaborate on [specific concern]?',
+ rationale: 'Demonstrates genuine listening and openness'
+ });
+ }
+
+ if (valuesResult.misunderstandings?.length > 0) {
+ responses.push({
+ approach: 'Clarifying misunderstanding',
+ priority: 'high',
+ text: 'I appreciate you raising this point - it highlights an area where our communication could be clearer. What we mean by [concept] is [clarification].',
+ rationale: 'Corrects misunderstanding without being condescending'
+ });
+ }
+
+ responses.push({
+ approach: 'Invitation to continued dialogue',
+ priority: 'low',
+ text: 'We value ongoing discussion about these important topics. If you\'d like to explore this further, feel free to [suggest next step].',
+ rationale: 'Maintains open communication channel'
+ });
+
+ return responses;
+}
+
+/**
+ * Determine if feedback warrants a response
+ */
+function shouldRespondToFeedback(sentiment, valuesResult, riskAssessment) {
+ // Always respond to high-risk feedback
+ if (riskAssessment.level === 'high') return true;
+
+ // Respond to values-misalignment concerns
+ if (valuesResult.concerns?.length > 0) return true;
+
+ // Respond to negative feedback
+ if (sentiment.overall === 'negative') return true;
+
+ // Respond to positive feedback (builds community)
+ if (sentiment.overall === 'positive') return true;
+
+ // Skip neutral/low-engagement comments
+ return false;
+}
diff --git a/src/routes/admin.routes.js b/src/routes/admin.routes.js
index a22cc49f..731ca4ff 100644
--- a/src/routes/admin.routes.js
+++ b/src/routes/admin.routes.js
@@ -7,6 +7,7 @@ const express = require('express');
const router = express.Router();
const adminController = require('../controllers/admin.controller');
+const frameworkContentAnalysis = require('../controllers/framework-content-analysis.controller');
const { authenticateToken, requireRole } = require('../middleware/auth.middleware');
const { validateRequired, validateObjectId } = require('../middleware/validation.middleware');
const { asyncHandler } = require('../middleware/error.middleware');
@@ -61,4 +62,30 @@ router.get('/activity',
asyncHandler(adminController.getActivityLog)
);
+/**
+ * Framework-Guided Comment & Feedback Analysis
+ * Sentiment analysis, values alignment, risk assessment, response recommendations
+ */
+
+// POST /api/admin/feedback/analyze - Analyze comment/feedback with framework guidance
+router.post('/feedback/analyze',
+ requireRole('admin', 'moderator'),
+ validateRequired(['source', 'content']),
+ asyncHandler(frameworkContentAnalysis.analyzeFeedback)
+);
+
+// POST /api/admin/feedback/save - Save feedback analysis
+router.post('/feedback/save',
+ requireRole('admin', 'moderator'),
+ validateRequired(['source', 'content']),
+ asyncHandler(frameworkContentAnalysis.saveFeedbackAnalysis)
+);
+
+// POST /api/admin/feedback/export - Export feedback analysis report
+router.post('/feedback/export',
+ requireRole('admin', 'moderator'),
+ validateRequired(['source', 'content']),
+ asyncHandler(frameworkContentAnalysis.exportFeedbackReport)
+);
+
module.exports = router;
diff --git a/src/routes/blog.routes.js b/src/routes/blog.routes.js
index ca703615..c1baa926 100644
--- a/src/routes/blog.routes.js
+++ b/src/routes/blog.routes.js
@@ -7,6 +7,7 @@ const express = require('express');
const router = express.Router();
const blogController = require('../controllers/blog.controller');
+const frameworkContentAnalysis = require('../controllers/framework-content-analysis.controller');
const { authenticateToken, requireRole } = require('../middleware/auth.middleware');
const { validateRequired, validateObjectId, validateSlug } = require('../middleware/validation.middleware');
const { asyncHandler } = require('../middleware/error.middleware');
@@ -105,6 +106,35 @@ router.post('/validate-article',
asyncHandler(blogController.validateArticle)
);
+/**
+ * Framework-Guided Pre-Publication Workflow
+ * Active agency management with compliance checks and response templates
+ */
+
+// POST /api/admin/blog/analyze - Framework-guided blog pre-publication analysis
+router.post('/admin/blog/analyze',
+ authenticateToken,
+ requireRole('admin'),
+ validateRequired(['title', 'content']),
+ asyncHandler(frameworkContentAnalysis.analyzeBlogPost)
+);
+
+// POST /api/admin/blog/draft - Save blog post as draft
+router.post('/admin/blog/draft',
+ authenticateToken,
+ requireRole('admin'),
+ validateRequired(['title', 'content']),
+ asyncHandler(frameworkContentAnalysis.saveBlogDraft)
+);
+
+// POST /api/admin/blog/publish - Publish blog post
+router.post('/admin/blog/publish',
+ authenticateToken,
+ requireRole('admin'),
+ validateRequired(['title', 'content']),
+ asyncHandler(frameworkContentAnalysis.publishBlogPost)
+);
+
// GET /api/blog/admin/posts?status=draft
router.get('/admin/posts',
authenticateToken,