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

308 рядки
8.8KB

  1. package main
  2. import (
  3. "sort"
  4. "strings"
  5. "sdr-wideband-suite/internal/pipeline"
  6. )
  7. type WindowSummary struct {
  8. Refinement *RefinementWindowStats `json:"refinement,omitempty"`
  9. MonitorWindows []pipeline.MonitorWindowStats `json:"monitor_windows,omitempty"`
  10. Outcomes *WindowOutcomeSummary `json:"outcomes,omitempty"`
  11. }
  12. type WindowOutcomeSummary struct {
  13. Windows []MonitorWindowOutcome `json:"windows,omitempty"`
  14. Zones []MonitorZoneOutcome `json:"zones,omitempty"`
  15. }
  16. type MonitorWindowOutcome struct {
  17. Index int `json:"index"`
  18. Label string `json:"label,omitempty"`
  19. Zone string `json:"zone,omitempty"`
  20. Refinement OutcomeCounts `json:"refinement,omitempty"`
  21. Record OutcomeCounts `json:"record,omitempty"`
  22. Decode OutcomeCounts `json:"decode,omitempty"`
  23. }
  24. type MonitorZoneOutcome struct {
  25. Zone string `json:"zone"`
  26. Refinement OutcomeCounts `json:"refinement,omitempty"`
  27. Record OutcomeCounts `json:"record,omitempty"`
  28. Decode OutcomeCounts `json:"decode,omitempty"`
  29. }
  30. type OutcomeCounts struct {
  31. Admit int `json:"admit,omitempty"`
  32. Hold int `json:"hold,omitempty"`
  33. Displace int `json:"displace,omitempty"`
  34. Defer int `json:"defer,omitempty"`
  35. Drop int `json:"drop,omitempty"`
  36. Enabled int `json:"enabled,omitempty"`
  37. }
  38. func (o *OutcomeCounts) addClass(class string) {
  39. switch class {
  40. case pipeline.AdmissionClassAdmit:
  41. o.Admit++
  42. case pipeline.AdmissionClassHold:
  43. o.Hold++
  44. case pipeline.AdmissionClassDisplace:
  45. o.Displace++
  46. case pipeline.AdmissionClassDefer:
  47. o.Defer++
  48. case pipeline.AdmissionClassDrop:
  49. o.Drop++
  50. }
  51. }
  52. func (o *OutcomeCounts) addEnabled(enabled bool) {
  53. if enabled {
  54. o.Enabled++
  55. }
  56. }
  57. func (o OutcomeCounts) hasAny() bool {
  58. return o.Admit > 0 || o.Hold > 0 || o.Displace > 0 || o.Defer > 0 || o.Drop > 0 || o.Enabled > 0
  59. }
  60. func (o *OutcomeCounts) addTotals(in OutcomeCounts) {
  61. o.Admit += in.Admit
  62. o.Hold += in.Hold
  63. o.Displace += in.Displace
  64. o.Defer += in.Defer
  65. o.Drop += in.Drop
  66. o.Enabled += in.Enabled
  67. }
  68. func buildWindowSummary(plan pipeline.RefinementPlan, refinementWindows []pipeline.RefinementWindow, candidates []pipeline.Candidate, workItems []pipeline.RefinementWorkItem, decisions []pipeline.SignalDecision) *WindowSummary {
  69. refinementStats := buildWindowStats(refinementWindows)
  70. monitorSummary := buildMonitorWindowSummary(plan.MonitorWindows, plan.MonitorWindowStats, candidates)
  71. outcomes := buildWindowOutcomeSummary(plan.MonitorWindows, plan.MonitorWindowStats, workItems, decisions)
  72. if refinementStats == nil && len(monitorSummary) == 0 && outcomes == nil {
  73. return nil
  74. }
  75. return &WindowSummary{
  76. Refinement: refinementStats,
  77. MonitorWindows: monitorSummary,
  78. Outcomes: outcomes,
  79. }
  80. }
  81. func buildMonitorWindowSummary(windows []pipeline.MonitorWindow, stats []pipeline.MonitorWindowStats, candidates []pipeline.Candidate) []pipeline.MonitorWindowStats {
  82. var summary []pipeline.MonitorWindowStats
  83. switch {
  84. case len(stats) > 0:
  85. summary = append([]pipeline.MonitorWindowStats(nil), stats...)
  86. case len(windows) > 0:
  87. summary = make([]pipeline.MonitorWindowStats, 0, len(windows))
  88. for _, win := range windows {
  89. summary = append(summary, pipeline.MonitorWindowStats{
  90. Index: win.Index,
  91. Label: win.Label,
  92. Zone: win.Zone,
  93. Source: win.Source,
  94. StartHz: win.StartHz,
  95. EndHz: win.EndHz,
  96. CenterHz: win.CenterHz,
  97. SpanHz: win.SpanHz,
  98. Priority: win.Priority,
  99. PriorityBias: win.PriorityBias,
  100. RecordBias: win.RecordBias,
  101. DecodeBias: win.DecodeBias,
  102. AutoRecord: win.AutoRecord,
  103. AutoDecode: win.AutoDecode,
  104. })
  105. }
  106. default:
  107. return nil
  108. }
  109. if len(candidates) > 0 && len(summary) > 0 {
  110. windowsForMatch := windows
  111. if len(windowsForMatch) == 0 {
  112. windowsForMatch = monitorWindowsFromStats(summary)
  113. }
  114. if len(windowsForMatch) > 0 {
  115. counts := map[int]int{}
  116. total := 0
  117. for _, cand := range candidates {
  118. matches := cand.MonitorMatches
  119. if len(matches) == 0 {
  120. matches = pipeline.MonitorWindowMatchesForCandidate(windowsForMatch, cand)
  121. }
  122. for _, match := range matches {
  123. counts[match.Index]++
  124. total++
  125. }
  126. }
  127. if total > 0 {
  128. for i := range summary {
  129. if summary[i].Candidates == 0 {
  130. summary[i].Candidates = counts[summary[i].Index]
  131. }
  132. }
  133. }
  134. }
  135. }
  136. sort.Slice(summary, func(i, j int) bool {
  137. return summary[i].Index < summary[j].Index
  138. })
  139. return summary
  140. }
  141. func buildWindowOutcomeSummary(windows []pipeline.MonitorWindow, stats []pipeline.MonitorWindowStats, workItems []pipeline.RefinementWorkItem, decisions []pipeline.SignalDecision) *WindowOutcomeSummary {
  142. base := windows
  143. if len(base) == 0 && len(stats) > 0 {
  144. base = monitorWindowsFromStats(stats)
  145. }
  146. if len(base) == 0 {
  147. return nil
  148. }
  149. outcomes := make([]MonitorWindowOutcome, 0, len(base))
  150. index := make(map[int]int, len(base))
  151. for _, win := range base {
  152. outcomes = append(outcomes, MonitorWindowOutcome{
  153. Index: win.Index,
  154. Label: win.Label,
  155. Zone: win.Zone,
  156. })
  157. index[win.Index] = len(outcomes) - 1
  158. }
  159. windowsForMatch := base
  160. if len(workItems) > 0 {
  161. for _, item := range workItems {
  162. class := outcomeClassForWorkItem(item)
  163. if class == "" {
  164. continue
  165. }
  166. matches := item.Candidate.MonitorMatches
  167. if len(matches) == 0 {
  168. matches = pipeline.MonitorWindowMatchesForCandidate(windowsForMatch, item.Candidate)
  169. }
  170. for _, match := range matches {
  171. if idx, ok := index[match.Index]; ok {
  172. outcomes[idx].Refinement.addClass(class)
  173. }
  174. }
  175. }
  176. }
  177. if len(decisions) > 0 {
  178. for _, decision := range decisions {
  179. if match := decisionWindowMatch(decision, "record"); match != nil {
  180. if idx, ok := index[match.Index]; ok {
  181. outcomes[idx].Record.addEnabled(decision.ShouldRecord)
  182. if decision.RecordAdmission != nil {
  183. outcomes[idx].Record.addClass(decision.RecordAdmission.Class)
  184. }
  185. }
  186. }
  187. if match := decisionWindowMatch(decision, "decode"); match != nil {
  188. if idx, ok := index[match.Index]; ok {
  189. outcomes[idx].Decode.addEnabled(decision.ShouldAutoDecode)
  190. if decision.DecodeAdmission != nil {
  191. outcomes[idx].Decode.addClass(decision.DecodeAdmission.Class)
  192. }
  193. }
  194. }
  195. }
  196. }
  197. hasOutcome := false
  198. for i := range outcomes {
  199. if outcomes[i].Refinement.hasAny() || outcomes[i].Record.hasAny() || outcomes[i].Decode.hasAny() {
  200. hasOutcome = true
  201. break
  202. }
  203. }
  204. if !hasOutcome {
  205. return nil
  206. }
  207. sort.Slice(outcomes, func(i, j int) bool {
  208. return outcomes[i].Index < outcomes[j].Index
  209. })
  210. zoneIndex := map[string]int{}
  211. zones := make([]MonitorZoneOutcome, 0, len(outcomes))
  212. for _, outcome := range outcomes {
  213. zone := strings.TrimSpace(outcome.Zone)
  214. if zone == "" {
  215. continue
  216. }
  217. idx, ok := zoneIndex[zone]
  218. if !ok {
  219. zones = append(zones, MonitorZoneOutcome{Zone: zone})
  220. idx = len(zones) - 1
  221. zoneIndex[zone] = idx
  222. }
  223. zones[idx].Refinement.addTotals(outcome.Refinement)
  224. zones[idx].Record.addTotals(outcome.Record)
  225. zones[idx].Decode.addTotals(outcome.Decode)
  226. }
  227. if len(zones) > 0 {
  228. sort.Slice(zones, func(i, j int) bool {
  229. return zones[i].Zone < zones[j].Zone
  230. })
  231. }
  232. return &WindowOutcomeSummary{Windows: outcomes, Zones: zones}
  233. }
  234. func outcomeClassForWorkItem(item pipeline.RefinementWorkItem) string {
  235. if item.Admission != nil && item.Admission.Class != "" {
  236. return item.Admission.Class
  237. }
  238. switch item.Status {
  239. case pipeline.RefinementStatusAdmitted, pipeline.RefinementStatusRunning, pipeline.RefinementStatusCompleted:
  240. return pipeline.AdmissionClassAdmit
  241. case pipeline.RefinementStatusDisplaced:
  242. return pipeline.AdmissionClassDisplace
  243. case pipeline.RefinementStatusSkipped:
  244. return pipeline.AdmissionClassDefer
  245. case pipeline.RefinementStatusDropped:
  246. return pipeline.AdmissionClassDrop
  247. default:
  248. return ""
  249. }
  250. }
  251. func decisionWindowMatch(decision pipeline.SignalDecision, action string) *pipeline.MonitorWindowMatch {
  252. switch strings.ToLower(strings.TrimSpace(action)) {
  253. case "record":
  254. if decision.RecordWindow != nil {
  255. return decision.RecordWindow
  256. }
  257. case "decode":
  258. if decision.DecodeWindow != nil {
  259. return decision.DecodeWindow
  260. }
  261. }
  262. return decision.MonitorDetail
  263. }
  264. func monitorWindowsFromStats(stats []pipeline.MonitorWindowStats) []pipeline.MonitorWindow {
  265. if len(stats) == 0 {
  266. return nil
  267. }
  268. windows := make([]pipeline.MonitorWindow, 0, len(stats))
  269. for _, stat := range stats {
  270. windows = append(windows, pipeline.MonitorWindow{
  271. Index: stat.Index,
  272. Label: stat.Label,
  273. Zone: stat.Zone,
  274. Source: stat.Source,
  275. StartHz: stat.StartHz,
  276. EndHz: stat.EndHz,
  277. CenterHz: stat.CenterHz,
  278. SpanHz: stat.SpanHz,
  279. Priority: stat.Priority,
  280. PriorityBias: stat.PriorityBias,
  281. RecordBias: stat.RecordBias,
  282. DecodeBias: stat.DecodeBias,
  283. AutoRecord: stat.AutoRecord,
  284. AutoDecode: stat.AutoDecode,
  285. })
  286. }
  287. return windows
  288. }