Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

174 řádky
6.2KB

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