package semantic import ( "testing" "github.com/kami/maven/internal/router" ) func TestValidRoute(t *testing.T) { for _, r := range AllRoutes { if !ValidRoute(r) { t.Errorf("ValidRoute(%q) = false, want true", r) } } if ValidRoute("bogus") { t.Error("ValidRoute(\"bogus\") = true, want false") } } func TestIntentToRouteMapping(t *testing.T) { cases := []struct { intent router.Intent route SemanticRoute }{ {router.IntentChat, RouteConversation}, {router.IntentQuery, RouteKnowledge}, {router.IntentAct, RouteAction}, {router.IntentReminder, RouteAction}, {router.IntentFact, RouteMemoryWrite}, {router.IntentNote, RouteMemoryWrite}, {router.IntentSystem, RouteSystem}, {router.Intent("unknown"), RouteUncertain}, {router.Intent(""), RouteUncertain}, } for _, c := range cases { got := IntentToRoute(c.intent) if got != c.route { t.Errorf("IntentToRoute(%q) = %q, want %q", c.intent, got, c.route) } } } func TestLoadCorpus(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } if len(exs) == 0 { t.Fatal("corpus is empty") } for _, e := range exs { if !ValidRoute(e.Route) { t.Errorf("example %q has invalid route %q", e.SourceID, e.Route) } if e.Source == "" { t.Errorf("example %q has empty source", e.SourceID) } if e.SplitGroup == "" { t.Errorf("example %q has empty split_group", e.SourceID) } } fastPath, residual := SplitCounts(exs) if fastPath == 0 { t.Error("no fast_path_resolved examples in corpus") } if residual == 0 { t.Error("no residual examples in corpus") } t.Logf("corpus: %d examples (%d fast-path, %d residual)", len(exs), fastPath, residual) } func TestCorpusValidation(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } stats := CorpusStatsFrom(exs) // Total matches. if stats.Total != len(exs) { t.Errorf("stats.Total=%d, want %d", stats.Total, len(exs)) } // Route counts sum to total. routeSum := 0 for _, c := range stats.RouteCounts { routeSum += c } if routeSum != stats.Total { t.Errorf("route counts sum to %d, want %d", routeSum, stats.Total) } // Source counts sum to total. sourceSum := 0 for _, c := range stats.SourceCounts { sourceSum += c } if sourceSum != stats.Total { t.Errorf("source counts sum to %d, want %d", sourceSum, stats.Total) } // Fast-path + residual == total. if stats.FastPath+stats.Residual != stats.Total { t.Errorf("fast_path(%d) + residual(%d) = %d, want %d", stats.FastPath, stats.Residual, stats.FastPath+stats.Residual, stats.Total) } // All SourceIDs are valid (non-empty, checked by ValidateCorpus). // All SplitGroups are non-empty (checked by ValidateCorpus). // No duplicate corpus identity (checked by ValidateCorpus). // No identical Text with conflicting labels (checked by ValidateCorpus). t.Logf("validation passed: %d rows, route_hash=%s", stats.Total, stats.DatasetHash) // Verify the exact counts match what we expect. if stats.Total != 136 { t.Errorf("total = %d, want 136", stats.Total) } if stats.SourceCounts["ru_routing_v1"] != 96 { t.Errorf("ru_routing_v1 count = %d, want 96", stats.SourceCounts["ru_routing_v1"]) } if stats.SourceCounts["contrastive"] != 40 { t.Errorf("contrastive count = %d, want 40", stats.SourceCounts["contrastive"]) } } func TestCorpusByRoute(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } byRoute := ByRoute(exs) for _, route := range AllRoutes { count := len(byRoute[route]) if count == 0 { t.Errorf("no examples for route %q", route) } t.Logf(" %s: %d examples", route, count) } } func TestSplitByFamily(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } train, eval := SplitByFamily(exs, 0.8) total := len(train) + len(eval) if total != len(exs) { t.Errorf("split lost examples: train=%d eval=%d total=%d, want %d", len(train), len(eval), total, len(exs)) } trainGroups := map[string]bool{} for _, e := range train { trainGroups[e.SplitGroup] = true } for _, e := range eval { if trainGroups[e.SplitGroup] { t.Errorf("split_group %q leaked across train/eval (example %q)", e.SplitGroup, e.SourceID) } } t.Logf("split: %d train, %d eval", len(train), len(eval)) } func TestFrozenHoldoutSplit(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } frozen, dev, hash := FrozenHoldoutSplit(exs) total := len(frozen) + len(dev) if total != len(exs) { t.Errorf("split lost examples: frozen=%d dev=%d total=%d, want %d", len(frozen), len(dev), total, len(exs)) } if len(frozen) == 0 { t.Error("frozen holdout is empty") } if len(dev) == 0 { t.Error("development pool is empty") } // No split_group leakage. frozenGroups := map[string]bool{} for _, e := range frozen { frozenGroups[e.SplitGroup] = true } for _, e := range dev { if frozenGroups[e.SplitGroup] { t.Errorf("split_group %q leaked across frozen/dev (example %q)", e.SplitGroup, e.SourceID) } } // Determinism: same split produces same hash. frozen2, dev2, hash2 := FrozenHoldoutSplit(exs) if hash != hash2 { t.Errorf("non-deterministic: hash=%s vs %s", hash, hash2) } if len(frozen) != len(frozen2) || len(dev) != len(dev2) { t.Errorf("non-deterministic: frozen=%d/%d dev=%d/%d", len(frozen), len(frozen2), len(dev), len(dev2)) } // Log route coverage in both splits (not all routes need to appear // in the frozen holdout — at 15% of 136 rows, small routes may miss). frozenRoutes := map[SemanticRoute]bool{} for _, e := range frozen { frozenRoutes[e.Route] = true } devRoutes := map[SemanticRoute]bool{} for _, e := range dev { devRoutes[e.Route] = true } for _, r := range AllRoutes { if !frozenRoutes[r] { t.Logf("note: route %q missing from frozen holdout (expected at 15%%)", r) } if !devRoutes[r] { t.Errorf("route %q missing from development pool", r) } } t.Logf("frozen: %d examples, dev: %d examples, holdout_hash=%s", len(frozen), len(dev), hash) } func TestGroupedCVFolds(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } _, dev, _ := FrozenHoldoutSplit(exs) folds := GroupedCVFolds(dev, 5) if len(folds) != 5 { t.Fatalf("expected 5 folds, got %d", len(folds)) } for i, f := range folds { total := len(f.Train) + len(f.Eval) if total != len(dev) { t.Errorf("fold %d: train=%d + eval=%d = %d, want %d", i, len(f.Train), len(f.Eval), total, len(dev)) } if len(f.Eval) == 0 { t.Errorf("fold %d: empty eval set", i) } // No split_group leakage within a fold. evalGroups := map[string]bool{} for _, e := range f.Eval { evalGroups[e.SplitGroup] = true } for _, e := range f.Train { if evalGroups[e.SplitGroup] { t.Errorf("fold %d: split_group %q leaked (example %q)", i, e.SplitGroup, e.SourceID) } } } // Every example appears in exactly one eval set across all folds. evalCount := 0 for _, f := range folds { evalCount += len(f.Eval) } if evalCount != len(dev) { t.Errorf("total eval examples across folds: %d, want %d", evalCount, len(dev)) } t.Logf("grouped CV: %d folds on %d development examples", len(folds), len(dev)) } func TestFamilyID(t *testing.T) { id1 := FamilyID("ru-act-002", "negation") id2 := FamilyID("ru-act-002", "question") id3 := FamilyID("ru-act-002", "") id4 := FamilyID("ru-act-001", "negation") if id1 == id2 { t.Error("different transforms produced same FamilyID") } if id1 == id3 { t.Error("transform and base produced same FamilyID") } if id1 == id4 { t.Error("different base IDs produced same FamilyID") } } func TestContrastFamiliesShareSplitGroup(t *testing.T) { exs, err := LoadCorpus() if err != nil { t.Fatal(err) } // Group contrastive examples by their base source_id. baseGroups := map[string]map[string]bool{} // source_id → {split_group: true} for _, e := range exs { if e.Source != "contrastive" { continue } if baseGroups[e.SourceID] == nil { baseGroups[e.SourceID] = map[string]bool{} } baseGroups[e.SourceID][e.SplitGroup] = true } // Every base seed's contrastive variants must share exactly one // split_group (the base seed's own group). for sourceID, groups := range baseGroups { if len(groups) != 1 { t.Errorf("base %q has contrastive variants in %d split groups: %v", sourceID, len(groups), groups) } } // Also verify that contrastive variants share the split_group with // their base seed from ru_routing_v1. ruGroups := map[string]string{} // source_id → split_group for _, e := range exs { if e.Source == "ru_routing_v1" { ruGroups[e.SourceID] = e.SplitGroup } } for sourceID, groups := range baseGroups { var contrastGroup string for g := range groups { contrastGroup = g } if ruGroup, ok := ruGroups[sourceID]; ok && contrastGroup != ruGroup { t.Errorf("base %q: ru_routing_v1 split_group=%q, contrastive split_group=%q", sourceID, ruGroup, contrastGroup) } } t.Logf("contrast family leakage check passed: %d base seeds, all groups consistent", len(baseGroups)) }