router/semantic: corpus validation infrastructure (slice 13)
Add ValidateCorpus and CorpusStatsFrom for structural integrity checks on the semantic route corpus. Validates total counts, route/source sums, fast-path+residual partition, empty SourceID/SplitGroup, invalid routes, duplicate identity, and conflicting labels on identical text. CorpusStats provides deterministic dataset hash (SHA-256 of sorted texts, first 16 bytes). ReproducibilityMeta in CorpusEnvelope records source fixture hashes, generator version, split algorithm, and dataset hash. TestCorpusValidation exercises the full validation pipeline.
This commit is contained in:
@@ -69,6 +69,61 @@ func TestLoadCorpus(t *testing.T) {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user