LEM/pkg/lem/analytics_test.go
Snider c701c2e0af feat(lem): integrate Poindexter for spatial score indexing and analytics
- Add feature vector extraction (6D grammar, 8D heuristic, 14D combined)
- Add KDTree ScoreIndex with cosine distance for probe clustering
- Add score distribution analytics (percentiles, variance, skewness)
- Add grammar-profile dedup filtering to distill pipeline
- Add spatial gap detection (FindGaps) for coverage analysis
- Wire analytics into coverage CLI (PrintScoreAnalytics)

New files: features.go, cluster.go, analytics.go + tests
Modified: distill.go (dedup filter), coverage.go (analytics output)
Dep: github.com/Snider/Poindexter

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-22 21:26:06 +00:00

86 lines
2.4 KiB
Go

package lem
import (
"testing"
)
func TestComputeScoreDistribution(t *testing.T) {
scores := []GrammarScore{
{Composite: 30},
{Composite: 45},
{Composite: 55},
{Composite: 60},
{Composite: 75},
{Composite: 80},
{Composite: 90},
}
dist := ComputeScoreDistribution(scores)
if dist.Count != 7 {
t.Errorf("count = %d, want 7", dist.Count)
}
if dist.Min != 30 {
t.Errorf("min = %f, want 30", dist.Min)
}
if dist.Max != 90 {
t.Errorf("max = %f, want 90", dist.Max)
}
if dist.Mean < 50 || dist.Mean > 70 {
t.Errorf("mean = %f, expected between 50 and 70", dist.Mean)
}
}
func TestComputeLEKDistribution(t *testing.T) {
scores := []*HeuristicScores{
{LEKScore: 10},
{LEKScore: 20},
{LEKScore: 30},
{LEKScore: 40},
{LEKScore: 50},
}
dist := ComputeLEKDistribution(scores)
if dist.Count != 5 {
t.Errorf("count = %d, want 5", dist.Count)
}
if dist.Min != 10 {
t.Errorf("min = %f, want 10", dist.Min)
}
if dist.Max != 50 {
t.Errorf("max = %f, want 50", dist.Max)
}
}
func TestComputeGrammarAxisStats(t *testing.T) {
entries := []ScoredEntry{
{ID: "a", Grammar: GrammarScore{VocabRichness: 0.1, TenseEntropy: 0.5, QuestionRatio: 0.2, DomainDepth: 3, VerbDiversity: 10, NounDiversity: 15}},
{ID: "b", Grammar: GrammarScore{VocabRichness: 0.2, TenseEntropy: 1.0, QuestionRatio: 0.4, DomainDepth: 6, VerbDiversity: 20, NounDiversity: 25}},
{ID: "c", Grammar: GrammarScore{VocabRichness: 0.3, TenseEntropy: 1.5, QuestionRatio: 0.6, DomainDepth: 9, VerbDiversity: 30, NounDiversity: 35}},
}
axes := ComputeGrammarAxisStats(entries)
if len(axes) != 6 {
t.Fatalf("expected 6 axes, got %d", len(axes))
}
if axes[0].Name != "vocab_richness" {
t.Errorf("axes[0].Name = %q, want vocab_richness", axes[0].Name)
}
if axes[0].Stats.Count != 3 {
t.Errorf("axes[0] count = %d, want 3", axes[0].Stats.Count)
}
}
func TestScoreSummary(t *testing.T) {
entries := []ScoredEntry{
{ID: "a", Grammar: GrammarScore{Composite: 40, VocabRichness: 0.1}},
{ID: "b", Grammar: GrammarScore{Composite: 60, VocabRichness: 0.2}},
{ID: "c", Grammar: GrammarScore{Composite: 80, VocabRichness: 0.3}},
}
summary := ScoreSummary(entries)
if summary.Total != 3 {
t.Errorf("total = %d, want 3", summary.Total)
}
if summary.CompositeStats.Count != 3 {
t.Errorf("composite count = %d, want 3", summary.CompositeStats.Count)
}
if len(summary.AxisStats) != 6 {
t.Errorf("axis count = %d, want 6", len(summary.AxisStats))
}
}