From e9829119395ff06b1e96ff310b02c8f62d3a7dcb Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 26 Feb 2026 06:32:24 +0000 Subject: [PATCH] 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 --- pkg/heuristic/heuristic.go | 8 ++++++- pkg/heuristic/heuristic_test.go | 37 ++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/pkg/heuristic/heuristic.go b/pkg/heuristic/heuristic.go index 75186e1..5438ca5 100644 --- a/pkg/heuristic/heuristic.go +++ b/pkg/heuristic/heuristic.go @@ -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 } diff --git a/pkg/heuristic/heuristic_test.go b/pkg/heuristic/heuristic_test.go index abe26d2..2a96986 100644 --- a/pkg/heuristic/heuristic_test.go +++ b/pkg/heuristic/heuristic_test.go @@ -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) } })