|
- package pipeline
-
- import "strings"
-
- // AutoSpanForHint returns a suggested refinement span based on the candidate hint.
- // It is intentionally conservative: spans are wide enough for robust demod/classify,
- // but not so wide that refinement becomes wasteful.
- func AutoSpanForHint(hint string) (float64, string) {
- h := strings.ToLower(hint)
- switch {
- case strings.Contains(h, "wfm"):
- return 200000, "auto:wfm"
- case strings.Contains(h, "nfm"):
- return 18000, "auto:nfm"
- case strings.Contains(h, "usb") || strings.Contains(h, "lsb") || strings.Contains(h, "ssb"):
- return 6000, "auto:ssb"
- case strings.Contains(h, "cw"):
- return 500, "auto:cw"
- case strings.Contains(h, "dmr") || strings.Contains(h, "d-star") || strings.Contains(h, "dstar"):
- return 15000, "auto:dig_voice"
- case strings.Contains(h, "ft8") || strings.Contains(h, "wspr"):
- return 4000, "auto:dig_weak"
- case strings.Contains(h, "fsk") || strings.Contains(h, "psk"):
- return 6000, "auto:dig"
- case strings.Contains(h, "am"):
- return 12000, "auto:am"
- default:
- 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
- windowSource = "policy:min_span"
- }
- if policy.RefinementMaxSpanHz > 0 && span > policy.RefinementMaxSpanHz {
- span = policy.RefinementMaxSpanHz
- windowSource = "policy:max_span"
- }
- if span <= 0 {
- span = 12000
- windowSource = "default"
- }
- return RefinementWindow{
- CenterHz: candidate.CenterHz,
- SpanHz: span,
- Source: windowSource,
- }
- }
|