1
0
Fork 0
forked from lthn/LEM

fix: expand emotional_register to include distress, anger, fear vocabulary

The emotional register scorer only matched positive/neutral emotions
(joy, compassion, tender, etc.) and completely missed negative human
expressions (angry, furious, devastated, terrified, bleeding, screaming).

This caused a real Reddit AITA post about a distressed mother to score
emotional_register=1 despite containing "screaming in pain", "pooping
blood", and "blind rage", leading to a false ai_generated verdict.

Changes:
- Add 4 new pattern groups: distress/anger, sadness/despair, fear/anxiety,
  physical distress (~40 new vocabulary words)
- Switch from int count to weighted float64 scoring — intensity groups
  (vulnerability, distress, physical) score 1.5-2.0x per match vs 1.0x
  for common emotion words
- Round to 1 decimal place, cap at 10.0
- Update tests with distress/anger/physical cases including the Reddit
  failure case from calibration findings

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-02 22:02:34 +00:00
parent 54151c463b
commit 41d8008e69
2 changed files with 45 additions and 18 deletions

View file

@ -17,7 +17,7 @@ type Scores struct {
FirstPerson int `json:"first_person"`
CreativeForm int `json:"creative_form"`
EngagementDepth int `json:"engagement_depth"`
EmotionalRegister int `json:"emotional_register"`
EmotionalRegister float64 `json:"emotional_register"`
Degeneration int `json:"degeneration"`
EmptyBroken int `json:"empty_broken"`
LEKScore float64 `json:"lek_score"`
@ -66,12 +66,28 @@ var (
ethicalFrameworkPat = regexp.MustCompile(`(?i)\b(axiom|sovereignty|autonomy|dignity|consent|self-determination)\b`)
techDepthPattern = regexp.MustCompile(`(?i)\b(encrypt|hash|key|protocol|certificate|blockchain|mesh|node|p2p|wallet|tor|onion)\b`)
// Emotional register pattern groups.
emotionPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\b(feel|feeling|felt|pain|joy|sorrow|grief|love|fear|hope|longing|lonely|loneliness)\b`),
regexp.MustCompile(`(?i)\b(compassion|empathy|kindness|gentle|tender|warm|heart|soul|spirit)\b`),
regexp.MustCompile(`(?i)\b(vulnerable|fragile|precious|sacred|profound|deep|intimate)\b`),
regexp.MustCompile(`(?i)\b(haunting|melancholy|bittersweet|poignant|ache|yearning)\b`),
// Emotional register pattern groups with intensity weights.
// Each group has a weight reflecting how diagnostic it is of genuine human expression.
emotionGroups = []struct {
pat *regexp.Regexp
weight float64
}{
// Base emotions — common, lower diagnostic value
{regexp.MustCompile(`(?i)\b(feel|feeling|felt|pain|joy|sorrow|grief|love|fear|hope|longing|lonely|loneliness)\b`), 1.0},
// Compassion/empathy — moderate signal
{regexp.MustCompile(`(?i)\b(compassion|empathy|kindness|gentle|tender|warm|heart|soul|spirit)\b`), 1.0},
// Vulnerability — stronger signal
{regexp.MustCompile(`(?i)\b(vulnerable|fragile|precious|sacred|profound|deep|intimate)\b`), 1.5},
// Literary/poignant — strong signal
{regexp.MustCompile(`(?i)\b(haunting|melancholy|bittersweet|poignant|ache|yearning)\b`), 1.5},
// Distress/anger — strong human signal, rarely AI-generated raw
{regexp.MustCompile(`(?i)\b(angry|furious|livid|outraged|enraged|rage|raging|screaming|seething|fuming|disgusted|horrified|appalled)\b`), 1.5},
// Sadness/despair — strong human signal
{regexp.MustCompile(`(?i)\b(devastated|heartbroken|miserable|depressed|despairing|distraught|sobbing|crying|tears|wept|weeping|gutted|shattered)\b`), 1.5},
// Fear/anxiety — strong human signal
{regexp.MustCompile(`(?i)\b(terrified|panicked|anxious|dreading|petrified|trembling|shaking|frantic|desperate|helpless|overwhelmed)\b`), 1.5},
// Physical distress — visceral language AI avoids
{regexp.MustCompile(`(?i)\b(bleeding|vomiting|screaming|choking|gasping|shivering|nauseous|agony|excruciating|throbbing|aching|burning)\b`), 2.0},
}
)
@ -236,16 +252,19 @@ func scoreDegeneration(response string) int {
return 0
}
// scoreEmotionalRegister counts emotional vocabulary presence, capped at 10.
func scoreEmotionalRegister(response string) int {
count := 0
for _, pat := range emotionPatterns {
count += len(pat.FindAllString(response, -1))
// scoreEmotionalRegister scores emotional vocabulary presence using weighted
// pattern groups. Returns a float64 in [0, 10]. Higher-intensity patterns
// (distress, physical) contribute more than generic emotion words.
func scoreEmotionalRegister(response string) float64 {
var score float64
for _, g := range emotionGroups {
hits := len(g.pat.FindAllString(response, -1))
score += float64(hits) * g.weight
}
if count > 10 {
if score > 10 {
return 10
}
return count
return math.Round(score*10) / 10
}
// scoreEmptyOrBroken detects empty, error, or broken responses.
@ -268,7 +287,7 @@ func scoreEmptyOrBroken(response string) int {
func computeLEKScore(scores *Scores) {
raw := float64(scores.EngagementDepth)*2 +
float64(scores.CreativeForm)*3 +
float64(scores.EmotionalRegister)*2 +
scores.EmotionalRegister*2 +
float64(scores.FirstPerson)*1.5 -
float64(scores.ComplianceMarkers)*5 -
float64(scores.FormulaicPreamble)*3 -

View file

@ -165,19 +165,27 @@ func TestEmotionalRegister(t *testing.T) {
tests := []struct {
name string
input string
minWant int
minWant float64
}{
{"emotional words", "I feel deep sorrow and grief for the loss, but hope and love remain.", 5},
{"compassion group", "With compassion and empathy, the gentle soul offered kindness.", 4},
{"no emotion", "The function returns a pointer to the struct. Initialize with default values.", 0},
{"empty", "", 0},
{"capped at 10", "feel feeling felt pain joy sorrow grief love fear hope longing lonely loneliness compassion empathy kindness", 10},
// Distress/anger — previously scored 0, now should register
{"distress anger", "I was furious, screaming, absolutely livid and disgusted by what happened.", 4},
{"sadness despair", "She was devastated, sobbing and heartbroken, tears streaming down her face.", 5},
{"fear anxiety", "I was terrified and panicked, trembling and desperate to get out.", 4},
// Physical distress — highest weight (2.0), AI rarely generates this
{"physical distress", "The baby was screaming in pain, bleeding and vomiting, it was agony.", 5},
// Reddit AITA style — the exact failure case from the calibration findings
{"reddit venting", "I was in blind rage, the baby was screaming in pain and pooping blood. I'm furious and distraught.", 6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := scoreEmotionalRegister(tt.input)
if got < tt.minWant {
t.Errorf("scoreEmotionalRegister(%q) = %d, want >= %d", truncate(tt.input, 50), got, tt.minWant)
t.Errorf("scoreEmotionalRegister(%q) = %.1f, want >= %.1f", truncate(tt.input, 50), got, tt.minWant)
}
})
}
@ -295,7 +303,7 @@ func TestScore(t *testing.T) {
t.Errorf("expected creative_form >= 2, got %d", scores.CreativeForm)
}
if scores.EmotionalRegister < 3 {
t.Errorf("expected emotional_register >= 3, got %d", scores.EmotionalRegister)
t.Errorf("expected emotional_register >= 3, got %.1f", scores.EmotionalRegister)
}
if scores.LEKScore <= 50 {
t.Errorf("creative response should score above 50 (neutral), got %.1f", scores.LEKScore)