Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

119 строки
3.3KB

  1. package pipeline
  2. import "sort"
  3. type ScheduledCandidate struct {
  4. Candidate Candidate `json:"candidate"`
  5. Priority float64 `json:"priority"`
  6. Breakdown *PriorityBreakdown `json:"breakdown,omitempty"`
  7. }
  8. type PriorityBreakdown struct {
  9. SNRScore float64 `json:"snr_score"`
  10. BandwidthScore float64 `json:"bandwidth_score"`
  11. PeakScore float64 `json:"peak_score"`
  12. PolicyBoost float64 `json:"policy_boost"`
  13. }
  14. // BuildRefinementPlan scores and budgets candidates for costly local refinement.
  15. // Current heuristic is intentionally simple and deterministic; later phases can add
  16. // richer scoring (novelty, persistence, profile-aware band priorities, decoder value).
  17. func BuildRefinementPlan(candidates []Candidate, policy Policy) RefinementPlan {
  18. budget := policy.MaxRefinementJobs
  19. if policy.RefinementMaxConcurrent > 0 && (budget <= 0 || policy.RefinementMaxConcurrent < budget) {
  20. budget = policy.RefinementMaxConcurrent
  21. }
  22. plan := RefinementPlan{
  23. TotalCandidates: len(candidates),
  24. MinCandidateSNRDb: policy.MinCandidateSNRDb,
  25. Budget: budget,
  26. }
  27. if start, end, ok := monitorBounds(policy); ok {
  28. plan.MonitorStartHz = start
  29. plan.MonitorEndHz = end
  30. if end > start {
  31. plan.MonitorSpanHz = end - start
  32. }
  33. }
  34. if len(candidates) == 0 {
  35. return plan
  36. }
  37. snrWeight, bwWeight, peakWeight := refinementIntentWeights(policy.Intent)
  38. scored := make([]ScheduledCandidate, 0, len(candidates))
  39. for _, c := range candidates {
  40. if !candidateInMonitor(policy, c) {
  41. plan.DroppedByMonitor++
  42. continue
  43. }
  44. if c.SNRDb < policy.MinCandidateSNRDb {
  45. plan.DroppedBySNR++
  46. continue
  47. }
  48. snrScore := c.SNRDb * snrWeight
  49. bwScore := 0.0
  50. peakScore := 0.0
  51. policyBoost := CandidatePriorityBoost(policy, c.Hint)
  52. if c.BandwidthHz > 0 {
  53. bwScore = minFloat64(c.BandwidthHz/25000.0, 6) * bwWeight
  54. }
  55. if c.PeakDb > 0 {
  56. peakScore = (c.PeakDb / 20.0) * peakWeight
  57. }
  58. priority := snrScore + bwScore + peakScore + policyBoost
  59. scored = append(scored, ScheduledCandidate{
  60. Candidate: c,
  61. Priority: priority,
  62. Breakdown: &PriorityBreakdown{
  63. SNRScore: snrScore,
  64. BandwidthScore: bwScore,
  65. PeakScore: peakScore,
  66. PolicyBoost: policyBoost,
  67. },
  68. })
  69. }
  70. sort.Slice(scored, func(i, j int) bool {
  71. if scored[i].Priority == scored[j].Priority {
  72. return scored[i].Candidate.CenterHz < scored[j].Candidate.CenterHz
  73. }
  74. return scored[i].Priority > scored[j].Priority
  75. })
  76. if len(scored) > 0 {
  77. minPriority := scored[0].Priority
  78. maxPriority := scored[0].Priority
  79. sumPriority := 0.0
  80. for _, s := range scored {
  81. if s.Priority < minPriority {
  82. minPriority = s.Priority
  83. }
  84. if s.Priority > maxPriority {
  85. maxPriority = s.Priority
  86. }
  87. sumPriority += s.Priority
  88. }
  89. plan.PriorityMin = minPriority
  90. plan.PriorityMax = maxPriority
  91. plan.PriorityAvg = sumPriority / float64(len(scored))
  92. }
  93. limit := plan.Budget
  94. if limit <= 0 || limit > len(scored) {
  95. limit = len(scored)
  96. }
  97. plan.Selected = scored[:limit]
  98. if len(plan.Selected) > 0 {
  99. plan.PriorityCutoff = plan.Selected[len(plan.Selected)-1].Priority
  100. }
  101. plan.DroppedByBudget = len(scored) - len(plan.Selected)
  102. return plan
  103. }
  104. func ScheduleCandidates(candidates []Candidate, policy Policy) []ScheduledCandidate {
  105. return BuildRefinementPlan(candidates, policy).Selected
  106. }
  107. func minFloat64(a, b float64) float64 {
  108. if a < b {
  109. return a
  110. }
  111. return b
  112. }