Wideband autonomous SDR analysis engine forked from sdr-visual-suite
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

225 行
7.7KB

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