feat(cultural-sensitivity): implement Phase 1 - detection and flagging (inst_081)
Phase 1: Cultural Sensitivity Detection Layer - Detects Western-centric framing (democracy, individual rights, freedom) - Detects Indigenous exclusion (missing Te Tiriti, CARE principles) - FLAGS for human review, never auto-blocks (preserves human agency) Implementation: - PluralisticDeliberationOrchestrator.assessCulturalSensitivity() - Pattern-based detection (Western-centric governance, Indigenous exclusion) - Risk levels: LOW, MEDIUM, HIGH - Recommended actions: APPROVE, SUGGEST_ADAPTATION, HUMAN_REVIEW - High-risk audiences: Non-Western countries (CN, RU, SA, IR, VN, TH, ID, MY, PH), Indigenous communities - Audit logging to MongoDB - media.controller.js respondToInquiry() - Cultural check after ContentGovernanceChecker passes - Stores cultural_sensitivity in response metadata - Returns flag if HIGH risk (doesn't block, flags for review) - blog.controller.js publishPost() - Cultural check after framework governance check - Stores cultural_sensitivity in moderation.cultural_sensitivity - Returns flag if HIGH risk (doesn't block, flags for review) - MediaInquiry.model.js - Added country, cultural_context fields to contact - respond() method supports cultural_sensitivity in response metadata Framework Integration: - Dual-layer governance: Universal rules (ContentGovernanceChecker) + Cultural sensitivity (PluralisticDeliberationOrchestrator) - inst_081 pluralism: Different value frameworks equally legitimate - Human-in-the-loop: AI detects/suggests, human decides Next: Phase 2 (UI/workflow), Phase 3 (learning/refinement) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8217f3cb8c
commit
cd97a5384d
4 changed files with 430 additions and 18 deletions
|
|
@ -12,6 +12,7 @@ const logger = require('../utils/logger.util');
|
||||||
const claudeAPI = require('../services/ClaudeAPI.service');
|
const claudeAPI = require('../services/ClaudeAPI.service');
|
||||||
const BoundaryEnforcer = require('../services/BoundaryEnforcer.service');
|
const BoundaryEnforcer = require('../services/BoundaryEnforcer.service');
|
||||||
const BlogCuration = require('../services/BlogCuration.service');
|
const BlogCuration = require('../services/BlogCuration.service');
|
||||||
|
const PluralisticDeliberationOrchestrator = require('../services/PluralisticDeliberationOrchestrator.service');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List published blog posts (public)
|
* List published blog posts (public)
|
||||||
|
|
@ -311,18 +312,91 @@ async function publishPost(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cultural sensitivity check (inst_081 pluralism)
|
||||||
|
// Detects Western-centric framing, Indigenous exclusion
|
||||||
|
// FLAGS for review, never blocks (human decides)
|
||||||
|
let culturalCheck = null;
|
||||||
|
let culturalReviewRequired = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get full text for cultural check (title + excerpt + content)
|
||||||
|
const fullText = [post.title, post.excerpt, post.content]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n')
|
||||||
|
.replace(/<[^>]*>/g, ''); // Strip HTML tags
|
||||||
|
|
||||||
|
culturalCheck = await PluralisticDeliberationOrchestrator.assessCulturalSensitivity(fullText, {
|
||||||
|
audience: {
|
||||||
|
// Phase 2: will add target_publications, language fields to BlogPost
|
||||||
|
tags: post.tags
|
||||||
|
},
|
||||||
|
content_type: 'blog_post',
|
||||||
|
post_id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (culturalCheck.risk_level === 'HIGH') {
|
||||||
|
culturalReviewRequired = true;
|
||||||
|
logger.warn(`Blog post flagged for cultural sensitivity review`, {
|
||||||
|
post_id: id,
|
||||||
|
slug: post.slug,
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
concerns_count: culturalCheck.concerns.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store cultural check results
|
||||||
|
await BlogPost.update(id, {
|
||||||
|
'moderation.cultural_sensitivity': {
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
concerns: culturalCheck.concerns,
|
||||||
|
suggestions: culturalCheck.suggestions,
|
||||||
|
recommended_action: culturalCheck.recommended_action,
|
||||||
|
checked_at: culturalCheck.timestamp
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (culturalError) {
|
||||||
|
logger.error('Cultural sensitivity check failed:', culturalError);
|
||||||
|
// Non-blocking: Allow publication but log the failure
|
||||||
|
await BlogPost.update(id, {
|
||||||
|
'moderation.cultural_sensitivity_error': culturalError.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Publish the post
|
// Publish the post
|
||||||
await BlogPost.publish(id, req.userId);
|
await BlogPost.publish(id, req.userId);
|
||||||
|
|
||||||
const updatedPost = await BlogPost.findById(id);
|
const updatedPost = await BlogPost.findById(id);
|
||||||
|
|
||||||
logger.info(`Blog post published: ${id} by ${req.user.email}`);
|
logger.info(`Blog post published: ${id} by ${req.user.email}`, {
|
||||||
|
cultural_review_required: culturalReviewRequired
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
post: updatedPost,
|
post: updatedPost,
|
||||||
message: 'Post published successfully'
|
message: culturalReviewRequired
|
||||||
});
|
? 'Post published - FLAGGED for cultural sensitivity review'
|
||||||
|
: 'Post published successfully'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (culturalCheck) {
|
||||||
|
response.cultural_sensitivity = {
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
review_required: culturalReviewRequired,
|
||||||
|
concerns_count: culturalCheck.concerns.length,
|
||||||
|
suggestions_count: culturalCheck.suggestions.length
|
||||||
|
};
|
||||||
|
|
||||||
|
// Include details if review required
|
||||||
|
if (culturalReviewRequired) {
|
||||||
|
response.cultural_sensitivity.concerns = culturalCheck.concerns;
|
||||||
|
response.cultural_sensitivity.suggestions = culturalCheck.suggestions;
|
||||||
|
response.cultural_sensitivity.note = 'Human review recommended (inst_081 pluralism)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(response);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Publish post error:', error);
|
logger.error('Publish post error:', error);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const GovernanceLog = require('../models/GovernanceLog.model');
|
||||||
const BoundaryEnforcer = require('../services/BoundaryEnforcer.service');
|
const BoundaryEnforcer = require('../services/BoundaryEnforcer.service');
|
||||||
const MediaTriageService = require('../services/MediaTriage.service');
|
const MediaTriageService = require('../services/MediaTriage.service');
|
||||||
const ContentGovernanceChecker = require('../services/ContentGovernanceChecker.service');
|
const ContentGovernanceChecker = require('../services/ContentGovernanceChecker.service');
|
||||||
|
const PluralisticDeliberationOrchestrator = require('../services/PluralisticDeliberationOrchestrator.service');
|
||||||
const logger = require('../utils/logger.util');
|
const logger = require('../utils/logger.util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -269,13 +270,48 @@ async function respondToInquiry(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cultural sensitivity check (inst_081 pluralism)
|
||||||
|
// Detects Western-centric framing, Indigenous exclusion for diverse audiences
|
||||||
|
// FLAGS for review, never blocks (human decides)
|
||||||
|
let culturalCheck = null;
|
||||||
|
let culturalReviewRequired = false;
|
||||||
|
|
||||||
|
if (inquiry.contact) {
|
||||||
|
culturalCheck = await PluralisticDeliberationOrchestrator.assessCulturalSensitivity(content, {
|
||||||
|
audience: {
|
||||||
|
country: inquiry.contact.country,
|
||||||
|
outlet: inquiry.contact.outlet,
|
||||||
|
cultural_context: inquiry.contact.cultural_context
|
||||||
|
},
|
||||||
|
content_type: 'media_response',
|
||||||
|
inquiry_id: id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (culturalCheck.risk_level === 'HIGH') {
|
||||||
|
culturalReviewRequired = true;
|
||||||
|
logger.warn(`Media response flagged for cultural sensitivity review`, {
|
||||||
|
inquiry_id: id,
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
concerns_count: culturalCheck.concerns.length,
|
||||||
|
responder: req.user.email
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const success = await MediaInquiry.respond(id, {
|
const success = await MediaInquiry.respond(id, {
|
||||||
content,
|
content,
|
||||||
responder: req.user.email,
|
responder: req.user.email,
|
||||||
governance_check: {
|
governance_check: {
|
||||||
passed: true,
|
passed: true,
|
||||||
scanned_at: governanceCheck.scannedAt
|
scanned_at: governanceCheck.scannedAt
|
||||||
}
|
},
|
||||||
|
cultural_sensitivity: culturalCheck ? {
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
concerns: culturalCheck.concerns,
|
||||||
|
suggestions: culturalCheck.suggestions,
|
||||||
|
recommended_action: culturalCheck.recommended_action,
|
||||||
|
checked_at: culturalCheck.timestamp
|
||||||
|
} : null
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|
@ -286,18 +322,39 @@ async function respondToInquiry(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Media inquiry ${id} responded to by ${req.user.email}`, {
|
logger.info(`Media inquiry ${id} responded to by ${req.user.email}`, {
|
||||||
governance_check_passed: true
|
governance_check_passed: true,
|
||||||
|
cultural_review_required: culturalReviewRequired
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Response recorded successfully (framework check passed)',
|
message: culturalReviewRequired
|
||||||
|
? 'Response recorded - FLAGGED for cultural sensitivity review'
|
||||||
|
: 'Response recorded successfully (framework check passed)',
|
||||||
note: 'Remember to send actual email to media contact separately',
|
note: 'Remember to send actual email to media contact separately',
|
||||||
governance: {
|
governance: {
|
||||||
checked: true,
|
checked: true,
|
||||||
violations: 0
|
violations: 0
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (culturalCheck) {
|
||||||
|
response.cultural_sensitivity = {
|
||||||
|
risk_level: culturalCheck.risk_level,
|
||||||
|
review_required: culturalReviewRequired,
|
||||||
|
concerns_count: culturalCheck.concerns.length,
|
||||||
|
suggestions_count: culturalCheck.suggestions.length
|
||||||
|
};
|
||||||
|
|
||||||
|
// Include details if review required
|
||||||
|
if (culturalReviewRequired) {
|
||||||
|
response.cultural_sensitivity.concerns = culturalCheck.concerns;
|
||||||
|
response.cultural_sensitivity.suggestions = culturalCheck.suggestions;
|
||||||
|
response.cultural_sensitivity.note = 'Human review recommended before sending (inst_081 pluralism)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(response);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Respond to inquiry error:', error);
|
logger.error('Respond to inquiry error:', error);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ class MediaInquiry {
|
||||||
name: data.contact.name,
|
name: data.contact.name,
|
||||||
email: data.contact.email,
|
email: data.contact.email,
|
||||||
outlet: data.contact.outlet,
|
outlet: data.contact.outlet,
|
||||||
phone: data.contact.phone
|
phone: data.contact.phone,
|
||||||
|
country: data.contact.country, // ISO country code for cultural sensitivity
|
||||||
|
cultural_context: data.contact.cultural_context // e.g., 'indigenous', 'non-western'
|
||||||
},
|
},
|
||||||
inquiry: {
|
inquiry: {
|
||||||
subject: data.inquiry.subject,
|
subject: data.inquiry.subject,
|
||||||
|
|
@ -127,16 +129,28 @@ class MediaInquiry {
|
||||||
static async respond(id, responseData) {
|
static async respond(id, responseData) {
|
||||||
const collection = await getCollection('media_inquiries');
|
const collection = await getCollection('media_inquiries');
|
||||||
|
|
||||||
|
const updateDoc = {
|
||||||
|
$set: {
|
||||||
|
status: 'responded',
|
||||||
|
'response.sent_at': new Date(),
|
||||||
|
'response.content': responseData.content,
|
||||||
|
'response.responder': responseData.responder
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add governance check results if provided
|
||||||
|
if (responseData.governance_check) {
|
||||||
|
updateDoc.$set['response.governance_check'] = responseData.governance_check;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cultural sensitivity check results if provided
|
||||||
|
if (responseData.cultural_sensitivity) {
|
||||||
|
updateDoc.$set['response.cultural_sensitivity'] = responseData.cultural_sensitivity;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await collection.updateOne(
|
const result = await collection.updateOne(
|
||||||
{ _id: new ObjectId(id) },
|
{ _id: new ObjectId(id) },
|
||||||
{
|
updateDoc
|
||||||
$set: {
|
|
||||||
status: 'responded',
|
|
||||||
'response.sent_at': new Date(),
|
|
||||||
'response.content': responseData.content,
|
|
||||||
'response.responder': responseData.responder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.modifiedCount > 0;
|
return result.modifiedCount > 0;
|
||||||
|
|
|
||||||
|
|
@ -562,6 +562,273 @@ class PluralisticDeliberationOrchestrator {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assess cultural sensitivity of external communication content
|
||||||
|
* Identifies culturally inappropriate messaging for diverse audiences
|
||||||
|
*
|
||||||
|
* @param {string} content - Content to assess
|
||||||
|
* @param {Object} context - Audience/communication context
|
||||||
|
* @param {Object} context.audience - Audience information (country, region, cultural_context)
|
||||||
|
* @param {string} context.content_type - Type (media_response, blog_post, template, newsletter)
|
||||||
|
* @returns {Promise<Object>} Cultural sensitivity assessment
|
||||||
|
*
|
||||||
|
* Core Principles:
|
||||||
|
* - inst_081: Pluralism - different value frameworks are equally legitimate
|
||||||
|
* - Detect Western-centric framings inappropriate for other cultures
|
||||||
|
* - FLAG for review, never auto-block (human decides)
|
||||||
|
* - Suggest adaptations, but preserve human agency
|
||||||
|
*/
|
||||||
|
async assessCulturalSensitivity(content, context = {}) {
|
||||||
|
const {
|
||||||
|
audience = {},
|
||||||
|
content_type = 'general',
|
||||||
|
target_publications = []
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
logger.info('[PluralisticDeliberationOrchestrator] Assessing cultural sensitivity', {
|
||||||
|
content_type,
|
||||||
|
audience_country: audience.country,
|
||||||
|
audience_region: audience.region
|
||||||
|
});
|
||||||
|
|
||||||
|
const assessment = {
|
||||||
|
culturally_sensitive: true,
|
||||||
|
risk_level: 'LOW',
|
||||||
|
concerns: [],
|
||||||
|
suggestions: [],
|
||||||
|
recommended_action: 'APPROVE',
|
||||||
|
timestamp: new Date(),
|
||||||
|
context
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if audience is non-Western or culturally distinct
|
||||||
|
const highRiskAudience = this._identifyHighRiskAudience(audience, target_publications);
|
||||||
|
const sensitiveTopics = this._detectSensitiveTopics(content);
|
||||||
|
|
||||||
|
// Western-centric governance language
|
||||||
|
const westernGovernancePatterns = {
|
||||||
|
democracy: {
|
||||||
|
patterns: [/\bdemocrac(?:y|tic)\b/gi, /\bdemocratic\s+(?:governance|oversight|control)\b/gi],
|
||||||
|
concern: 'Democratic framing may have political connotations in autocratic contexts',
|
||||||
|
suggestion: 'Consider "participatory governance", "stakeholder input", or "inclusive decision-making"'
|
||||||
|
},
|
||||||
|
individual_rights: {
|
||||||
|
patterns: [/\bindividual\s+(?:rights|freedom|autonomy)\b/gi, /\bpersonal\s+freedom\b/gi],
|
||||||
|
concern: 'Individualistic framing may not resonate in collectivist cultures',
|
||||||
|
suggestion: 'Balance with "community wellbeing", "collective benefit", or "shared responsibility"'
|
||||||
|
},
|
||||||
|
western_ethics_only: {
|
||||||
|
patterns: [/\bethics\b(?!.*(?:diverse|pluralistic|multiple|indigenous))/gi],
|
||||||
|
concern: 'Implies universal Western ethics without acknowledging other frameworks',
|
||||||
|
suggestion: 'Reference "diverse ethical frameworks" or "culturally-grounded values"'
|
||||||
|
},
|
||||||
|
freedom_emphasis: {
|
||||||
|
patterns: [/\bfreedom\s+of\s+(?:speech|expression|press)\b/gi],
|
||||||
|
concern: 'Western rights discourse may be politically sensitive in some regions',
|
||||||
|
suggestion: 'Frame as "open communication" or "information access" if contextually appropriate'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Indigenous/cultural insensitivity patterns
|
||||||
|
const culturalInsensitivityPatterns = {
|
||||||
|
western_only_governance: {
|
||||||
|
patterns: [/\bgovernance\b(?!.*(?:indigenous|te\s+tiriti|care\s+principles))/gi],
|
||||||
|
concern: 'Western governance frameworks only, excludes Indigenous perspectives',
|
||||||
|
suggestion: 'Acknowledge Indigenous governance (Te Tiriti, CARE principles, relational sovereignty)'
|
||||||
|
},
|
||||||
|
data_ownership_western: {
|
||||||
|
patterns: [/\bdata\s+(?:ownership|rights)\b(?!.*(?:sovereignty|collective))/gi],
|
||||||
|
concern: 'Individual data ownership framing conflicts with collective Indigenous data sovereignty',
|
||||||
|
suggestion: 'Reference "data sovereignty", "collective data rights", or "community data governance"'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check patterns
|
||||||
|
if (highRiskAudience || sensitiveTopics.length > 0) {
|
||||||
|
// Scan for Western-centric patterns
|
||||||
|
for (const [key, config] of Object.entries(westernGovernancePatterns)) {
|
||||||
|
for (const pattern of config.patterns) {
|
||||||
|
if (pattern.test(content)) {
|
||||||
|
assessment.culturally_sensitive = false;
|
||||||
|
assessment.concerns.push({
|
||||||
|
type: 'western_centric_framing',
|
||||||
|
pattern_key: key,
|
||||||
|
detail: config.concern,
|
||||||
|
audience_context: this._formatAudienceContext(audience, target_publications)
|
||||||
|
});
|
||||||
|
assessment.suggestions.push({
|
||||||
|
type: 'reframing',
|
||||||
|
original_concern: key,
|
||||||
|
suggestion: config.suggestion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for Indigenous insensitivity (if audience includes Indigenous communities)
|
||||||
|
if (this._isIndigenousAudience(audience, target_publications)) {
|
||||||
|
for (const [key, config] of Object.entries(culturalInsensitivityPatterns)) {
|
||||||
|
for (const pattern of config.patterns) {
|
||||||
|
if (pattern.test(content)) {
|
||||||
|
assessment.culturally_sensitive = false;
|
||||||
|
assessment.concerns.push({
|
||||||
|
type: 'indigenous_exclusion',
|
||||||
|
pattern_key: key,
|
||||||
|
detail: config.concern,
|
||||||
|
audience_context: 'Indigenous community or Te Tiriti context'
|
||||||
|
});
|
||||||
|
assessment.suggestions.push({
|
||||||
|
type: 'indigenous_inclusion',
|
||||||
|
original_concern: key,
|
||||||
|
suggestion: config.suggestion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine risk level and recommended action
|
||||||
|
const highSeverityConcerns = assessment.concerns.filter(c =>
|
||||||
|
c.type === 'western_centric_framing' && highRiskAudience
|
||||||
|
);
|
||||||
|
|
||||||
|
if (assessment.concerns.length === 0) {
|
||||||
|
assessment.risk_level = 'LOW';
|
||||||
|
assessment.recommended_action = 'APPROVE';
|
||||||
|
} else if (highSeverityConcerns.length > 0 || assessment.concerns.length >= 3) {
|
||||||
|
assessment.risk_level = 'HIGH';
|
||||||
|
assessment.recommended_action = 'HUMAN_REVIEW';
|
||||||
|
} else {
|
||||||
|
assessment.risk_level = 'MEDIUM';
|
||||||
|
assessment.recommended_action = 'SUGGEST_ADAPTATION';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
this._auditCulturalSensitivity(assessment).catch(error => {
|
||||||
|
logger.error('[PluralisticDeliberationOrchestrator] Failed to audit cultural sensitivity check', {
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[PluralisticDeliberationOrchestrator] Cultural sensitivity assessment complete', {
|
||||||
|
risk_level: assessment.risk_level,
|
||||||
|
concerns_count: assessment.concerns.length,
|
||||||
|
recommended_action: assessment.recommended_action
|
||||||
|
});
|
||||||
|
|
||||||
|
return assessment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify if audience is high-risk for cultural insensitivity
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_identifyHighRiskAudience(audience, target_publications = []) {
|
||||||
|
// Non-Western countries
|
||||||
|
const nonWesternCountries = ['CN', 'RU', 'SA', 'IR', 'VN', 'TH', 'ID', 'MY', 'PH'];
|
||||||
|
if (audience.country && nonWesternCountries.includes(audience.country)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indigenous communities
|
||||||
|
if (audience.cultural_context && audience.cultural_context.includes('indigenous')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publications in non-Western regions
|
||||||
|
if (target_publications.some(pub => pub.region && !['Western', 'US', 'EU', 'NZ', 'AU', 'CA'].includes(pub.region))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect sensitive topics in content
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_detectSensitiveTopics(content) {
|
||||||
|
const topics = [];
|
||||||
|
const topicPatterns = {
|
||||||
|
values: /\b(?:values|ethics|morals?|principles)\b/gi,
|
||||||
|
governance: /\b(?:governance|democracy|rights|freedom)\b/gi,
|
||||||
|
religion: /\b(?:religion|spiritual|faith|belief)\b/gi,
|
||||||
|
politics: /\b(?:political|government|state|regime)\b/gi,
|
||||||
|
culture: /\b(?:culture|tradition|indigenous|tribal)\b/gi
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [topic, pattern] of Object.entries(topicPatterns)) {
|
||||||
|
if (pattern.test(content)) {
|
||||||
|
topics.push(topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if audience is Indigenous
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_isIndigenousAudience(audience, target_publications = []) {
|
||||||
|
if (audience.cultural_context && /indigenous|māori|aboriginal|first\s+nations/i.test(audience.cultural_context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_publications.some(pub => pub.audience && /indigenous|māori/i.test(pub.audience))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format audience context for reporting
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_formatAudienceContext(audience, target_publications) {
|
||||||
|
const parts = [];
|
||||||
|
if (audience.country) parts.push(`Country: ${audience.country}`);
|
||||||
|
if (audience.region) parts.push(`Region: ${audience.region}`);
|
||||||
|
if (audience.outlet) parts.push(`Outlet: ${audience.outlet}`);
|
||||||
|
if (target_publications.length > 0) {
|
||||||
|
parts.push(`Publications: ${target_publications.map(p => p.name).join(', ')}`);
|
||||||
|
}
|
||||||
|
return parts.join(' | ') || 'General audience';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit cultural sensitivity check
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _auditCulturalSensitivity(assessment) {
|
||||||
|
try {
|
||||||
|
const memoryProxy = await getMemoryProxy();
|
||||||
|
const collection = await memoryProxy.getCollection('auditLogs');
|
||||||
|
|
||||||
|
await collection.insertOne({
|
||||||
|
timestamp: new Date(),
|
||||||
|
service: 'PluralisticDeliberationOrchestrator',
|
||||||
|
action: 'cultural_sensitivity_check',
|
||||||
|
decision: assessment.recommended_action,
|
||||||
|
context: {
|
||||||
|
content_type: assessment.context.content_type,
|
||||||
|
audience: assessment.context.audience,
|
||||||
|
risk_level: assessment.risk_level,
|
||||||
|
concerns_count: assessment.concerns.length,
|
||||||
|
culturally_sensitive: assessment.culturally_sensitive
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
concerns: assessment.concerns,
|
||||||
|
suggestions: assessment.suggestions
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[PluralisticDeliberationOrchestrator] Failed to create audit log', { error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get deliberation statistics
|
* Get deliberation statistics
|
||||||
* @returns {Object} Statistics object
|
* @returns {Object} Statistics object
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue