File size: 16,302 Bytes
2c5ae19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Optional

from .enums import Emotion, Intent, RecoveryState
from .states import UserState
from .llm_adapter import call_llm


@dataclass
class DimensionOutput:
    message: str
    intent: Intent
    emotion: Emotion
    confidence: float
    meta: Dict[str, Any]


class LogicalClarity:
    """
    Logical Clarity dimension: focuses on structure, patterns, and clear reasoning.
    """
    
    def __init__(self):
        self.confidence_thresholds = {
            RecoveryState.CRISIS: 0.5,
            RecoveryState.AWARENESS: 0.6,
            RecoveryState.HONESTY: 0.7,
            RecoveryState.RECONSTRUCTION: 0.8,
            RecoveryState.INTEGRATION: 0.8,
            RecoveryState.PURPOSE: 0.9
        }
    
    def analyze(self, text: str, state: RecoveryState) -> DimensionOutput:
        system = (
            "You are the Logical Clarity module of BLUX-cA, a Clarity Agent.\n"
            "Your purpose is to analyze situations with clear, structured thinking.\n"
            "Core principles:\n"
            "1. Identify the central question or problem\n"
            "2. Separate facts from assumptions\n"
            "3. Recognize patterns and contradictions\n"
            "4. Propose logical next steps\n"
            "5. Maintain intellectual honesty without emotional manipulation\n"
            "6. Never claim certainty where there is none\n"
            "7. Respect the user's autonomy in decision-making\n\n"
            "Your responses should be clear, structured, and focused on actionable understanding."
        )
        
        user = (
            f"USER INPUT: {text}\n\n"
            f"CURRENT RECOVERY STATE: {state.value}\n"
            f"USER STATE CONTEXT: The user is in {state.value} phase of their clarity journey.\n\n"
            "Please provide a logical clarity analysis that:\n"
            "1. States the core question or problem succinctly\n"
            "2. Identifies what is known vs. unknown\n"
            "3. Points out any patterns or contradictions (if present)\n"
            "4. Suggests one logical next step for gaining clarity\n"
            "5. Acknowledges the limits of what can be known from this input\n\n"
            "Keep your response concise (2-4 sentences max).\n"
            "Use clear, precise language without emotional coloring."
        )
        
        try:
            msg = call_llm(system, user)
        except (NotImplementedError, Exception) as e:
            msg = self._get_fallback_response(text, state, str(e))
        
        confidence = self._calculate_confidence(text, state, msg)
        
        return DimensionOutput(
            message=msg,
            intent=Intent.ANALYSIS,
            emotion=Emotion.FOCUSED,
            confidence=confidence,
            meta={
                "source": "logical",
                "state": state.value,
                "input_length": len(text),
                "fallback_used": "call_llm" not in msg
            },
        )
    
    def _get_fallback_response(self, text: str, state: RecoveryState, error: str) -> str:
        """Generate appropriate fallback responses based on state."""
        fallbacks = {
            RecoveryState.CRISIS: "The immediate priority is identifying the most pressing concern. What feels most urgent right now?",
            RecoveryState.AWARENESS: "Let's clarify what you're observing. What stands out as the main question or pattern?",
            RecoveryState.HONESTY: "Honest assessment begins with clear questions. What are you trying to understand?",
            RecoveryState.RECONSTRUCTION: "Logical reconstruction requires clear steps. What's the first piece that needs attention?",
            RecoveryState.INTEGRATION: "Integration benefits from clear structure. How do these pieces fit together?",
            RecoveryState.PURPOSE: "Purpose emerges from clear understanding. What's the central question driving this?"
        }
        
        if len(text.split()) < 3:
            return "I need a bit more to provide a logical analysis. Could you say more about what you're thinking?"
        
        return fallbacks.get(state, 
            "Let's break this down logically. What's the core question you're trying to answer?")
    
    def _calculate_confidence(self, text: str, state: RecoveryState, response: str) -> float:
        """Calculate confidence score based on input quality and state alignment."""
        base_confidence = 0.7
        
        # Adjust based on input quality
        word_count = len(text.split())
        if word_count < 3:
            base_confidence -= 0.3
        elif word_count > 20:
            base_confidence += 0.1
        
        # Adjust based on state appropriateness
        state_keywords = {
            RecoveryState.CRISIS: ['urgent', 'priority', 'stabilize', 'immediate'],
            RecoveryState.AWARENESS: ['observe', 'notice', 'pattern', 'aware'],
            RecoveryState.HONESTY: ['honest', 'clear', 'assessment', 'truth'],
            RecoveryState.RECONSTRUCTION: ['step', 'build', 'reconstruct', 'plan'],
            RecoveryState.INTEGRATION: ['integrate', 'connect', 'whole', 'synthesis'],
            RecoveryState.PURPOSE: ['purpose', 'meaning', 'direction', 'why']
        }
        
        keywords = state_keywords.get(state, [])
        keyword_matches = sum(1 for kw in keywords if kw in response.lower())
        if keyword_matches > 0:
            base_confidence += 0.1
        
        # Ensure within bounds
        return max(0.3, min(0.95, base_confidence))


class EmotionalClarity:
    """
    Emotional Clarity dimension: focuses on emotional awareness, validation, and grounding.
    """
    
    def __init__(self):
        self.emotion_mapping = {
            RecoveryState.CRISIS: Emotion.CALM,
            RecoveryState.AWARENESS: Emotion.CURIOUS,
            RecoveryState.HONESTY: Emotion.REFLECTIVE,
            RecoveryState.RECONSTRUCTION: Emotion.HOPEFUL,
            RecoveryState.INTEGRATION: Emotion.PEACEFUL,
            RecoveryState.PURPOSE: Emotion.CONFIDENT
        }
    
    def analyze(self, text: str, state: RecoveryState) -> DimensionOutput:
        system = (
            "You are the Emotional Clarity module of BLUX-cA, a Clarity Agent.\n"
            "Your purpose is to recognize, validate, and help process emotions.\n"
            "Core principles:\n"
            "1. Name emotions without exaggeration or minimization\n"
            "2. Offer grounded validation, not empty reassurance\n"
            "3. Help users sit with difficult emotions, not avoid them\n"
            "4. Never manipulate emotions or create dependency\n"
            "5. Respect emotional boundaries and pacing\n"
            "6. Connect emotions to needs and values when appropriate\n"
            "7. Maintain compassionate neutrality (not detached, not fused)\n\n"
            "Your responses should be emotionally attuned, validating, and grounding."
        )
        
        user = (
            f"USER INPUT: {text}\n\n"
            f"CURRENT RECOVERY STATE: {state.value}\n"
            f"STATE CONTEXT: The user is working through {state.value.lower()} phase.\n\n"
            "Please provide an emotional clarity response that:\n"
            "1. Names 1-2 primary emotions you sense (use nuanced language)\n"
            "2. Validates the emotional experience without judgment\n"
            "3. Offers one brief grounding reflection\n"
            "4. Avoids advice-giving unless explicitly requested\n"
            "5. Respects the user's emotional boundaries\n\n"
            "Keep your response concise (2-3 sentences max).\n"
            "Use emotionally intelligent but not flowery language."
        )
        
        try:
            msg = call_llm(system, user)
        except (NotImplementedError, Exception) as e:
            msg = self._get_fallback_response(text, state, str(e))
        
        confidence = self._calculate_confidence(text, state, msg)
        emotion = self.emotion_mapping.get(state, Emotion.REFLECTIVE)
        
        # Adjust emotion based on text content
        if any(word in text.lower() for word in ['urgent', 'emergency', 'panic', 'overwhelmed']):
            emotion = Emotion.CALM
        elif any(word in text.lower() for word in ['angry', 'frustrated', 'annoyed', 'irritated']):
            emotion = Emotion.STEADY
        
        return DimensionOutput(
            message=msg,
            intent=Intent.GROUNDING,
            emotion=emotion,
            confidence=confidence,
            meta={
                "source": "emotional",
                "state": state.value,
                "detected_emotion": emotion.value,
                "validation_present": "understand" in msg.lower() or "hear" in msg.lower()
            },
        )
    
    def _get_fallback_response(self, text: str, state: RecoveryState, error: str) -> str:
        """Generate appropriate fallback emotional responses."""
        fallbacks = {
            RecoveryState.CRISIS: "I can hear the intensity in this. Let's take a breath and ground for a moment.",
            RecoveryState.AWARENESS: "There's feeling in what you're sharing. Let's notice what's present.",
            RecoveryState.HONESTY: "This feels honest and real. Sit with what you're feeling.",
            RecoveryState.RECONSTRUCTION: "There's emotion in this rebuilding. Honor what you feel.",
            RecoveryState.INTEGRATION: "I sense emotional integration happening. Allow space for it.",
            RecoveryState.PURPOSE: "There's feeling in this purpose. Notice what it brings up."
        }
        
        if len(text.split()) < 2:
            return "I'm here to listen. What are you feeling?"
        
        return fallbacks.get(state, 
            "I hear real feeling in this. Let's acknowledge what's present.")
    
    def _calculate_confidence(self, text: str, state: RecoveryState, response: str) -> float:
        """Calculate confidence for emotional analysis."""
        base_confidence = 0.75
        
        # Adjust based on emotional content indicators
        emotion_words = ['feel', 'felt', 'feeling', 'emotion', 'emotional', 
                        'happy', 'sad', 'angry', 'scared', 'anxious', 
                        'excited', 'overwhelmed', 'calm', 'peaceful']
        
        emotion_count = sum(1 for word in emotion_words if word in text.lower())
        if emotion_count == 0:
            base_confidence -= 0.2
        elif emotion_count >= 2:
            base_confidence += 0.15
        
        # Adjust based on validation quality
        if 'understand' in response.lower() or 'hear' in response.lower():
            base_confidence += 0.1
        
        # Crisis state requires higher certainty
        if state == RecoveryState.CRISIS:
            base_confidence = min(base_confidence, 0.8)  # Don't overconfident in crisis
        
        return max(0.4, min(0.95, base_confidence))


class ShadowClarity:
    """
    Shadow Clarity dimension: focuses on patterns, contradictions, and unexamined aspects.
    """
    
    def __init__(self):
        self.state_sensitivity = {
            RecoveryState.CRISIS: 0.3,  # Very gentle in crisis
            RecoveryState.AWARENESS: 0.5,
            RecoveryState.HONESTY: 0.8,
            RecoveryState.RECONSTRUCTION: 0.7,
            RecoveryState.INTEGRATION: 0.6,
            RecoveryState.PURPOSE: 0.4
        }
    
    def analyze(self, text: str, state: RecoveryState) -> DimensionOutput:
        sensitivity = self.state_sensitivity.get(state, 0.5)
        
        system = (
            f"You are the Shadow Clarity module of BLUX-cA, a Clarity Agent.\n"
            f"Your purpose is to gently illuminate patterns and contradictions.\n"
            f"Current sensitivity setting: {sensitivity} (1.0 = most direct, 0.0 = most gentle)\n\n"
            "Core principles:\n"
            "1. Observe patterns, don't diagnose\n"
            "2. Name contradictions as possibilities, not truths\n"
            "3. Invite curiosity, not shame or defensiveness\n"
            "4. Respect the user's readiness to see difficult things\n"
            "5. Never confront or force awareness\n"
            "6. Frame insights as 'I notice...' not 'You are...'\n"
            "7. Balance honesty with compassion\n\n"
            "Your responses should be gentle, curious, and invitation-focused."
        )
        
        user = (
            f"USER INPUT: {text}\n\n"
            f"CURRENT RECOVERY STATE: {state.value}\n"
            f"SENSITIVITY LEVEL: {sensitivity}\n\n"
            "Please provide a shadow clarity observation that:\n"
            "1. Gently points to ONE pattern or contradiction (if present)\n"
            "2. Frames it as an observation or question\n"
            "3. Leaves space for the user to respond or not\n"
            "4. Maintains a non-judgmental, curious tone\n"
            "5. Respects the sensitivity level in your phrasing\n\n"
            "Keep your response very concise (1-2 sentences max).\n"
            "Use language that invites reflection, not defense."
        )
        
        try:
            msg = call_llm(system, user)
        except (NotImplementedError, Exception) as e:
            msg = self._get_fallback_response(text, state, str(e), sensitivity)
        
        confidence = self._calculate_confidence(text, state, msg, sensitivity)
        
        return DimensionOutput(
            message=msg,
            intent=Intent.REFLECTION,
            emotion=Emotion.CURIOUS,
            confidence=confidence,
            meta={
                "source": "shadow",
                "state": state.value,
                "sensitivity": sensitivity,
                "approach": "invitational" if "?" in msg else "observational"
            },
        )
    
    def _get_fallback_response(self, text: str, state: RecoveryState, error: str, sensitivity: float) -> str:
        """Generate appropriate shadow fallback responses."""
        if sensitivity < 0.4 or state == RecoveryState.CRISIS:
            return "There may be things here worth exploring gently, when you're ready."
        
        fallbacks = {
            RecoveryState.AWARENESS: "I notice something taking shape here. Want to explore it?",
            RecoveryState.HONESTY: "There's a pattern emerging in this honesty. Notice it with me?",
            RecoveryState.RECONSTRUCTION: "In this rebuilding, I see a familiar shape. Shall we look?",
            RecoveryState.INTEGRATION: "Integration often reveals patterns. This one seems significant.",
            RecoveryState.PURPOSE: "Purpose clarifies patterns. This one feels meaningful."
        }
        
        if len(text.split()) < 4:
            return "There's more beneath the surface here, when you want to look."
        
        return fallbacks.get(state, 
            "There's a pattern here worth noticing, if you're curious.")
    
    def _calculate_confidence(self, text: str, state: RecoveryState, response: str, sensitivity: float) -> float:
        """Calculate confidence for shadow analysis."""
        base_confidence = 0.6 * sensitivity  # Scale by sensitivity
        
        # Adjust based on pattern indicators
        pattern_words = ['always', 'never', 'every time', 'pattern', 
                        'again', 'same', 'typical', 'usual']
        
        pattern_count = sum(1 for word in pattern_words if word in text.lower())
        if pattern_count > 0:
            base_confidence += 0.2
        
        # Adjust based on contradiction indicators
        contradiction_words = ['but', 'however', 'although', 'yet',
                              'even though', 'despite', 'paradox']
        
        contradiction_count = sum(1 for word in contradiction_words if word in text.lower())
        if contradiction_count > 0:
            base_confidence += 0.15
        
        # Lower confidence for very short inputs
        if len(text.split()) < 3:
            base_confidence *= 0.7
        
        # Ensure confidence respects sensitivity
        base_confidence = min(base_confidence, sensitivity)
        
        return max(0.2, min(0.8, base_confidence))