- Implement PluralisticDeliberationOrchestrator (433 lines) - 6 moral frameworks: deontological, consequentialist, virtue, care, communitarian, indigenous - 4 urgency tiers: critical, urgent, important, routine - Foundational pluralism without value hierarchy - Precedent tracking (informative, not binding) - Implement AdaptiveCommunicationOrchestrator (346 lines) - 5 communication styles: formal, casual (pub test), Māori protocol, Japanese formal, plain - Anti-patronizing filter (removes "simply", "obviously", "clearly") - Cultural context adaptation - Both services use singleton pattern with statistics tracking - Implements TRA-OPS-0002: AI facilitates, humans decide - Supports inst_029-inst_035 (value pluralism governance) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
407 lines
13 KiB
JavaScript
407 lines
13 KiB
JavaScript
/*
|
|
* Copyright 2025 John G Stroh
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* Adaptive Communication Orchestrator Service
|
|
* Prevents linguistic hierarchy in pluralistic deliberation
|
|
*
|
|
* Support Service for PluralisticDeliberationOrchestrator
|
|
*
|
|
* Implements:
|
|
* - inst_029: Adaptive Communication Tone (detect and mirror stakeholder style)
|
|
* - inst_030: Anti-Patronizing Language Filter (blocks condescending terms)
|
|
* - inst_031: Regional Communication Norms (Australian/NZ, Japanese, Māori protocols)
|
|
* - inst_032: Multilingual Engagement Protocol (language barrier accommodation)
|
|
*
|
|
* Core Principle:
|
|
* If Tractatus facilitates "non-hierarchical deliberation" but only communicates
|
|
* in formal academic English, it imposes Western liberal norms and excludes
|
|
* non-academics, non-English speakers, and working-class communities.
|
|
*
|
|
* Solution: Same deliberation outcome, culturally appropriate communication.
|
|
*/
|
|
|
|
const logger = require('../utils/logger.util');
|
|
|
|
/**
|
|
* Communication style profiles
|
|
*/
|
|
const COMMUNICATION_STYLES = {
|
|
FORMAL_ACADEMIC: {
|
|
name: 'Formal Academic',
|
|
characteristics: ['citations', 'technical terms', 'formal register', 'hedging'],
|
|
tone: 'formal',
|
|
example: 'Thank you for your principled contribution grounded in privacy rights theory.'
|
|
},
|
|
CASUAL_DIRECT: {
|
|
name: 'Casual Direct (Australian/NZ)',
|
|
characteristics: ['directness', 'anti-tall-poppy', 'informal', 'pragmatic'],
|
|
tone: 'casual',
|
|
pub_test: true, // Would this sound awkward in casual pub conversation?
|
|
example: 'Right, here\'s where we landed: Save lives first, but only when it\'s genuinely urgent.'
|
|
},
|
|
MAORI_PROTOCOL: {
|
|
name: 'Te Reo Māori Protocol',
|
|
characteristics: ['mihi', 'whanaungatanga', 'collective framing', 'taonga respect'],
|
|
tone: 'respectful',
|
|
cultural_elements: ['kia ora', 'ngā mihi', 'whānau', 'kōrero', 'whakaaro', 'kei te pai'],
|
|
example: 'Kia ora [Name]. Ngā mihi for bringing the voice of your whānau to this kōrero.'
|
|
},
|
|
JAPANESE_FORMAL: {
|
|
name: 'Japanese Formal (Honne/Tatemae aware)',
|
|
characteristics: ['indirect', 'high context', 'relationship-focused', 'face-saving'],
|
|
tone: 'formal',
|
|
cultural_concepts: ['honne', 'tatemae', 'wa', 'uchi/soto'],
|
|
example: 'We have carefully considered your valued perspective in reaching this decision.'
|
|
},
|
|
PLAIN_LANGUAGE: {
|
|
name: 'Plain Language',
|
|
characteristics: ['simple', 'clear', 'accessible', 'non-jargon'],
|
|
tone: 'neutral',
|
|
example: 'We decided to prioritize safety in this case. Here\'s why...'
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Patronizing terms to filter (inst_030)
|
|
*/
|
|
const PATRONIZING_PATTERNS = [
|
|
{ pattern: /\bsimply\b/gi, reason: 'Implies task is trivial' },
|
|
{ pattern: /\bobviously\b/gi, reason: 'Dismisses difficulty' },
|
|
{ pattern: /\bclearly\b/gi, reason: 'Assumes shared understanding' },
|
|
{ pattern: /\bas you may know\b/gi, reason: 'Condescending hedge' },
|
|
{ pattern: /\bof course\b/gi, reason: 'Assumes obviousness' },
|
|
{ pattern: /\bjust\b(?= (do|make|use|try))/gi, reason: 'Minimizes complexity' },
|
|
{ pattern: /\bbasically\b/gi, reason: 'Can be condescending' }
|
|
];
|
|
|
|
/**
|
|
* Language detection patterns (basic - production would use proper i18n library)
|
|
*/
|
|
const LANGUAGE_PATTERNS = {
|
|
'te-reo-maori': /\b(kia ora|ngā mihi|whānau|kōrero|aroha|mana|taonga)\b/i,
|
|
'japanese': /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/,
|
|
'spanish': /\b(hola|gracias|por favor|señor|señora)\b/i,
|
|
'french': /\b(bonjour|merci|monsieur|madame)\b/i,
|
|
'german': /\b(guten tag|danke|herr|frau)\b/i
|
|
};
|
|
|
|
class AdaptiveCommunicationOrchestrator {
|
|
constructor() {
|
|
this.styles = COMMUNICATION_STYLES;
|
|
this.patronizingPatterns = PATRONIZING_PATTERNS;
|
|
this.languagePatterns = LANGUAGE_PATTERNS;
|
|
|
|
// Statistics tracking
|
|
this.stats = {
|
|
total_adaptations: 0,
|
|
by_style: {
|
|
FORMAL_ACADEMIC: 0,
|
|
CASUAL_DIRECT: 0,
|
|
MAORI_PROTOCOL: 0,
|
|
JAPANESE_FORMAL: 0,
|
|
PLAIN_LANGUAGE: 0
|
|
},
|
|
patronizing_terms_removed: 0,
|
|
languages_detected: {}
|
|
};
|
|
|
|
logger.info('AdaptiveCommunicationOrchestrator initialized');
|
|
}
|
|
|
|
/**
|
|
* Adapt communication to target audience style
|
|
* @param {String} message - The message to adapt
|
|
* @param {Object} context - Audience context
|
|
* @returns {String} Adapted message
|
|
*/
|
|
adaptCommunication(message, context = {}) {
|
|
try {
|
|
let adaptedMessage = message;
|
|
|
|
// 1. Detect input language (inst_032)
|
|
const detectedLanguage = this._detectLanguage(message);
|
|
if (detectedLanguage && detectedLanguage !== 'english') {
|
|
logger.info('Non-English language detected', { language: detectedLanguage });
|
|
// In production, would trigger translation workflow
|
|
}
|
|
|
|
// 2. Apply anti-patronizing filter (inst_030)
|
|
adaptedMessage = this._removePatronizingLanguage(adaptedMessage);
|
|
|
|
// 3. Adapt to target communication style (inst_029)
|
|
const targetStyle = context.audience || 'PLAIN_LANGUAGE';
|
|
adaptedMessage = this._adaptToStyle(adaptedMessage, targetStyle, context);
|
|
|
|
// 4. Apply regional/cultural adaptations (inst_031)
|
|
if (context.cultural_context) {
|
|
adaptedMessage = this._applyCulturalContext(adaptedMessage, context.cultural_context);
|
|
}
|
|
|
|
this.stats.total_adaptations++;
|
|
if (this.stats.by_style[targetStyle] !== undefined) {
|
|
this.stats.by_style[targetStyle]++;
|
|
}
|
|
|
|
return adaptedMessage;
|
|
|
|
} catch (error) {
|
|
logger.error('Communication adaptation error:', error);
|
|
// Fallback: return original message
|
|
return message;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if message passes pub test (inst_029)
|
|
* Would this sound awkward in casual Australian/NZ pub conversation?
|
|
* @param {String} message - The message to check
|
|
* @returns {Object} Pub test result
|
|
*/
|
|
pubTest(message) {
|
|
const awkwardIndicators = [
|
|
{ pattern: /\bhereby\b/gi, reason: 'Too formal/legal' },
|
|
{ pattern: /\bforthwith\b/gi, reason: 'Archaic formal' },
|
|
{ pattern: /\bnotwithstanding\b/gi, reason: 'Unnecessarily complex' },
|
|
{ pattern: /\bpursuant to\b/gi, reason: 'Overly legal' },
|
|
{ pattern: /\bin accordance with\b/gi, reason: 'Bureaucratic' },
|
|
{ pattern: /\bas per\b/gi, reason: 'Business jargon' }
|
|
];
|
|
|
|
const violations = [];
|
|
|
|
for (const indicator of awkwardIndicators) {
|
|
const matches = message.match(indicator.pattern);
|
|
if (matches) {
|
|
violations.push({
|
|
term: matches[0],
|
|
reason: indicator.reason,
|
|
suggestion: 'Use simpler, conversational language'
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
passes: violations.length === 0,
|
|
violations,
|
|
message: violations.length === 0
|
|
? 'Message passes pub test - sounds natural in casual conversation'
|
|
: 'Message would sound awkward in casual pub conversation'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detect communication style from incoming message
|
|
* @param {String} message - The message to analyze
|
|
* @returns {String} Detected style key
|
|
*/
|
|
detectStyle(message) {
|
|
const lowerMessage = message.toLowerCase();
|
|
|
|
// Check for Māori protocol indicators
|
|
if (this.languagePatterns['te-reo-maori'].test(message)) {
|
|
return 'MAORI_PROTOCOL';
|
|
}
|
|
|
|
// Check for formal academic indicators
|
|
const formalIndicators = ['furthermore', 'notwithstanding', 'pursuant to', 'hereby', 'therefore'];
|
|
const formalCount = formalIndicators.filter(term => lowerMessage.includes(term)).length;
|
|
if (formalCount >= 2) {
|
|
return 'FORMAL_ACADEMIC';
|
|
}
|
|
|
|
// Check for casual/direct indicators
|
|
const casualIndicators = ['right,', 'ok,', 'yeah,', 'fair?', 'reckon', 'mate'];
|
|
const casualCount = casualIndicators.filter(term => lowerMessage.includes(term)).length;
|
|
if (casualCount >= 1) {
|
|
return 'CASUAL_DIRECT';
|
|
}
|
|
|
|
// Default to plain language
|
|
return 'PLAIN_LANGUAGE';
|
|
}
|
|
|
|
/**
|
|
* Generate culturally-adapted greeting
|
|
* @param {String} recipientName - Name of recipient
|
|
* @param {Object} context - Cultural context
|
|
* @returns {String} Appropriate greeting
|
|
*/
|
|
generateGreeting(recipientName, context = {}) {
|
|
const style = context.communication_style || 'PLAIN_LANGUAGE';
|
|
|
|
switch (style) {
|
|
case 'MAORI_PROTOCOL':
|
|
return `Kia ora ${recipientName}`;
|
|
case 'JAPANESE_FORMAL':
|
|
return `${recipientName}様、いつもお世話になっております。`; // Formal Japanese greeting
|
|
case 'CASUAL_DIRECT':
|
|
return `Hi ${recipientName}`;
|
|
case 'FORMAL_ACADEMIC':
|
|
return `Dear ${recipientName},`;
|
|
default:
|
|
return `Hello ${recipientName}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Private helper methods
|
|
*/
|
|
|
|
_detectLanguage(message) {
|
|
for (const [language, pattern] of Object.entries(this.languagePatterns)) {
|
|
if (pattern.test(message)) {
|
|
// Track language detection
|
|
if (!this.stats.languages_detected[language]) {
|
|
this.stats.languages_detected[language] = 0;
|
|
}
|
|
this.stats.languages_detected[language]++;
|
|
|
|
return language;
|
|
}
|
|
}
|
|
return 'english'; // Default assumption
|
|
}
|
|
|
|
_removePatronizingLanguage(message) {
|
|
let cleaned = message;
|
|
let removedCount = 0;
|
|
|
|
for (const { pattern, reason } of this.patronizingPatterns) {
|
|
const beforeLength = cleaned.length;
|
|
cleaned = cleaned.replace(pattern, '');
|
|
const afterLength = cleaned.length;
|
|
|
|
if (beforeLength !== afterLength) {
|
|
removedCount++;
|
|
logger.debug('Removed patronizing term', { reason });
|
|
}
|
|
}
|
|
|
|
if (removedCount > 0) {
|
|
this.stats.patronizing_terms_removed += removedCount;
|
|
// Clean up extra spaces left by removals
|
|
cleaned = cleaned.replace(/\s{2,}/g, ' ').trim();
|
|
}
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
_adaptToStyle(message, styleKey, context) {
|
|
const style = this.styles[styleKey];
|
|
|
|
if (!style) {
|
|
logger.warn('Unknown communication style', { styleKey });
|
|
return message;
|
|
}
|
|
|
|
// Style-specific adaptations
|
|
switch (styleKey) {
|
|
case 'FORMAL_ACADEMIC':
|
|
return this._adaptToFormalAcademic(message, context);
|
|
case 'CASUAL_DIRECT':
|
|
return this._adaptToCasualDirect(message, context);
|
|
case 'MAORI_PROTOCOL':
|
|
return this._adaptToMaoriProtocol(message, context);
|
|
case 'JAPANESE_FORMAL':
|
|
return this._adaptToJapaneseFormal(message, context);
|
|
case 'PLAIN_LANGUAGE':
|
|
return this._adaptToPlainLanguage(message, context);
|
|
default:
|
|
return message;
|
|
}
|
|
}
|
|
|
|
_adaptToFormalAcademic(message, context) {
|
|
// Add formal register, hedge appropriately
|
|
// (In production, would use NLP transformation)
|
|
return message;
|
|
}
|
|
|
|
_adaptToCasualDirect(message, context) {
|
|
// Remove unnecessary formality, make direct
|
|
let adapted = message;
|
|
|
|
// Replace formal phrases with casual equivalents
|
|
const replacements = [
|
|
{ formal: /I would like to inform you that/gi, casual: 'Just so you know,' },
|
|
{ formal: /It is important to note that/gi, casual: 'Key thing:' },
|
|
{ formal: /We have determined that/gi, casual: 'We figured' },
|
|
{ formal: /In accordance with/gi, casual: 'Following' }
|
|
];
|
|
|
|
for (const { formal, casual } of replacements) {
|
|
adapted = adapted.replace(formal, casual);
|
|
}
|
|
|
|
return adapted;
|
|
}
|
|
|
|
_adaptToMaoriProtocol(message, context) {
|
|
// Add appropriate te reo Māori protocol elements
|
|
// (In production, would consult with Māori language experts)
|
|
return message;
|
|
}
|
|
|
|
_adaptToJapaneseFormal(message, context) {
|
|
// Add appropriate Japanese formal register elements
|
|
// (In production, would use Japanese language processing)
|
|
return message;
|
|
}
|
|
|
|
_adaptToPlainLanguage(message, context) {
|
|
// Simplify jargon, use clear language
|
|
let adapted = message;
|
|
|
|
// Replace jargon with plain equivalents
|
|
const jargonReplacements = [
|
|
{ jargon: /utilize/gi, plain: 'use' },
|
|
{ jargon: /facilitate/gi, plain: 'help' },
|
|
{ jargon: /implement/gi, plain: 'do' },
|
|
{ jargon: /endeavor/gi, plain: 'try' }
|
|
];
|
|
|
|
for (const { jargon, plain } of jargonReplacements) {
|
|
adapted = adapted.replace(jargon, plain);
|
|
}
|
|
|
|
return adapted;
|
|
}
|
|
|
|
_applyCulturalContext(message, culturalContext) {
|
|
// Apply cultural adaptations based on context
|
|
// (In production, would be much more sophisticated)
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Get communication adaptation statistics
|
|
* @returns {Object} Statistics object
|
|
*/
|
|
getStats() {
|
|
return {
|
|
...this.stats,
|
|
timestamp: new Date()
|
|
};
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
const orchestrator = new AdaptiveCommunicationOrchestrator();
|
|
|
|
// Export both singleton (default) and class (for testing)
|
|
module.exports = orchestrator;
|
|
module.exports.AdaptiveCommunicationOrchestrator = AdaptiveCommunicationOrchestrator;
|