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

219 строки
7.4KB

  1. package pipeline
  2. import (
  3. "fmt"
  4. "sort"
  5. "strings"
  6. )
  7. const (
  8. RoleSurveillancePrimary = "surveillance-primary"
  9. RoleSurveillanceDerived = "surveillance-derived"
  10. RoleSurveillanceSupport = "surveillance-support"
  11. RolePresentation = "presentation"
  12. )
  13. // CandidateEvidenceState summarizes fused evidence semantics for a candidate.
  14. type CandidateEvidenceState struct {
  15. TotalLevelEntries int `json:"total_level_entries"`
  16. LevelCount int `json:"level_count"`
  17. DetectionLevelCount int `json:"detection_level_count"`
  18. PrimaryLevelCount int `json:"primary_level_count,omitempty"`
  19. DerivedLevelCount int `json:"derived_level_count,omitempty"`
  20. SupportLevelCount int `json:"support_level_count,omitempty"`
  21. PresentationLevelCount int `json:"presentation_level_count,omitempty"`
  22. Levels []string `json:"levels,omitempty"`
  23. Provenance []string `json:"provenance,omitempty"`
  24. Fused bool `json:"fused,omitempty"`
  25. DerivedOnly bool `json:"derived_only,omitempty"`
  26. MultiLevelConfirmed bool `json:"multi_level_confirmed,omitempty"`
  27. MultiLevelConfirmedHint string `json:"multi_level_confirmed_hint,omitempty"`
  28. }
  29. // EvidenceScoreDetails explains how evidence influenced refinement scoring.
  30. type EvidenceScoreDetails struct {
  31. RawScore float64 `json:"raw_score"`
  32. Weight float64 `json:"weight"`
  33. WeightedScore float64 `json:"weighted_score"`
  34. DetectionLevels int `json:"detection_levels"`
  35. PrimaryLevels int `json:"primary_levels,omitempty"`
  36. DerivedLevels int `json:"derived_levels,omitempty"`
  37. SupportLevels int `json:"support_levels,omitempty"`
  38. ProvenanceCount int `json:"provenance_count,omitempty"`
  39. DerivedOnly bool `json:"derived_only,omitempty"`
  40. MultiLevelConfirmed bool `json:"multi_level_confirmed,omitempty"`
  41. MultiLevelBonus float64 `json:"multi_level_bonus,omitempty"`
  42. ProvenanceBonus float64 `json:"provenance_bonus,omitempty"`
  43. DerivedPenalty float64 `json:"derived_penalty,omitempty"`
  44. StrategyBias float64 `json:"strategy_bias,omitempty"`
  45. }
  46. // IsPresentationLevel reports whether a level is intended only for presentation.
  47. func IsPresentationLevel(level AnalysisLevel) bool {
  48. role := strings.ToLower(strings.TrimSpace(level.Role))
  49. truth := strings.ToLower(strings.TrimSpace(level.Truth))
  50. name := strings.ToLower(strings.TrimSpace(level.Name))
  51. if role == RolePresentation {
  52. return true
  53. }
  54. if strings.Contains(role, "presentation") || strings.Contains(truth, "presentation") {
  55. return true
  56. }
  57. return strings.Contains(name, "presentation") || strings.Contains(name, "display")
  58. }
  59. // IsSupportLevel reports whether a level is a non-detection support level.
  60. func IsSupportLevel(level AnalysisLevel) bool {
  61. role := strings.ToLower(strings.TrimSpace(level.Role))
  62. if role == RoleSurveillanceSupport {
  63. return true
  64. }
  65. return strings.Contains(role, "surveillance-support") || strings.Contains(role, "support")
  66. }
  67. // IsDetectionLevel reports whether a level is intended for detection/analysis.
  68. func IsDetectionLevel(level AnalysisLevel) bool {
  69. if IsPresentationLevel(level) {
  70. return false
  71. }
  72. if IsSupportLevel(level) {
  73. return false
  74. }
  75. role := strings.ToLower(strings.TrimSpace(level.Role))
  76. truth := strings.ToLower(strings.TrimSpace(level.Truth))
  77. name := strings.ToLower(strings.TrimSpace(level.Name))
  78. switch role {
  79. case RoleSurveillancePrimary, RoleSurveillanceDerived:
  80. return true
  81. case RoleSurveillanceSupport:
  82. return false
  83. }
  84. if strings.Contains(truth, "surveillance") {
  85. return true
  86. }
  87. if role == "surveillance" || strings.HasPrefix(role, "surveillance-") {
  88. return true
  89. }
  90. return strings.Contains(name, "surveillance")
  91. }
  92. func isPrimarySurveillanceLevel(level AnalysisLevel) bool {
  93. role := strings.ToLower(strings.TrimSpace(level.Role))
  94. name := strings.ToLower(strings.TrimSpace(level.Name))
  95. if role == RoleSurveillancePrimary {
  96. return true
  97. }
  98. return role == "surveillance" || name == "surveillance"
  99. }
  100. func isDerivedSurveillanceLevel(level AnalysisLevel) bool {
  101. role := strings.ToLower(strings.TrimSpace(level.Role))
  102. name := strings.ToLower(strings.TrimSpace(level.Name))
  103. if role == RoleSurveillanceSupport {
  104. return false
  105. }
  106. if role == RoleSurveillanceDerived {
  107. return true
  108. }
  109. if strings.HasPrefix(role, "surveillance-") && role != "surveillance" {
  110. return true
  111. }
  112. if strings.HasPrefix(name, "surveillance-") && name != "surveillance" {
  113. return true
  114. }
  115. return strings.Contains(role, "lowres") || strings.Contains(name, "lowres") || strings.Contains(name, "derived")
  116. }
  117. func evidenceLevelKey(level AnalysisLevel) string {
  118. if level.Name != "" {
  119. return level.Name
  120. }
  121. if level.SampleRate > 0 && level.FFTSize > 0 {
  122. return fmt.Sprintf("sr%d-fft%d", level.SampleRate, level.FFTSize)
  123. }
  124. return "unknown"
  125. }
  126. // CandidateEvidenceStateFor builds a fused evidence state from a candidate.
  127. func CandidateEvidenceStateFor(candidate Candidate) CandidateEvidenceState {
  128. state := CandidateEvidenceState{}
  129. if len(candidate.Evidence) == 0 {
  130. return state
  131. }
  132. levelSet := map[string]struct{}{}
  133. provenanceSet := map[string]struct{}{}
  134. detectionLevels := map[string]struct{}{}
  135. primaryLevels := map[string]struct{}{}
  136. derivedLevels := map[string]struct{}{}
  137. presentationLevels := map[string]struct{}{}
  138. supportLevels := map[string]struct{}{}
  139. for _, ev := range candidate.Evidence {
  140. levelKey := evidenceLevelKey(ev.Level)
  141. levelSet[levelKey] = struct{}{}
  142. if ev.Provenance != "" {
  143. provenanceSet[ev.Provenance] = struct{}{}
  144. }
  145. if IsPresentationLevel(ev.Level) {
  146. presentationLevels[levelKey] = struct{}{}
  147. continue
  148. }
  149. if IsSupportLevel(ev.Level) {
  150. supportLevels[levelKey] = struct{}{}
  151. continue
  152. }
  153. if IsDetectionLevel(ev.Level) {
  154. detectionLevels[levelKey] = struct{}{}
  155. if isPrimarySurveillanceLevel(ev.Level) {
  156. primaryLevels[levelKey] = struct{}{}
  157. } else if isDerivedSurveillanceLevel(ev.Level) {
  158. derivedLevels[levelKey] = struct{}{}
  159. }
  160. }
  161. }
  162. state.TotalLevelEntries = len(candidate.Evidence)
  163. state.LevelCount = len(levelSet)
  164. state.DetectionLevelCount = len(detectionLevels)
  165. state.PrimaryLevelCount = len(primaryLevels)
  166. state.DerivedLevelCount = len(derivedLevels)
  167. state.SupportLevelCount = len(supportLevels)
  168. state.PresentationLevelCount = len(presentationLevels)
  169. state.Levels = sortedKeys(levelSet)
  170. state.Provenance = sortedKeys(provenanceSet)
  171. state.Fused = state.LevelCount > 1 || len(state.Provenance) > 1
  172. state.DerivedOnly = state.DerivedLevelCount > 0 && state.PrimaryLevelCount == 0 && state.DetectionLevelCount == state.DerivedLevelCount
  173. state.MultiLevelConfirmed = state.DetectionLevelCount >= 2
  174. if state.MultiLevelConfirmed {
  175. if state.PrimaryLevelCount > 0 && state.DerivedLevelCount > 0 {
  176. state.MultiLevelConfirmedHint = "primary+derived"
  177. } else {
  178. state.MultiLevelConfirmedHint = "multi-detection"
  179. }
  180. }
  181. return state
  182. }
  183. // RefreshCandidateEvidenceState updates the candidate's cached evidence summary.
  184. func RefreshCandidateEvidenceState(candidate *Candidate) {
  185. if candidate == nil {
  186. return
  187. }
  188. state := CandidateEvidenceStateFor(*candidate)
  189. if state.TotalLevelEntries == 0 {
  190. candidate.EvidenceState = nil
  191. return
  192. }
  193. candidate.EvidenceState = &state
  194. }
  195. func sortedKeys(src map[string]struct{}) []string {
  196. if len(src) == 0 {
  197. return nil
  198. }
  199. out := make([]string, 0, len(src))
  200. for k := range src {
  201. out = append(out, k)
  202. }
  203. sort.Strings(out)
  204. return out
  205. }