diff --git a/internal/pipeline/candidate_fusion_test.go b/internal/pipeline/candidate_fusion_test.go new file mode 100644 index 0000000..ae33cfd --- /dev/null +++ b/internal/pipeline/candidate_fusion_test.go @@ -0,0 +1,64 @@ +package pipeline + +import "testing" + +func TestFuseCandidatesDedup(t *testing.T) { + primary := []Candidate{ + { + ID: 1, + CenterHz: 100.0e6, + BandwidthHz: 20000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance"}}, + }, + }, + } + derived := []Candidate{ + { + ID: -1, + CenterHz: 100.0e6 + 2000, + BandwidthHz: 25000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance-lowres"}}, + }, + }, + } + fused := FuseCandidates(primary, derived) + if len(fused) != 1 { + t.Fatalf("expected 1 fused candidate, got %d", len(fused)) + } + if got := CandidateEvidenceLevelCount(fused[0]); got != 2 { + t.Fatalf("expected 2 evidence levels after fuse, got %d", got) + } +} + +func TestFuseCandidatesSingleVsMultiResolution(t *testing.T) { + primary := []Candidate{ + { + ID: 1, + CenterHz: 101.0e6, + BandwidthHz: 12000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance"}}, + }, + }, + } + single := FuseCandidates(primary, nil) + if len(single) != 1 { + t.Fatalf("expected single-resolution to keep 1 candidate, got %d", len(single)) + } + derived := []Candidate{ + { + ID: -2, + CenterHz: 101.3e6, + BandwidthHz: 15000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance-lowres"}}, + }, + }, + } + multi := FuseCandidates(primary, derived) + if len(multi) != 2 { + t.Fatalf("expected multi-resolution to keep 2 candidates, got %d", len(multi)) + } +} diff --git a/internal/pipeline/scheduler_test.go b/internal/pipeline/scheduler_test.go index ffa2080..e6fa4f4 100644 --- a/internal/pipeline/scheduler_test.go +++ b/internal/pipeline/scheduler_test.go @@ -144,6 +144,37 @@ func TestScheduleCandidatesPriorityBoost(t *testing.T) { } } +func TestScheduleCandidatesEvidenceBoost(t *testing.T) { + policy := Policy{MaxRefinementJobs: 2, MinCandidateSNRDb: 0} + single := Candidate{ + ID: 1, + SNRDb: 8, + BandwidthHz: 12000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance"}}, + }, + } + multi := Candidate{ + ID: 2, + SNRDb: 8, + BandwidthHz: 12000, + Evidence: []LevelEvidence{ + {Level: AnalysisLevel{Name: "surveillance"}}, + {Level: AnalysisLevel{Name: "surveillance-lowres"}}, + }, + } + plan := BuildRefinementPlan([]Candidate{single, multi}, policy) + if len(plan.Ranked) < 2 { + t.Fatalf("expected ranked candidates, got %d", len(plan.Ranked)) + } + if plan.Ranked[0].Candidate.ID != multi.ID { + t.Fatalf("expected multi-level candidate to rank first, got %+v", plan.Ranked[0]) + } + if plan.Ranked[0].Breakdown == nil || plan.Ranked[0].Breakdown.EvidenceScore <= 0 { + t.Fatalf("expected evidence score to be populated, got %+v", plan.Ranked[0].Breakdown) + } +} + func TestBuildRefinementPlanPriorityStats(t *testing.T) { policy := Policy{MaxRefinementJobs: 1, MinCandidateSNRDb: 0} cands := []Candidate{