Wideband autonomous SDR analysis engine forked from sdr-visual-suite
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

293 líneas
8.4KB

  1. package pipeline
  2. import (
  3. "math"
  4. "strings"
  5. "time"
  6. )
  7. type HoldPolicy struct {
  8. BaseMs int `json:"base_ms"`
  9. RefinementMs int `json:"refinement_ms"`
  10. RecordMs int `json:"record_ms"`
  11. DecodeMs int `json:"decode_ms"`
  12. Profile string `json:"profile,omitempty"`
  13. Strategy string `json:"strategy,omitempty"`
  14. Reasons []string `json:"reasons,omitempty"`
  15. }
  16. type RefinementHold struct {
  17. Active map[int64]time.Time
  18. }
  19. type RefinementAdmission struct {
  20. Budget int `json:"budget"`
  21. BudgetSource string `json:"budget_source,omitempty"`
  22. HoldMs int `json:"hold_ms"`
  23. HoldSource string `json:"hold_source,omitempty"`
  24. Planned int `json:"planned"`
  25. Admitted int `json:"admitted"`
  26. Skipped int `json:"skipped"`
  27. Displaced int `json:"displaced"`
  28. PriorityCutoff float64 `json:"priority_cutoff,omitempty"`
  29. PriorityTier string `json:"priority_tier,omitempty"`
  30. Reason string `json:"reason,omitempty"`
  31. }
  32. type RefinementAdmissionResult struct {
  33. Plan RefinementPlan
  34. WorkItems []RefinementWorkItem
  35. Admitted []ScheduledCandidate
  36. Admission RefinementAdmission
  37. }
  38. func HoldPolicyFromPolicy(policy Policy) HoldPolicy {
  39. base := policy.DecisionHoldMs
  40. if base < 0 {
  41. base = 0
  42. }
  43. refMult := 1.0
  44. recMult := 1.0
  45. decMult := 1.0
  46. reasons := make([]string, 0, 2)
  47. profile := strings.ToLower(strings.TrimSpace(policy.Profile))
  48. strategy := strings.ToLower(strings.TrimSpace(policy.RefinementStrategy))
  49. archiveProfile := profileContains(profile, "archive")
  50. archiveStrategy := strategyContains(strategy, "archive")
  51. if archiveProfile || archiveStrategy {
  52. recMult *= 1.5
  53. decMult *= 1.1
  54. refMult *= 1.2
  55. if archiveProfile {
  56. reasons = append(reasons, HoldReasonProfileArchive)
  57. }
  58. if archiveStrategy {
  59. reasons = append(reasons, HoldReasonStrategyArchive)
  60. }
  61. }
  62. digitalProfile := profileContains(profile, "digital")
  63. digitalStrategy := strategyContains(strategy, "digital")
  64. if digitalProfile || digitalStrategy {
  65. decMult *= 1.6
  66. recMult *= 0.85
  67. refMult *= 1.1
  68. if digitalProfile {
  69. reasons = append(reasons, HoldReasonProfileDigital)
  70. }
  71. if digitalStrategy {
  72. reasons = append(reasons, HoldReasonStrategyDigital)
  73. }
  74. }
  75. if profileContains(profile, "aggressive") {
  76. refMult *= 1.15
  77. reasons = append(reasons, HoldReasonProfileAggressive)
  78. }
  79. if strategyContains(strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)), "multi") {
  80. refMult *= 1.1
  81. reasons = append(reasons, HoldReasonStrategyMultiRes)
  82. }
  83. return HoldPolicy{
  84. BaseMs: base,
  85. RefinementMs: scaleHold(base, refMult),
  86. RecordMs: scaleHold(base, recMult),
  87. DecodeMs: scaleHold(base, decMult),
  88. Profile: policy.Profile,
  89. Strategy: policy.RefinementStrategy,
  90. Reasons: reasons,
  91. }
  92. }
  93. func AdmitRefinementPlan(plan RefinementPlan, policy Policy, now time.Time, hold *RefinementHold) RefinementAdmissionResult {
  94. ranked := plan.Ranked
  95. if len(ranked) == 0 {
  96. ranked = plan.Selected
  97. }
  98. workItems := append([]RefinementWorkItem(nil), plan.WorkItems...)
  99. admission := RefinementAdmission{
  100. Budget: plan.Budget,
  101. BudgetSource: plan.BudgetSource,
  102. }
  103. if len(ranked) == 0 {
  104. admission.Reason = ReasonAdmissionNoCandidates
  105. return RefinementAdmissionResult{Plan: plan, WorkItems: workItems, Admission: admission}
  106. }
  107. holdPolicy := HoldPolicyFromPolicy(policy)
  108. admission.HoldMs = holdPolicy.RefinementMs
  109. admission.HoldSource = "resources.decision_hold_ms"
  110. if len(holdPolicy.Reasons) > 0 {
  111. admission.HoldSource += ":" + strings.Join(holdPolicy.Reasons, ",")
  112. }
  113. planned := len(ranked)
  114. admission.Planned = planned
  115. selected := map[int64]struct{}{}
  116. held := map[int64]struct{}{}
  117. if hold != nil {
  118. purgeHold(hold.Active, now)
  119. for id := range hold.Active {
  120. if rankedContains(ranked, id) {
  121. selected[id] = struct{}{}
  122. held[id] = struct{}{}
  123. }
  124. }
  125. }
  126. limit := plan.Budget
  127. if limit <= 0 || limit > planned {
  128. limit = planned
  129. }
  130. if len(selected) > limit {
  131. limit = len(selected)
  132. if limit > planned {
  133. limit = planned
  134. }
  135. }
  136. for _, cand := range ranked {
  137. if len(selected) >= limit {
  138. break
  139. }
  140. if _, ok := selected[cand.Candidate.ID]; ok {
  141. continue
  142. }
  143. selected[cand.Candidate.ID] = struct{}{}
  144. }
  145. if hold != nil && admission.HoldMs > 0 {
  146. until := now.Add(time.Duration(admission.HoldMs) * time.Millisecond)
  147. if hold.Active == nil {
  148. hold.Active = map[int64]time.Time{}
  149. }
  150. for id := range selected {
  151. hold.Active[id] = until
  152. }
  153. }
  154. admitted := make([]ScheduledCandidate, 0, len(selected))
  155. for _, cand := range ranked {
  156. if _, ok := selected[cand.Candidate.ID]; ok {
  157. admitted = append(admitted, cand)
  158. }
  159. }
  160. admission.Admitted = len(admitted)
  161. admission.Skipped = planned - admission.Admitted
  162. if admission.Skipped < 0 {
  163. admission.Skipped = 0
  164. }
  165. displaced := map[int64]struct{}{}
  166. if len(admitted) > 0 {
  167. admission.PriorityCutoff = admitted[len(admitted)-1].Priority
  168. for _, cand := range ranked {
  169. if _, ok := selected[cand.Candidate.ID]; ok {
  170. continue
  171. }
  172. if cand.Priority >= admission.PriorityCutoff {
  173. displaced[cand.Candidate.ID] = struct{}{}
  174. }
  175. }
  176. }
  177. admission.Displaced = len(displaced)
  178. admission.PriorityTier = PriorityTierFromRange(admission.PriorityCutoff, plan.PriorityMin, plan.PriorityMax)
  179. if admission.PriorityCutoff > 0 {
  180. admission.Reason = admissionReason("admission:budget", policy, holdPolicy, "budget:"+slugToken(plan.BudgetSource))
  181. }
  182. plan.Selected = admitted
  183. plan.PriorityCutoff = admission.PriorityCutoff
  184. plan.DroppedByBudget = admission.Skipped
  185. for i := range workItems {
  186. item := &workItems[i]
  187. if item.Status != RefinementStatusPlanned {
  188. continue
  189. }
  190. id := item.Candidate.ID
  191. if _, ok := selected[id]; ok {
  192. item.Status = RefinementStatusAdmitted
  193. item.Reason = RefinementReasonAdmitted
  194. class := AdmissionClassAdmit
  195. reason := "refinement:admit:budget"
  196. if _, wasHeld := held[id]; wasHeld {
  197. class = AdmissionClassHold
  198. reason = "refinement:admit:hold"
  199. }
  200. if item.Admission == nil {
  201. item.Admission = &PriorityAdmission{Basis: "refinement"}
  202. }
  203. item.Admission.Class = class
  204. item.Admission.Score = item.Priority
  205. item.Admission.Cutoff = admission.PriorityCutoff
  206. item.Admission.Tier = PriorityTierFromRange(item.Priority, plan.PriorityMin, plan.PriorityMax)
  207. item.Admission.Reason = admissionReason(reason, policy, holdPolicy, "budget:"+slugToken(plan.BudgetSource))
  208. continue
  209. }
  210. if _, ok := displaced[id]; ok {
  211. item.Status = RefinementStatusDisplaced
  212. item.Reason = RefinementReasonDisplaced
  213. if item.Admission == nil {
  214. item.Admission = &PriorityAdmission{Basis: "refinement"}
  215. }
  216. item.Admission.Class = AdmissionClassDisplace
  217. item.Admission.Score = item.Priority
  218. item.Admission.Cutoff = admission.PriorityCutoff
  219. item.Admission.Tier = PriorityTierFromRange(item.Priority, plan.PriorityMin, plan.PriorityMax)
  220. item.Admission.Reason = admissionReason("refinement:displace:hold", policy, holdPolicy, "pressure:hold", "budget:"+slugToken(plan.BudgetSource))
  221. continue
  222. }
  223. item.Status = RefinementStatusSkipped
  224. item.Reason = RefinementReasonBudget
  225. if item.Admission == nil {
  226. item.Admission = &PriorityAdmission{Basis: "refinement"}
  227. }
  228. item.Admission.Class = AdmissionClassDefer
  229. item.Admission.Score = item.Priority
  230. item.Admission.Cutoff = admission.PriorityCutoff
  231. item.Admission.Tier = PriorityTierFromRange(item.Priority, plan.PriorityMin, plan.PriorityMax)
  232. item.Admission.Reason = admissionReason("refinement:skip:budget", policy, holdPolicy, "pressure:budget", "budget:"+slugToken(plan.BudgetSource))
  233. }
  234. return RefinementAdmissionResult{
  235. Plan: plan,
  236. WorkItems: workItems,
  237. Admitted: admitted,
  238. Admission: admission,
  239. }
  240. }
  241. func purgeHold(active map[int64]time.Time, now time.Time) {
  242. for id, until := range active {
  243. if now.After(until) {
  244. delete(active, id)
  245. }
  246. }
  247. }
  248. func rankedContains(items []ScheduledCandidate, id int64) bool {
  249. for _, item := range items {
  250. if item.Candidate.ID == id {
  251. return true
  252. }
  253. }
  254. return false
  255. }
  256. func scaleHold(base int, mult float64) int {
  257. if base <= 0 {
  258. return 0
  259. }
  260. return int(math.Round(float64(base) * mult))
  261. }
  262. func profileContains(profile string, token string) bool {
  263. if profile == "" || token == "" {
  264. return false
  265. }
  266. return strings.Contains(profile, strings.ToLower(token))
  267. }
  268. func strategyContains(strategy string, token string) bool {
  269. if strategy == "" || token == "" {
  270. return false
  271. }
  272. return strings.Contains(strategy, strings.ToLower(token))
  273. }