| @@ -0,0 +1,16 @@ | |||||
| package pipeline | |||||
| func candidateInMonitor(policy Policy, candidate Candidate) bool { | |||||
| start := policy.MonitorStartHz | |||||
| end := policy.MonitorEndHz | |||||
| if start == 0 || end == 0 || end <= start { | |||||
| return true | |||||
| } | |||||
| left := candidate.CenterHz | |||||
| right := candidate.CenterHz | |||||
| if candidate.BandwidthHz > 0 { | |||||
| left = candidate.CenterHz - candidate.BandwidthHz/2 | |||||
| right = candidate.CenterHz + candidate.BandwidthHz/2 | |||||
| } | |||||
| return right >= start && left <= end | |||||
| } | |||||
| @@ -27,6 +27,7 @@ type RefinementPlan struct { | |||||
| TotalCandidates int `json:"total_candidates"` | TotalCandidates int `json:"total_candidates"` | ||||
| MinCandidateSNRDb float64 `json:"min_candidate_snr_db"` | MinCandidateSNRDb float64 `json:"min_candidate_snr_db"` | ||||
| Budget int `json:"budget"` | Budget int `json:"budget"` | ||||
| DroppedByMonitor int `json:"dropped_by_monitor"` | |||||
| DroppedBySNR int `json:"dropped_by_snr"` | DroppedBySNR int `json:"dropped_by_snr"` | ||||
| DroppedByBudget int `json:"dropped_by_budget"` | DroppedByBudget int `json:"dropped_by_budget"` | ||||
| Selected []ScheduledCandidate `json:"selected,omitempty"` | Selected []ScheduledCandidate `json:"selected,omitempty"` | ||||
| @@ -26,6 +26,10 @@ func BuildRefinementPlan(candidates []Candidate, policy Policy) RefinementPlan { | |||||
| snrWeight, bwWeight, peakWeight := refinementIntentWeights(policy.Intent) | snrWeight, bwWeight, peakWeight := refinementIntentWeights(policy.Intent) | ||||
| scored := make([]ScheduledCandidate, 0, len(candidates)) | scored := make([]ScheduledCandidate, 0, len(candidates)) | ||||
| for _, c := range candidates { | for _, c := range candidates { | ||||
| if !candidateInMonitor(policy, c) { | |||||
| plan.DroppedByMonitor++ | |||||
| continue | |||||
| } | |||||
| if c.SNRDb < policy.MinCandidateSNRDb { | if c.SNRDb < policy.MinCandidateSNRDb { | ||||
| plan.DroppedBySNR++ | plan.DroppedBySNR++ | ||||
| continue | continue | ||||
| @@ -60,6 +60,23 @@ func TestBuildRefinementPlanRespectsMaxConcurrent(t *testing.T) { | |||||
| } | } | ||||
| } | } | ||||
| func TestBuildRefinementPlanAppliesMonitorSpan(t *testing.T) { | |||||
| policy := Policy{MaxRefinementJobs: 5, MinCandidateSNRDb: 0, MonitorStartHz: 150, MonitorEndHz: 350} | |||||
| cands := []Candidate{ | |||||
| {ID: 1, CenterHz: 100, BandwidthHz: 20}, | |||||
| {ID: 2, CenterHz: 200, BandwidthHz: 50}, | |||||
| {ID: 3, CenterHz: 300, BandwidthHz: 100}, | |||||
| {ID: 4, CenterHz: 500, BandwidthHz: 50}, | |||||
| } | |||||
| plan := BuildRefinementPlan(cands, policy) | |||||
| if plan.DroppedByMonitor != 2 { | |||||
| t.Fatalf("expected 2 dropped by monitor, got %d", plan.DroppedByMonitor) | |||||
| } | |||||
| if len(plan.Selected) != 2 { | |||||
| t.Fatalf("expected 2 selected within monitor, got %d", len(plan.Selected)) | |||||
| } | |||||
| } | |||||
| func TestAutoSpanForHint(t *testing.T) { | func TestAutoSpanForHint(t *testing.T) { | ||||
| span, source := AutoSpanForHint("WFM_STEREO") | span, source := AutoSpanForHint("WFM_STEREO") | ||||
| if span < 150000 || source == "" { | if span < 150000 || source == "" { | ||||