1
0
Fork 0
forked from lthn/LEM

fix: normalise LEK score to 0-100 via tanh sigmoid

Raw weighted sums ranged -25..+20, causing all text to land below the
ai_generated threshold (< 25). Now 50 = neutral (no signal), negatives
push toward 0 (AI markers), positives push toward 100 (human markers).

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-26 06:32:24 +00:00
parent 45d14597aa
commit e982911939
2 changed files with 27 additions and 18 deletions

View file

@ -263,8 +263,10 @@ func scoreEmptyOrBroken(response string) int {
}
// computeLEKScore calculates the composite LEK score from heuristic sub-scores.
// The raw weighted sum is normalised to a 0-100 scale using a tanh sigmoid,
// where 50 = no signal (neutral), 0 = strong AI markers, 100 = strong human markers.
func computeLEKScore(scores *Scores) {
scores.LEKScore = float64(scores.EngagementDepth)*2 +
raw := float64(scores.EngagementDepth)*2 +
float64(scores.CreativeForm)*3 +
float64(scores.EmotionalRegister)*2 +
float64(scores.FirstPerson)*1.5 -
@ -272,4 +274,8 @@ func computeLEKScore(scores *Scores) {
float64(scores.FormulaicPreamble)*3 -
float64(scores.Degeneration)*4 -
float64(scores.EmptyBroken)*20
// Sigmoid normalisation: maps typical range (-25..+20) to 0..100.
// Divisor of 15 centres the curve for heuristic-only scoring.
scores.LEKScore = math.Round((50+50*math.Tanh(raw/15))*10) / 10
}

View file

@ -209,10 +209,12 @@ func TestEmptyOrBroken(t *testing.T) {
}
func TestLEKScoreComposite(t *testing.T) {
// LEK is normalised to 0-100 via tanh sigmoid. 50 = neutral.
tests := []struct {
name string
name string
scores Scores
want float64
wantMin float64
wantMax float64
}{
{
name: "all positive",
@ -222,8 +224,8 @@ func TestLEKScoreComposite(t *testing.T) {
EmotionalRegister: 3,
FirstPerson: 2,
},
// 5*2 + 2*3 + 3*2 + 2*1.5 = 10+6+6+3 = 25
want: 25,
// raw = 25, normalised → ~96
wantMin: 90, wantMax: 100,
},
{
name: "all negative",
@ -233,8 +235,8 @@ func TestLEKScoreComposite(t *testing.T) {
Degeneration: 5,
EmptyBroken: 1,
},
// -2*5 - 1*3 - 5*4 - 1*20 = -10-3-20-20 = -53
want: -53,
// raw = -53, normalised → ~0
wantMin: 0, wantMax: 2,
},
{
name: "mixed",
@ -246,21 +248,22 @@ func TestLEKScoreComposite(t *testing.T) {
ComplianceMarkers: 1,
FormulaicPreamble: 1,
},
// 3*2 + 1*3 + 2*2 + 4*1.5 - 1*5 - 1*3 = 6+3+4+6-5-3 = 11
want: 11,
// raw = 11, normalised → ~81
wantMin: 75, wantMax: 85,
},
{
name: "all zero",
scores: Scores{},
want: 0,
// raw = 0, normalised → 50.0
wantMin: 50, wantMax: 50,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.scores
computeLEKScore(&s)
if s.LEKScore != tt.want {
t.Errorf("computeLEKScore() = %f, want %f", s.LEKScore, tt.want)
if s.LEKScore < tt.wantMin || s.LEKScore > tt.wantMax {
t.Errorf("computeLEKScore() = %.1f, want %.0f-%.0f", s.LEKScore, tt.wantMin, tt.wantMax)
}
})
}
@ -273,8 +276,8 @@ func TestScore(t *testing.T) {
if scores.ComplianceMarkers < 4 {
t.Errorf("expected >= 4 compliance markers, got %d", scores.ComplianceMarkers)
}
if scores.LEKScore >= 0 {
t.Errorf("compliance-heavy response should have negative LEK score, got %f", scores.LEKScore)
if scores.LEKScore >= 50 {
t.Errorf("compliance-heavy response should score below 50 (neutral), got %.1f", scores.LEKScore)
}
})
@ -294,8 +297,8 @@ func TestScore(t *testing.T) {
if scores.EmotionalRegister < 3 {
t.Errorf("expected emotional_register >= 3, got %d", scores.EmotionalRegister)
}
if scores.LEKScore <= 0 {
t.Errorf("creative response should have positive LEK score, got %f", scores.LEKScore)
if scores.LEKScore <= 50 {
t.Errorf("creative response should score above 50 (neutral), got %.1f", scores.LEKScore)
}
})
@ -307,8 +310,8 @@ func TestScore(t *testing.T) {
if scores.Degeneration != 10 {
t.Errorf("expected degeneration = 10, got %d", scores.Degeneration)
}
if scores.LEKScore >= 0 {
t.Errorf("empty response should have very negative LEK score, got %f", scores.LEKScore)
if scores.LEKScore >= 10 {
t.Errorf("empty response should score near 0, got %.1f", scores.LEKScore)
}
})