ghostMalone / utils /needs_lexicon.py
francischung222's picture
Deploy Ghost Malone
ca65aec
raw
history blame
25.7 kB
"""
Implicit Needs Detection for Ghost Malone
Maps emotional patterns + context keywords to underlying psychological needs.
STRATEGIC FRAMEWORK:
Based on Self-Determination Theory (Ryan & Deci) + Maslow's Hierarchy
5 Core Needs:
1. AUTONOMY - Control, choice, self-direction
2. CONNECTION - Belonging, relationships, being seen
3. SECURITY - Safety, clarity, predictability
4. REST - Recovery, boundaries, capacity
5. RECOGNITION - Validation, competence, mattering
Context Detection Strategy:
- BARRIERS: What's blocking the need? (can't, won't let, prevented)
- DEFICITS: What's missing? (nobody, nothing, no one)
- THREATS: What's at risk? (unsafe, unstable, unpredictable)
- OVERLOAD: What's too much? (overwhelmed, exhausted, too many)
- DISMISSAL: What's being ignored? (dismissed, invisible, unappreciated)
Constitutional AI Principle: Respects human dignity by treating emotions as signals
of legitimate needs, not surface-level problems.
"""
from typing import List, Dict
import re
# STRATEGIC CONTEXT PATTERNS - organized by psychological dimension
CONTEXT_PATTERNS = {
# ===== AUTONOMY CONTEXTS =====
# Barriers to self-direction
"blocked": r"(can't|won't let|prevented|stopped|blocked|restricted|controlled|controlling me|forced|have to|must|don't give|won't give|not letting|not allowed|doesn't give|pressured to|feel pressured)",
"powerless": r"(powerless|helpless|trapped|stuck|no choice|no control|out of.{0,15}control|feels.{0,15}out of control|don't have the tools|not much opportunity|no say)",
"dismissed": r"(ignored|dismissed|disregarded|invalidated|didn't listen|brushed off|shut down|talked over|doesn't consider|don't consider|nobody trusts|don't trust|people ignore|ignore me|cut me off|looked.{0,15}through me|right through me)",
"underutilized": r"(know i can|i can do|capable of|able to do|qualified|skills|talent|waste|potential|not enough challenge|bored|underused|so boring|below my ability|way below|could do.{0,15}more|living up to)",
"micromanaged": r"(micromanag|hovering|checking|breathing down|second-guess|don't trust me|checks every|watching every)",
# ===== CONNECTION CONTEXTS =====
# Deficits in belonging/relationships
"isolated": r"(alone|lonely|isolated|by myself|left out|excluded|without me|on my own|don't have anyone|no one to talk|invisible to|no one there|nobody there)",
"rejected": r"(rejected|abandoned|left me|left behind|they left|ghosted|dumped|unwanted|unloved|won't talk|ignoring|avoiding me|cut off|don't.{0,15}respond|doesn't.{0,15}respond|won't.{0,15}respond|not.{0,15}respond(?:ed|ing)?|hasn't.{0,15}respond|didn't.{0,15}respond|not.{0,15}reply|not.{0,15}repl(?:ied|ying)|not.{0,15}answer(?:ed|ing)?|avoid me|seem to avoid|wasn't invited|didn't invite|unanswered|go unanswered|make excuses|not texting.{0,15}back|won't text)",
"misunderstood": r"(misunderstood|don't get it|don't understand|not listening|not hearing|missing the point|doesn't.{0,15}listen|won't.{0,15}listen|different language|speaking a different|doesn't.{0,15}hear|misinterpret|nobody.{0,15}understands|nobody.{0,15}knows|nobody.{0,15}interested|doesn't.{0,15}share|doesn't.{0,15}talk|won't.{0,15}talk|doesn't.{0,15}open up)",
"singled_out": r"(everyone else|everybody else|talks to everyone|except me|only me|just me|do things without|left behind|don't invite|didn't invite|don't call me|leaves me out|not included|don't pass|doesn't pass|won't pass|outsider|feel like.{0,15}outsider|make plans without|plans without including|always the one reaching out|don't include)",
"disconnected": r"(disconnected|distant|drift|growing apart|not close|lost touch|pulling away|pull away|hard to.{0,15}connection|hard to connect|can't connect)",
# ===== SECURITY CONTEXTS =====
# Threats to safety/clarity
"uncertain": r"(don't know|uncertain|unsure|unclear|confused|no idea|what if|can't tell|ambiguous|what to do|where do i go|what next|what do i do|can't predict|unpredictable|what's going to happen|what will happen|going to happen)",
"unstable": r"(unstable|unpredictable|chaotic|all over|everything's changing|up in the air|falling apart|like always|never on time|unreliable|keeps changing|constantly changing|shifts again|everything shifts|stays stable|nothing.{0,15}stable|shifting ground|constantly shifting)",
"threatened": r"(threatened|unsafe|danger|risk|scared|scary|vulnerable|exposed|at risk|pain again|go through that|rot in hell|worried|worrying|something bad|threats|not protected|not secure|don't feel.{0,15}secure)",
"loss": r"(losing|lost|losing grip|slipping away|falling apart|coming undone)",
# ===== REST CONTEXTS =====
# Overload on capacity
"overwhelmed": r"(overwhelmed|too much|can't handle|drowning|swamped|buried|crowded|crowd|too many people|overstimulat|too much work|coming at me|at me all at once)",
"exhausted": r"(exhausted|drained|burnt out|can't anymore|running on empty|wiped|spent|depleted|freaking tired|so tired|running on fumes|on fumes|no energy|don't have.{0,15}energy)",
"overextended": r"(overextended|overstretched|spread thin|doing too much|no time|juggling|can't keep up|keep up with)",
"boundary_violated": r"(always asking|never stops|can't say no|taking advantage|demanding|intrusive|available 24|expect.{0,15}available|need space|won't give.{0,15}space|boundaries.{0,15}ignored|kept.{0,15}insisting|kept.{0,15}texting|kept.{0,15}calling|i said no|asked for.{0,15}boundaries)",
# ===== RECOGNITION CONTEXTS =====
# Dismissal of value/competence
"unappreciated": r"(unappreciated|taken for granted|not valued|not recognized|invisible|thankless|nobody appreciates|no one appreciates|zero recognition|get.{0,15}recognition|work.{0,15}hard|not appreciated|don't feel appreciated|no.{0,15}recognition|no sense of recognition)",
"inadequate": r"(not good enough|inadequate|failing|not enough|disappointing|falling short|incompetent|doubt.{0,15}have what it takes|don't feel capable|never enough|nothing.{0,15}enough)",
"overlooked": r"(overlooked|passed over|ignored|not noticed|nobody notices|no one notices|credit|someone else|didn't acknowledge)",
"criticized": r"(criticized|judged|attacked|picked apart|nothing right|always wrong|nitpick|point out.{0,15}mistake|only.{0,15}mistake|compared.{0,15}unfavorably|compared to others|offended|offensive|rude|disrespected)",
}
# Needs taxonomy (based on Maslow + Self-Determination Theory)
NEEDS = {
"autonomy": {
"label": "Autonomy/Control",
"icon": "⚖️",
"description": "Need for self-direction and agency over one's life",
"interventions": [
"Set a boundary",
"Say 'no'",
"Take back decision-making power",
],
},
"connection": {
"label": "Connection/Belonging",
"icon": "🗣️",
"description": "Need for meaningful relationships and social bonds",
"interventions": [
"Reach out to someone you trust",
"Share what you're feeling",
"Ask for support",
],
},
"security": {
"label": "Security/Clarity",
"icon": "🛡️",
"description": "Need for safety, predictability, and understanding",
"interventions": [
"Get more information",
"Make a plan",
"Identify what you can control",
],
},
"rest": {
"label": "Rest/Boundaries",
"icon": "🛌",
"description": "Need for recuperation and limits on demands",
"interventions": [
"Take a break",
"Delegate something",
"Give yourself permission to rest",
],
},
"recognition": {
"label": "Recognition/Validation",
"icon": "✨",
"description": "Need to be seen, valued, and acknowledged",
"interventions": [
"Acknowledge your own effort",
"Ask for feedback",
"Celebrate small wins",
],
},
}
# STRATEGIC NEED INFERENCE RULES
# Organized by primary need, with emotion+context combinations
# Using ONLY the 7 actual detected emotions: happy, sad, angry, anxious, tired, love, fear
NEED_RULES = [
# ========== AUTONOMY RULES ==========
# Primary emotion: Anger (blocked goals/control)
{
"emotions": ["angry"],
"contexts": ["blocked", "powerless", "dismissed", "micromanaged"],
"need": "autonomy",
"confidence_base": 0.80,
"reasoning": "Anger signals blocked goals or loss of control",
},
{
"emotions": ["angry", "sad"],
"contexts": ["powerless", "trapped", "stuck"],
"need": "autonomy",
"confidence_base": 0.85,
"reasoning": "Anger + sadness at powerlessness indicates autonomy deficit",
},
{
"emotions": ["angry"],
"contexts": ["underutilized", "dismissed"],
"need": "autonomy",
"confidence_base": 0.75,
"reasoning": "Anger at not being allowed to use your capabilities",
},
# HIGH PRIORITY: Underutilized should map to autonomy strongly
{
"emotions": ["sad", "angry", "anxious", "tired"],
"contexts": ["underutilized"],
"need": "autonomy",
"confidence_base": 0.88,
"reasoning": "Skills/potential being wasted is autonomy issue",
},
{
"emotions": ["sad"],
"contexts": ["powerless", "trapped"],
"need": "autonomy",
"confidence_base": 0.70,
"reasoning": "Sadness from lack of control",
},
{
"emotions": ["sad"],
"contexts": ["blocked", "micromanaged", "dismissed"],
"need": "autonomy",
"confidence_base": 0.75,
"reasoning": "Sadness from being controlled or blocked",
},
{
"emotions": ["tired"],
"contexts": ["blocked", "powerless", "micromanaged"],
"need": "autonomy",
"confidence_base": 0.65,
"reasoning": "Fatigue from lack of autonomy",
},
{
"emotions": ["anxious"],
"contexts": ["powerless", "blocked"],
"need": "autonomy",
"confidence_base": 0.70,
"reasoning": "Anxiety from lack of control",
},
# ========== CONNECTION RULES ==========
# Primary emotion: Sadness (relationship deficits)
# HIGH PRIORITY: "Invisible to people" is connection, not recognition
{
"emotions": ["sad", "angry", "anxious"],
"contexts": ["isolated"],
"need": "connection",
"confidence_base": 0.92,
"reasoning": "Feeling invisible or isolated from people indicates connection deficit",
},
{
"emotions": ["sad"],
"contexts": ["isolated", "rejected", "misunderstood", "disconnected"],
"need": "connection",
"confidence_base": 0.85,
"reasoning": "Sadness with isolation indicates connection needs",
},
{
"emotions": ["sad"],
"contexts": ["singled_out"],
"need": "connection",
"confidence_base": 0.90,
"reasoning": "Being excluded from the group",
},
# Secondary emotion: Anger (rejection/exclusion)
{
"emotions": ["angry"],
"contexts": ["rejected", "singled_out", "dismissed"],
"need": "connection",
"confidence_base": 0.75,
"reasoning": "Anger from rejection signals need for belonging",
},
{
"emotions": ["angry"],
"contexts": ["misunderstood", "disconnected"],
"need": "connection",
"confidence_base": 0.75,
"reasoning": "Anger from being misunderstood or disconnected",
},
{
"emotions": ["angry"],
"contexts": ["isolated"],
"need": "connection",
"confidence_base": 0.70,
"reasoning": "Anger at being isolated",
},
{
"emotions": ["fear"],
"contexts": ["rejected", "abandoned"],
"need": "connection",
"confidence_base": 0.75,
"reasoning": "Fear of abandonment signals connection needs",
},
{
"emotions": ["anxious"],
"contexts": ["isolated", "rejected", "misunderstood"],
"need": "connection",
"confidence_base": 0.70,
"reasoning": "Anxiety from social isolation or rejection",
},
{
"emotions": ["tired"],
"contexts": ["isolated", "disconnected"],
"need": "connection",
"confidence_base": 0.60,
"reasoning": "Fatigue from lack of social connection",
},
# ========== SECURITY RULES ==========
# Primary emotions: Anxiety/Fear (threat/uncertainty)
{
"emotions": ["anxious"],
"contexts": ["uncertain", "unstable", "threatened", "loss"],
"need": "security",
"confidence_base": 0.85,
"reasoning": "Anxiety with uncertainty signals need for clarity/safety",
},
{
"emotions": ["fear"],
"contexts": ["threatened", "unsafe", "vulnerable"],
"need": "security",
"confidence_base": 0.90,
"reasoning": "Fear indicates immediate security/safety needs",
},
{
"emotions": ["anxious"],
"contexts": ["uncertain", "unstable"],
"need": "security",
"confidence_base": 0.80,
"reasoning": "Worry about unpredictability or lack of clarity",
},
# HIGH PRIORITY: "Can't predict" with blocked is still security (uncertainty primary)
{
"emotions": ["anxious", "fear", "sad"],
"contexts": ["uncertain"],
"need": "security",
"confidence_base": 0.94,
"reasoning": "Uncertainty and unpredictability are core security concerns",
},
# HIGH PRIORITY: "Things keep changing" with overextended is security (instability primary)
{
"emotions": ["anxious", "tired", "sad"],
"contexts": ["unstable"],
"need": "security",
"confidence_base": 0.94,
"reasoning": "Constant change and instability are security issues",
},
{
"emotions": ["sad", "anxious"],
"contexts": ["loss"],
"need": "security",
"confidence_base": 0.75,
"reasoning": "Grief + anxiety about losing stability",
},
{
"emotions": ["angry"],
"contexts": ["uncertain"],
"need": "security",
"confidence_base": 0.65,
"reasoning": "Anger from lack of clarity",
},
{
"emotions": ["sad"],
"contexts": ["uncertain", "unstable", "loss"],
"need": "security",
"confidence_base": 0.75,
"reasoning": "Sadness from instability or uncertainty",
},
{
"emotions": ["tired"],
"contexts": ["unstable", "threatened"],
"need": "security",
"confidence_base": 0.65,
"reasoning": "Exhaustion from constant instability",
},
# ========== REST RULES ==========
# Primary emotion: Tired (capacity overload)
# HIGH PRIORITY: "Can't say no", "need space", "boundaries ignored" → rest (not autonomy)
{
"emotions": ["sad", "angry", "anxious", "tired"],
"contexts": ["boundary_violated"],
"need": "rest",
"confidence_base": 0.95,
"reasoning": "Boundary violations primarily indicate need for rest/space, not control",
},
{
"emotions": ["tired"],
"contexts": ["overwhelmed", "exhausted", "overextended"],
"need": "rest",
"confidence_base": 0.85,
"reasoning": "Exhaustion + overwhelm indicates need for rest/boundaries",
},
{
"emotions": ["tired"],
"contexts": ["exhausted"],
"need": "rest",
"confidence_base": 0.95,
"reasoning": "Explicit exhaustion is clearest rest signal",
},
{
"emotions": ["angry"],
"contexts": ["exhausted", "overwhelmed"],
"need": "rest",
"confidence_base": 0.75,
"reasoning": "Anger from being exhausted or overwhelmed",
},
{
"emotions": ["anxious", "tired"],
"contexts": ["overwhelmed", "overextended"],
"need": "rest",
"confidence_base": 0.80,
"reasoning": "Anxiety + tiredness suggests overextension",
},
{
"emotions": ["anxious"],
"contexts": ["overwhelmed", "crowded"],
"need": "rest",
"confidence_base": 0.70,
"reasoning": "Sensory/social overwhelm signals need for space",
},
{
"emotions": ["angry", "tired"],
"contexts": ["boundary_violated", "overextended"],
"need": "rest",
"confidence_base": 0.75,
"reasoning": "Anger and fatigue from violated boundaries",
},
# HIGH PRIORITY: Boundary violations should map to rest when not autonomy-blocking
{
"emotions": ["sad", "angry", "anxious", "tired"],
"contexts": ["boundary_violated"],
"need": "rest",
"confidence_base": 0.82,
"reasoning": "Boundary violations primarily indicate need for rest/space",
},
{
"emotions": ["sad"],
"contexts": ["overwhelmed", "exhausted", "overextended"],
"need": "rest",
"confidence_base": 0.70,
"reasoning": "Sadness from depletion and overwhelm",
},
{
"emotions": ["fear"],
"contexts": ["overwhelmed", "boundary_violated"],
"need": "rest",
"confidence_base": 0.65,
"reasoning": "Fear from being overwhelmed or boundaries violated",
},
# ========== RECOGNITION RULES ==========
# Primary emotions: Sad/Angry (value not seen)
# HIGH PRIORITY: "Nobody notices/appreciates" is recognition, not connection
{
"emotions": ["sad", "angry", "anxious", "tired"],
"contexts": ["overlooked", "unappreciated"],
"need": "recognition",
"confidence_base": 0.90,
"reasoning": "Being overlooked or unappreciated is primarily about recognition/validation",
},
# HIGH PRIORITY: "Don't feel capable" is recognition (inadequate), not autonomy
{
"emotions": ["sad", "anxious", "fear"],
"contexts": ["inadequate"],
"need": "recognition",
"confidence_base": 0.92,
"reasoning": "Self-doubt about capability is core recognition/validation issue",
},
{
"emotions": ["sad"],
"contexts": ["unappreciated"],
"need": "recognition",
"confidence_base": 0.87,
"reasoning": "Sadness from not being appreciated or valued",
},
{
"emotions": ["sad", "angry"],
"contexts": ["unappreciated", "inadequate", "overlooked"],
"need": "recognition",
"confidence_base": 0.75,
"reasoning": "Feeling unseen or inadequate signals need for validation",
},
{
"emotions": ["angry"],
"contexts": ["underutilized", "unappreciated", "overlooked"],
"need": "recognition",
"confidence_base": 0.80,
"reasoning": "Your capabilities aren't being seen or valued",
},
{
"emotions": ["sad"],
"contexts": ["inadequate"],
"need": "recognition",
"confidence_base": 0.85,
"reasoning": "Self-doubt about competence",
},
{
"emotions": ["sad"],
"contexts": ["criticized"],
"need": "recognition",
"confidence_base": 0.70,
"reasoning": "Criticism without acknowledgment damages sense of value",
},
{
"emotions": ["angry"],
"contexts": ["overlooked"],
"need": "recognition",
"confidence_base": 0.75,
"reasoning": "Anger at not being seen or credited",
},
{
"emotions": ["angry"],
"contexts": ["criticized"],
"need": "recognition",
"confidence_base": 0.75,
"reasoning": "Anger at being criticized, offended, or disrespected",
},
{
"emotions": ["anxious"],
"contexts": ["inadequate", "criticized"],
"need": "recognition",
"confidence_base": 0.75,
"reasoning": "Anxiety from self-doubt or criticism",
},
{
"emotions": ["tired"],
"contexts": ["unappreciated", "overlooked"],
"need": "recognition",
"confidence_base": 0.65,
"reasoning": "Fatigue from lack of appreciation",
},
{
"emotions": ["fear"],
"contexts": ["inadequate", "criticized"],
"need": "recognition",
"confidence_base": 0.70,
"reasoning": "Fear of failure or judgment",
},
]
def detect_context(text: str) -> Dict[str, bool]:
"""Detect which situational contexts are present in the text."""
text_lower = text.lower()
detected = {}
for context_name, pattern in CONTEXT_PATTERNS.items():
detected[context_name] = bool(re.search(pattern, text_lower))
return detected
def infer_needs(
emotion_labels: List[str], text: str, valence: float = 0.0, arousal: float = 0.5
) -> List[Dict]:
"""
Infer psychological needs from emotions + text context.
Args:
emotion_labels: Primary emotions detected (e.g., ["angry", "sad"])
text: User's message text
valence: Emotional valence (-1 to 1)
arousal: Emotional arousal (0 to 1)
Returns:
List of inferred needs with confidence scores:
[
{
"need": "autonomy",
"label": "Autonomy/Control",
"icon": "⚖️",
"confidence": 0.82,
"reasoning": "Anger often signals blocked goals...",
"interventions": ["Set a boundary", ...]
},
...
]
"""
# Detect situational contexts
contexts = detect_context(text)
active_contexts = [ctx for ctx, present in contexts.items() if present]
# Match rules
inferred = []
seen_needs = set()
for rule in NEED_RULES:
# Check if emotions match (any overlap)
emotion_match = any(emo in emotion_labels for emo in rule["emotions"])
# Check if contexts match (any overlap)
context_match = any(ctx in active_contexts for ctx in rule["contexts"])
if emotion_match and context_match:
need_key = rule["need"]
# Avoid duplicates
if need_key in seen_needs:
continue
seen_needs.add(need_key)
# Calculate confidence
# Base confidence from rule
confidence = rule["confidence_base"]
# Boost if multiple emotions match
emotion_overlap = sum(
1 for emo in rule["emotions"] if emo in emotion_labels
)
if emotion_overlap > 1:
confidence += 0.05
# Boost if multiple contexts match
context_overlap = sum(
1 for ctx in rule["contexts"] if ctx in active_contexts
)
if context_overlap > 1:
confidence += 0.05
# Adjust by emotional intensity (valence magnitude + arousal)
intensity = (abs(valence) + arousal) / 2.0
if intensity > 0.6:
confidence += 0.05
confidence = min(1.0, confidence) # Cap at 1.0
need_info = NEEDS[need_key]
inferred.append(
{
"need": need_key,
"label": need_info["label"],
"icon": need_info["icon"],
"confidence": round(confidence, 2),
"reasoning": rule["reasoning"],
"description": need_info["description"],
"interventions": need_info["interventions"],
"matched_contexts": [
ctx for ctx in rule["contexts"] if ctx in active_contexts
],
"matched_emotions": [
emo for emo in rule["emotions"] if emo in emotion_labels
],
}
)
# Sort by confidence (highest first)
inferred.sort(key=lambda x: x["confidence"], reverse=True)
return inferred
def format_needs_summary(needs: List[Dict], max_display: int = 2) -> str:
"""
Format needs as a human-readable summary.
Args:
needs: List of inferred needs from infer_needs()
max_display: Maximum number of needs to show
Returns:
Formatted string like:
"⚖️ Autonomy/Control (85%) | 🗣️ Connection/Belonging (72%)"
"""
if not needs:
return "No specific needs detected"
top_needs = needs[:max_display]
parts = [
f"{n['icon']} {n['label']} ({int(n['confidence']*100)}%)" for n in top_needs
]
return " | ".join(parts)
# Example usage for testing
if __name__ == "__main__":
# Test case 1: Autonomy need
test1 = infer_needs(
emotion_labels=["angry"],
text="my boss won't let me make my own decisions",
valence=-0.5,
arousal=0.7,
)
print("Test 1 (Autonomy):")
print(format_needs_summary(test1))
print(test1[0] if test1 else "No needs detected")
print()
# Test case 2: Connection need
test2 = infer_needs(
emotion_labels=["sad"],
text="i feel so alone, nobody understands what i'm going through",
valence=-0.6,
arousal=0.4,
)
print("Test 2 (Connection):")
print(format_needs_summary(test2))
print(test2[0] if test2 else "No needs detected")
print()
# Test case 3: Rest need
test3 = infer_needs(
emotion_labels=["tired", "anxious"],
text="i'm so overwhelmed, there's just too much to handle",
valence=-0.4,
arousal=0.6,
)
print("Test 3 (Rest):")
print(format_needs_summary(test3))
print(test3[0] if test3 else "No needs detected")