/* * 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;