tractatus/src/services/AdaptiveCommunicationOrchestrator.service.js
TheFlow cb7d84e639 feat(services): add 6th core service - value pluralism deliberation
- 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>
2025-10-12 16:35:15 +13:00

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;