- 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>
86 lines
2.4 KiB
Go
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))
|
|
}
|
|
}
|