diff --git a/internal/pipeline/goals.go b/internal/pipeline/goals.go index cca092e..3157730 100644 --- a/internal/pipeline/goals.go +++ b/internal/pipeline/goals.go @@ -15,14 +15,39 @@ func WantsClass(values []string, class string) bool { } func CandidatePriorityBoost(policy Policy, hint string) float64 { + boost := hintMatchBoost(policy.SignalPriorities, hint, 3.0) + boost += hintMatchBoost(policy.AutoRecordClasses, hint, 1.5) + boost += hintMatchBoost(policy.AutoDecodeClasses, hint, 1.0) + return boost +} + +func DecisionPriorityBoost(policy Policy, hint string, class string, queue string) float64 { + tag := strings.TrimSpace(hint) + if tag == "" { + tag = strings.TrimSpace(class) + } + boost := CandidatePriorityBoost(policy, tag) + switch strings.ToLower(strings.TrimSpace(queue)) { + case "record": + boost += hintMatchBoost(policy.AutoRecordClasses, tag, 3.0) + case "decode": + boost += hintMatchBoost(policy.AutoDecodeClasses, tag, 3.0) + } + return boost +} + +func hintMatchBoost(values []string, hint string, weight float64) float64 { h := strings.ToLower(strings.TrimSpace(hint)) - for i, want := range policy.SignalPriorities { + if h == "" || len(values) == 0 { + return 0 + } + for i, want := range values { w := strings.ToLower(strings.TrimSpace(want)) if w == "" { continue } if strings.Contains(h, w) || strings.Contains(w, h) { - return float64(len(policy.SignalPriorities)-i) * 3.0 + return float64(len(values)-i) * weight } } return 0 diff --git a/internal/pipeline/phases.go b/internal/pipeline/phases.go index f0ceee3..b9a2f0a 100644 --- a/internal/pipeline/phases.go +++ b/internal/pipeline/phases.go @@ -50,3 +50,8 @@ type RefinementResult struct { Decisions []SignalDecision `json:"decisions,omitempty"` Candidates []Candidate `json:"candidates,omitempty"` } + +type RefinementStep struct { + Input RefinementInput `json:"input"` + Result RefinementResult `json:"result"` +} diff --git a/internal/pipeline/policy.go b/internal/pipeline/policy.go index 4b61a17..fa76486 100644 --- a/internal/pipeline/policy.go +++ b/internal/pipeline/policy.go @@ -15,6 +15,7 @@ type Policy struct { SurveillanceFPS int `json:"surveillance_fps"` DisplayBins int `json:"display_bins"` DisplayFPS int `json:"display_fps"` + SurveillanceStrategy string `json:"surveillance_strategy"` RefinementEnabled bool `json:"refinement_enabled"` MaxRefinementJobs int `json:"max_refinement_jobs"` RefinementMaxConcurrent int `json:"refinement_max_concurrent"` @@ -41,6 +42,7 @@ func PolicyFromConfig(cfg config.Config) Policy { SurveillanceFPS: cfg.Surveillance.FrameRate, DisplayBins: cfg.Surveillance.DisplayBins, DisplayFPS: cfg.Surveillance.DisplayFPS, + SurveillanceStrategy: cfg.Surveillance.Strategy, RefinementEnabled: cfg.Refinement.Enabled, MaxRefinementJobs: cfg.Resources.MaxRefinementJobs, RefinementMaxConcurrent: cfg.Refinement.MaxConcurrent, diff --git a/internal/pipeline/window_rules.go b/internal/pipeline/window_rules.go index f7a261f..09cc8e7 100644 --- a/internal/pipeline/window_rules.go +++ b/internal/pipeline/window_rules.go @@ -28,3 +28,30 @@ func AutoSpanForHint(hint string) (float64, string) { return 0, "" } } + +// RefinementWindowForCandidate applies policy-aware span rules to a candidate. +func RefinementWindowForCandidate(policy Policy, candidate Candidate) RefinementWindow { + span := candidate.BandwidthHz + windowSource := "candidate" + if policy.RefinementAutoSpan && (span <= 0 || span < 2000 || span > 400000) { + autoSpan, autoSource := AutoSpanForHint(candidate.Hint) + if autoSpan > 0 { + span = autoSpan + windowSource = autoSource + } + } + if policy.RefinementMinSpanHz > 0 && span < policy.RefinementMinSpanHz { + span = policy.RefinementMinSpanHz + } + if policy.RefinementMaxSpanHz > 0 && span > policy.RefinementMaxSpanHz { + span = policy.RefinementMaxSpanHz + } + if span <= 0 { + span = 12000 + } + return RefinementWindow{ + CenterHz: candidate.CenterHz, + SpanHz: span, + Source: windowSource, + } +}