| @@ -143,6 +143,7 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||||
| "mode": policy.Mode, | "mode": policy.Mode, | ||||
| "intent": policy.Intent, | "intent": policy.Intent, | ||||
| "surveillance_strategy": policy.SurveillanceStrategy, | "surveillance_strategy": policy.SurveillanceStrategy, | ||||
| "surveillance_detection": policy.SurveillanceDetection, | |||||
| "refinement_strategy": policy.RefinementStrategy, | "refinement_strategy": policy.RefinementStrategy, | ||||
| "monitor_center_hz": policy.MonitorCenterHz, | "monitor_center_hz": policy.MonitorCenterHz, | ||||
| "monitor_start_hz": policy.MonitorStartHz, | "monitor_start_hz": policy.MonitorStartHz, | ||||
| @@ -178,21 +179,24 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||||
| candidateEvidence := buildCandidateEvidenceSummary(snap.surveillance.Candidates) | candidateEvidence := buildCandidateEvidenceSummary(snap.surveillance.Candidates) | ||||
| candidateEvidenceStates := buildCandidateEvidenceStateSummary(snap.surveillance.Candidates) | candidateEvidenceStates := buildCandidateEvidenceStateSummary(snap.surveillance.Candidates) | ||||
| out := map[string]any{ | out := map[string]any{ | ||||
| "plan": snap.refinement.Input.Plan, | |||||
| "windows": snap.refinement.Input.Windows, | |||||
| "window_stats": windowStats, | |||||
| "request": snap.refinement.Input.Request, | |||||
| "context": snap.refinement.Input.Context, | |||||
| "detail_level": snap.refinement.Input.Detail, | |||||
| "arbitration": arbitration, | |||||
| "work_items": snap.refinement.Input.WorkItems, | |||||
| "candidates": len(snap.refinement.Input.Candidates), | |||||
| "scheduled": len(snap.refinement.Input.Scheduled), | |||||
| "signals": len(snap.refinement.Result.Signals), | |||||
| "decisions": len(snap.refinement.Result.Decisions), | |||||
| "surveillance_level": snap.surveillance.Level, | |||||
| "surveillance_levels": snap.surveillance.Levels, | |||||
| "surveillance_level_set": levelSet, | |||||
| "plan": snap.refinement.Input.Plan, | |||||
| "windows": snap.refinement.Input.Windows, | |||||
| "window_stats": windowStats, | |||||
| "request": snap.refinement.Input.Request, | |||||
| "context": snap.refinement.Input.Context, | |||||
| "detail_level": snap.refinement.Input.Detail, | |||||
| "arbitration": arbitration, | |||||
| "work_items": snap.refinement.Input.WorkItems, | |||||
| "candidates": len(snap.refinement.Input.Candidates), | |||||
| "scheduled": len(snap.refinement.Input.Scheduled), | |||||
| "signals": len(snap.refinement.Result.Signals), | |||||
| "decisions": len(snap.refinement.Result.Decisions), | |||||
| "surveillance_level": snap.surveillance.Level, | |||||
| "surveillance_levels": snap.surveillance.Levels, | |||||
| "surveillance_level_set": levelSet, | |||||
| "surveillance_detection_policy": snap.surveillance.DetectionPolicy, | |||||
| "surveillance_detection_levels": levelSet.Detection, | |||||
| "surveillance_support_levels": levelSet.Support, | |||||
| "surveillance_active_levels": func() []pipeline.AnalysisLevel { | "surveillance_active_levels": func() []pipeline.AnalysisLevel { | ||||
| if len(levelSet.All) > 0 { | if len(levelSet.All) > 0 { | ||||
| return levelSet.All | return levelSet.All | ||||
| @@ -36,7 +36,7 @@ type CandidateEvidenceStateSummary struct { | |||||
| } | } | ||||
| func buildSurveillanceLevelSummaries(set pipeline.SurveillanceLevelSet, spectra []pipeline.SurveillanceLevelSpectrum) map[string]SurveillanceLevelSummary { | func buildSurveillanceLevelSummaries(set pipeline.SurveillanceLevelSet, spectra []pipeline.SurveillanceLevelSpectrum) map[string]SurveillanceLevelSummary { | ||||
| if set.Primary.Name == "" && len(set.Derived) == 0 && set.Presentation.Name == "" && len(set.All) == 0 { | |||||
| if set.Primary.Name == "" && len(set.Derived) == 0 && len(set.Support) == 0 && set.Presentation.Name == "" && len(set.All) == 0 { | |||||
| return nil | return nil | ||||
| } | } | ||||
| bins := map[string]int{} | bins := map[string]int{} | ||||
| @@ -54,6 +54,9 @@ func buildSurveillanceLevelSummaries(set pipeline.SurveillanceLevelSet, spectra | |||||
| if len(set.Derived) > 0 { | if len(set.Derived) > 0 { | ||||
| levels = append(levels, set.Derived...) | levels = append(levels, set.Derived...) | ||||
| } | } | ||||
| if len(set.Support) > 0 { | |||||
| levels = append(levels, set.Support...) | |||||
| } | |||||
| if set.Presentation.Name != "" { | if set.Presentation.Name != "" { | ||||
| levels = append(levels, set.Presentation) | levels = append(levels, set.Presentation) | ||||
| } | } | ||||
| @@ -82,12 +82,13 @@ type surveillanceLevelSpec struct { | |||||
| } | } | ||||
| type surveillancePlan struct { | type surveillancePlan struct { | ||||
| Primary pipeline.AnalysisLevel | |||||
| Levels []pipeline.AnalysisLevel | |||||
| LevelSet pipeline.SurveillanceLevelSet | |||||
| Presentation pipeline.AnalysisLevel | |||||
| Context pipeline.AnalysisContext | |||||
| Specs []surveillanceLevelSpec | |||||
| Primary pipeline.AnalysisLevel | |||||
| Levels []pipeline.AnalysisLevel | |||||
| LevelSet pipeline.SurveillanceLevelSet | |||||
| Presentation pipeline.AnalysisLevel | |||||
| Context pipeline.AnalysisContext | |||||
| DetectionPolicy pipeline.SurveillanceDetectionPolicy | |||||
| Specs []surveillanceLevelSpec | |||||
| } | } | ||||
| const derivedIDBlock = int64(1_000_000_000) | const derivedIDBlock = int64(1_000_000_000) | ||||
| @@ -442,18 +443,19 @@ func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.S | |||||
| candidates := pipeline.FuseCandidates(primaryCandidates, derivedCandidates) | candidates := pipeline.FuseCandidates(primaryCandidates, derivedCandidates) | ||||
| scheduled := pipeline.ScheduleCandidates(candidates, policy) | scheduled := pipeline.ScheduleCandidates(candidates, policy) | ||||
| return pipeline.SurveillanceResult{ | return pipeline.SurveillanceResult{ | ||||
| Level: plan.Primary, | |||||
| Levels: plan.Levels, | |||||
| LevelSet: plan.LevelSet, | |||||
| DisplayLevel: plan.Presentation, | |||||
| Context: plan.Context, | |||||
| Spectra: art.surveillanceSpectra, | |||||
| Candidates: candidates, | |||||
| Scheduled: scheduled, | |||||
| Finished: art.finished, | |||||
| Signals: art.detected, | |||||
| NoiseFloor: art.noiseFloor, | |||||
| Thresholds: art.thresholds, | |||||
| Level: plan.Primary, | |||||
| Levels: plan.Levels, | |||||
| LevelSet: plan.LevelSet, | |||||
| DetectionPolicy: plan.DetectionPolicy, | |||||
| DisplayLevel: plan.Presentation, | |||||
| Context: plan.Context, | |||||
| Spectra: art.surveillanceSpectra, | |||||
| Candidates: candidates, | |||||
| Scheduled: scheduled, | |||||
| Finished: art.finished, | |||||
| Signals: art.detected, | |||||
| NoiseFloor: art.noiseFloor, | |||||
| Thresholds: art.thresholds, | |||||
| } | } | ||||
| } | } | ||||
| @@ -862,11 +864,13 @@ func (rt *dspRuntime) buildSurveillancePlan(policy pipeline.Policy) surveillance | |||||
| baseFFT = rt.cfg.FFTSize | baseFFT = rt.cfg.FFTSize | ||||
| } | } | ||||
| span := spanForPolicy(policy, float64(baseRate)) | span := spanForPolicy(policy, float64(baseRate)) | ||||
| primary := analysisLevel("surveillance", "surveillance", "surveillance", baseRate, baseFFT, rt.cfg.CenterHz, span, "baseband", 1, baseRate) | |||||
| detectionPolicy := pipeline.SurveillanceDetectionPolicyFromPolicy(policy) | |||||
| primary := analysisLevel("surveillance", pipeline.RoleSurveillancePrimary, "surveillance", baseRate, baseFFT, rt.cfg.CenterHz, span, "baseband", 1, baseRate) | |||||
| levels := []pipeline.AnalysisLevel{primary} | levels := []pipeline.AnalysisLevel{primary} | ||||
| specs := []surveillanceLevelSpec{{Level: primary, Decim: 1, AllowGPU: true}} | specs := []surveillanceLevelSpec{{Level: primary, Decim: 1, AllowGPU: true}} | ||||
| context := pipeline.AnalysisContext{Surveillance: primary} | context := pipeline.AnalysisContext{Surveillance: primary} | ||||
| derivedLevels := make([]pipeline.AnalysisLevel, 0, 2) | derivedLevels := make([]pipeline.AnalysisLevel, 0, 2) | ||||
| supportLevels := make([]pipeline.AnalysisLevel, 0, 2) | |||||
| strategy := strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)) | strategy := strings.ToLower(strings.TrimSpace(policy.SurveillanceStrategy)) | ||||
| switch strategy { | switch strategy { | ||||
| @@ -876,36 +880,51 @@ func (rt *dspRuntime) buildSurveillancePlan(policy pipeline.Policy) surveillance | |||||
| derivedFFT := baseFFT / decim | derivedFFT := baseFFT / decim | ||||
| if derivedRate >= 200000 && derivedFFT >= 256 { | if derivedRate >= 200000 && derivedFFT >= 256 { | ||||
| derivedSpan := spanForPolicy(policy, float64(derivedRate)) | derivedSpan := spanForPolicy(policy, float64(derivedRate)) | ||||
| derived := analysisLevel("surveillance-lowres", "surveillance-lowres", "surveillance", derivedRate, derivedFFT, rt.cfg.CenterHz, derivedSpan, "decimated", decim, baseRate) | |||||
| levels = append(levels, derived) | |||||
| role := pipeline.RoleSurveillanceSupport | |||||
| if detectionPolicy.DerivedDetectionEnabled { | |||||
| role = pipeline.RoleSurveillanceDerived | |||||
| } | |||||
| derived := analysisLevel("surveillance-lowres", role, "surveillance", derivedRate, derivedFFT, rt.cfg.CenterHz, derivedSpan, "decimated", decim, baseRate) | |||||
| if detectionPolicy.DerivedDetectionEnabled { | |||||
| levels = append(levels, derived) | |||||
| derivedLevels = append(derivedLevels, derived) | |||||
| } else { | |||||
| supportLevels = append(supportLevels, derived) | |||||
| } | |||||
| specs = append(specs, surveillanceLevelSpec{Level: derived, Decim: decim, AllowGPU: false}) | specs = append(specs, surveillanceLevelSpec{Level: derived, Decim: decim, AllowGPU: false}) | ||||
| context.Derived = append(context.Derived, derived) | context.Derived = append(context.Derived, derived) | ||||
| derivedLevels = append(derivedLevels, derived) | |||||
| } | } | ||||
| } | } | ||||
| presentation := analysisLevel("presentation", "presentation", "presentation", baseRate, rt.cfg.Surveillance.DisplayBins, rt.cfg.CenterHz, span, "display", 1, baseRate) | |||||
| presentation := analysisLevel("presentation", pipeline.RolePresentation, "presentation", baseRate, rt.cfg.Surveillance.DisplayBins, rt.cfg.CenterHz, span, "display", 1, baseRate) | |||||
| context.Presentation = presentation | context.Presentation = presentation | ||||
| levelSet := pipeline.SurveillanceLevelSet{ | levelSet := pipeline.SurveillanceLevelSet{ | ||||
| Primary: primary, | Primary: primary, | ||||
| Derived: append([]pipeline.AnalysisLevel(nil), derivedLevels...), | Derived: append([]pipeline.AnalysisLevel(nil), derivedLevels...), | ||||
| Support: append([]pipeline.AnalysisLevel(nil), supportLevels...), | |||||
| Presentation: presentation, | Presentation: presentation, | ||||
| } | } | ||||
| allLevels := make([]pipeline.AnalysisLevel, 0, 1+len(derivedLevels)+1) | |||||
| detectionLevels := make([]pipeline.AnalysisLevel, 0, 1+len(derivedLevels)) | |||||
| detectionLevels = append(detectionLevels, primary) | |||||
| detectionLevels = append(detectionLevels, derivedLevels...) | |||||
| levelSet.Detection = detectionLevels | |||||
| allLevels := make([]pipeline.AnalysisLevel, 0, 1+len(derivedLevels)+len(supportLevels)+1) | |||||
| allLevels = append(allLevels, primary) | allLevels = append(allLevels, primary) | ||||
| allLevels = append(allLevels, derivedLevels...) | allLevels = append(allLevels, derivedLevels...) | ||||
| allLevels = append(allLevels, supportLevels...) | |||||
| if presentation.Name != "" { | if presentation.Name != "" { | ||||
| allLevels = append(allLevels, presentation) | allLevels = append(allLevels, presentation) | ||||
| } | } | ||||
| levelSet.All = allLevels | levelSet.All = allLevels | ||||
| return surveillancePlan{ | return surveillancePlan{ | ||||
| Primary: primary, | |||||
| Levels: levels, | |||||
| LevelSet: levelSet, | |||||
| Presentation: presentation, | |||||
| Context: context, | |||||
| Specs: specs, | |||||
| Primary: primary, | |||||
| Levels: levels, | |||||
| LevelSet: levelSet, | |||||
| Presentation: presentation, | |||||
| Context: context, | |||||
| DetectionPolicy: detectionPolicy, | |||||
| Specs: specs, | |||||
| } | } | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ package config | |||||
| import ( | import ( | ||||
| "math" | "math" | ||||
| "os" | "os" | ||||
| "strings" | |||||
| "time" | "time" | ||||
| "gopkg.in/yaml.v3" | "gopkg.in/yaml.v3" | ||||
| @@ -87,11 +88,12 @@ type PipelineConfig struct { | |||||
| } | } | ||||
| type SurveillanceConfig struct { | type SurveillanceConfig struct { | ||||
| AnalysisFFTSize int `yaml:"analysis_fft_size" json:"analysis_fft_size"` | |||||
| FrameRate int `yaml:"frame_rate" json:"frame_rate"` | |||||
| Strategy string `yaml:"strategy" json:"strategy"` | |||||
| DisplayBins int `yaml:"display_bins" json:"display_bins"` | |||||
| DisplayFPS int `yaml:"display_fps" json:"display_fps"` | |||||
| AnalysisFFTSize int `yaml:"analysis_fft_size" json:"analysis_fft_size"` | |||||
| FrameRate int `yaml:"frame_rate" json:"frame_rate"` | |||||
| Strategy string `yaml:"strategy" json:"strategy"` | |||||
| DisplayBins int `yaml:"display_bins" json:"display_bins"` | |||||
| DisplayFPS int `yaml:"display_fps" json:"display_fps"` | |||||
| DerivedDetection string `yaml:"derived_detection" json:"derived_detection"` | |||||
| } | } | ||||
| type RefinementConfig struct { | type RefinementConfig struct { | ||||
| @@ -170,11 +172,12 @@ func Default() Config { | |||||
| }, | }, | ||||
| }, | }, | ||||
| Surveillance: SurveillanceConfig{ | Surveillance: SurveillanceConfig{ | ||||
| AnalysisFFTSize: 2048, | |||||
| FrameRate: 15, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 15, | |||||
| AnalysisFFTSize: 2048, | |||||
| FrameRate: 15, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 15, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: RefinementConfig{ | Refinement: RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -198,11 +201,12 @@ func Default() Config { | |||||
| Description: "Current single-band pipeline behavior", | Description: "Current single-band pipeline behavior", | ||||
| Pipeline: &PipelineConfig{Mode: "legacy", Profile: "legacy", Goals: PipelineGoalConfig{Intent: "general-monitoring"}}, | Pipeline: &PipelineConfig{Mode: "legacy", Profile: "legacy", Goals: PipelineGoalConfig{Intent: "general-monitoring"}}, | ||||
| Surveillance: &SurveillanceConfig{ | Surveillance: &SurveillanceConfig{ | ||||
| AnalysisFFTSize: 2048, | |||||
| FrameRate: 15, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 15, | |||||
| AnalysisFFTSize: 2048, | |||||
| FrameRate: 15, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 15, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: &RefinementConfig{ | Refinement: &RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -229,11 +233,12 @@ func Default() Config { | |||||
| SignalPriorities: []string{"digital", "wfm"}, | SignalPriorities: []string{"digital", "wfm"}, | ||||
| }}, | }}, | ||||
| Surveillance: &SurveillanceConfig{ | Surveillance: &SurveillanceConfig{ | ||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: &RefinementConfig{ | Refinement: &RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -260,11 +265,12 @@ func Default() Config { | |||||
| SignalPriorities: []string{"digital", "wfm", "trunk"}, | SignalPriorities: []string{"digital", "wfm", "trunk"}, | ||||
| }}, | }}, | ||||
| Surveillance: &SurveillanceConfig{ | Surveillance: &SurveillanceConfig{ | ||||
| AnalysisFFTSize: 8192, | |||||
| FrameRate: 10, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 4096, | |||||
| DisplayFPS: 10, | |||||
| AnalysisFFTSize: 8192, | |||||
| FrameRate: 10, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 4096, | |||||
| DisplayFPS: 10, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: &RefinementConfig{ | Refinement: &RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -291,11 +297,12 @@ func Default() Config { | |||||
| SignalPriorities: []string{"wfm", "nfm", "digital"}, | SignalPriorities: []string{"wfm", "nfm", "digital"}, | ||||
| }}, | }}, | ||||
| Surveillance: &SurveillanceConfig{ | Surveillance: &SurveillanceConfig{ | ||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "single-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: &RefinementConfig{ | Refinement: &RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -322,11 +329,12 @@ func Default() Config { | |||||
| SignalPriorities: []string{"ft8", "wspr", "fsk", "psk", "dmr"}, | SignalPriorities: []string{"ft8", "wspr", "fsk", "psk", "dmr"}, | ||||
| }}, | }}, | ||||
| Surveillance: &SurveillanceConfig{ | Surveillance: &SurveillanceConfig{ | ||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| AnalysisFFTSize: 4096, | |||||
| FrameRate: 12, | |||||
| Strategy: "multi-resolution", | |||||
| DisplayBins: 2048, | |||||
| DisplayFPS: 12, | |||||
| DerivedDetection: "auto", | |||||
| }, | }, | ||||
| Refinement: &RefinementConfig{ | Refinement: &RefinementConfig{ | ||||
| Enabled: true, | Enabled: true, | ||||
| @@ -495,6 +503,14 @@ func applyDefaults(cfg Config) Config { | |||||
| if cfg.Surveillance.Strategy == "" { | if cfg.Surveillance.Strategy == "" { | ||||
| cfg.Surveillance.Strategy = "single-resolution" | cfg.Surveillance.Strategy = "single-resolution" | ||||
| } | } | ||||
| if cfg.Surveillance.DerivedDetection == "" { | |||||
| cfg.Surveillance.DerivedDetection = "auto" | |||||
| } | |||||
| switch strings.ToLower(strings.TrimSpace(cfg.Surveillance.DerivedDetection)) { | |||||
| case "auto", "on", "off", "true", "false", "enabled", "disabled", "enable", "disable": | |||||
| default: | |||||
| cfg.Surveillance.DerivedDetection = "auto" | |||||
| } | |||||
| if cfg.Surveillance.DisplayBins <= 0 { | if cfg.Surveillance.DisplayBins <= 0 { | ||||
| cfg.Surveillance.DisplayBins = cfg.FFTSize | cfg.Surveillance.DisplayBins = cfg.FFTSize | ||||
| } | } | ||||
| @@ -6,6 +6,13 @@ import ( | |||||
| "strings" | "strings" | ||||
| ) | ) | ||||
| const ( | |||||
| RoleSurveillancePrimary = "surveillance-primary" | |||||
| RoleSurveillanceDerived = "surveillance-derived" | |||||
| RoleSurveillanceSupport = "surveillance-support" | |||||
| RolePresentation = "presentation" | |||||
| ) | |||||
| // CandidateEvidenceState summarizes fused evidence semantics for a candidate. | // CandidateEvidenceState summarizes fused evidence semantics for a candidate. | ||||
| type CandidateEvidenceState struct { | type CandidateEvidenceState struct { | ||||
| TotalLevelEntries int `json:"total_level_entries"` | TotalLevelEntries int `json:"total_level_entries"` | ||||
| @@ -13,6 +20,7 @@ type CandidateEvidenceState struct { | |||||
| DetectionLevelCount int `json:"detection_level_count"` | DetectionLevelCount int `json:"detection_level_count"` | ||||
| PrimaryLevelCount int `json:"primary_level_count,omitempty"` | PrimaryLevelCount int `json:"primary_level_count,omitempty"` | ||||
| DerivedLevelCount int `json:"derived_level_count,omitempty"` | DerivedLevelCount int `json:"derived_level_count,omitempty"` | ||||
| SupportLevelCount int `json:"support_level_count,omitempty"` | |||||
| PresentationLevelCount int `json:"presentation_level_count,omitempty"` | PresentationLevelCount int `json:"presentation_level_count,omitempty"` | ||||
| Levels []string `json:"levels,omitempty"` | Levels []string `json:"levels,omitempty"` | ||||
| Provenance []string `json:"provenance,omitempty"` | Provenance []string `json:"provenance,omitempty"` | ||||
| @@ -30,6 +38,7 @@ type EvidenceScoreDetails struct { | |||||
| DetectionLevels int `json:"detection_levels"` | DetectionLevels int `json:"detection_levels"` | ||||
| PrimaryLevels int `json:"primary_levels,omitempty"` | PrimaryLevels int `json:"primary_levels,omitempty"` | ||||
| DerivedLevels int `json:"derived_levels,omitempty"` | DerivedLevels int `json:"derived_levels,omitempty"` | ||||
| SupportLevels int `json:"support_levels,omitempty"` | |||||
| ProvenanceCount int `json:"provenance_count,omitempty"` | ProvenanceCount int `json:"provenance_count,omitempty"` | ||||
| DerivedOnly bool `json:"derived_only,omitempty"` | DerivedOnly bool `json:"derived_only,omitempty"` | ||||
| MultiLevelConfirmed bool `json:"multi_level_confirmed,omitempty"` | MultiLevelConfirmed bool `json:"multi_level_confirmed,omitempty"` | ||||
| @@ -44,20 +53,41 @@ func IsPresentationLevel(level AnalysisLevel) bool { | |||||
| role := strings.ToLower(strings.TrimSpace(level.Role)) | role := strings.ToLower(strings.TrimSpace(level.Role)) | ||||
| truth := strings.ToLower(strings.TrimSpace(level.Truth)) | truth := strings.ToLower(strings.TrimSpace(level.Truth)) | ||||
| name := strings.ToLower(strings.TrimSpace(level.Name)) | name := strings.ToLower(strings.TrimSpace(level.Name)) | ||||
| if role == RolePresentation { | |||||
| return true | |||||
| } | |||||
| if strings.Contains(role, "presentation") || strings.Contains(truth, "presentation") { | if strings.Contains(role, "presentation") || strings.Contains(truth, "presentation") { | ||||
| return true | return true | ||||
| } | } | ||||
| return strings.Contains(name, "presentation") || strings.Contains(name, "display") | return strings.Contains(name, "presentation") || strings.Contains(name, "display") | ||||
| } | } | ||||
| // IsSupportLevel reports whether a level is a non-detection support level. | |||||
| func IsSupportLevel(level AnalysisLevel) bool { | |||||
| role := strings.ToLower(strings.TrimSpace(level.Role)) | |||||
| if role == RoleSurveillanceSupport { | |||||
| return true | |||||
| } | |||||
| return strings.Contains(role, "surveillance-support") || strings.Contains(role, "support") | |||||
| } | |||||
| // IsDetectionLevel reports whether a level is intended for detection/analysis. | // IsDetectionLevel reports whether a level is intended for detection/analysis. | ||||
| func IsDetectionLevel(level AnalysisLevel) bool { | func IsDetectionLevel(level AnalysisLevel) bool { | ||||
| if IsPresentationLevel(level) { | if IsPresentationLevel(level) { | ||||
| return false | return false | ||||
| } | } | ||||
| if IsSupportLevel(level) { | |||||
| return false | |||||
| } | |||||
| role := strings.ToLower(strings.TrimSpace(level.Role)) | role := strings.ToLower(strings.TrimSpace(level.Role)) | ||||
| truth := strings.ToLower(strings.TrimSpace(level.Truth)) | truth := strings.ToLower(strings.TrimSpace(level.Truth)) | ||||
| name := strings.ToLower(strings.TrimSpace(level.Name)) | name := strings.ToLower(strings.TrimSpace(level.Name)) | ||||
| switch role { | |||||
| case RoleSurveillancePrimary, RoleSurveillanceDerived: | |||||
| return true | |||||
| case RoleSurveillanceSupport: | |||||
| return false | |||||
| } | |||||
| if strings.Contains(truth, "surveillance") { | if strings.Contains(truth, "surveillance") { | ||||
| return true | return true | ||||
| } | } | ||||
| @@ -70,12 +100,21 @@ func IsDetectionLevel(level AnalysisLevel) bool { | |||||
| func isPrimarySurveillanceLevel(level AnalysisLevel) bool { | func isPrimarySurveillanceLevel(level AnalysisLevel) bool { | ||||
| role := strings.ToLower(strings.TrimSpace(level.Role)) | role := strings.ToLower(strings.TrimSpace(level.Role)) | ||||
| name := strings.ToLower(strings.TrimSpace(level.Name)) | name := strings.ToLower(strings.TrimSpace(level.Name)) | ||||
| if role == RoleSurveillancePrimary { | |||||
| return true | |||||
| } | |||||
| return role == "surveillance" || name == "surveillance" | return role == "surveillance" || name == "surveillance" | ||||
| } | } | ||||
| func isDerivedSurveillanceLevel(level AnalysisLevel) bool { | func isDerivedSurveillanceLevel(level AnalysisLevel) bool { | ||||
| role := strings.ToLower(strings.TrimSpace(level.Role)) | role := strings.ToLower(strings.TrimSpace(level.Role)) | ||||
| name := strings.ToLower(strings.TrimSpace(level.Name)) | name := strings.ToLower(strings.TrimSpace(level.Name)) | ||||
| if role == RoleSurveillanceSupport { | |||||
| return false | |||||
| } | |||||
| if role == RoleSurveillanceDerived { | |||||
| return true | |||||
| } | |||||
| if strings.HasPrefix(role, "surveillance-") && role != "surveillance" { | if strings.HasPrefix(role, "surveillance-") && role != "surveillance" { | ||||
| return true | return true | ||||
| } | } | ||||
| @@ -107,6 +146,7 @@ func CandidateEvidenceStateFor(candidate Candidate) CandidateEvidenceState { | |||||
| primaryLevels := map[string]struct{}{} | primaryLevels := map[string]struct{}{} | ||||
| derivedLevels := map[string]struct{}{} | derivedLevels := map[string]struct{}{} | ||||
| presentationLevels := map[string]struct{}{} | presentationLevels := map[string]struct{}{} | ||||
| supportLevels := map[string]struct{}{} | |||||
| for _, ev := range candidate.Evidence { | for _, ev := range candidate.Evidence { | ||||
| levelKey := evidenceLevelKey(ev.Level) | levelKey := evidenceLevelKey(ev.Level) | ||||
| levelSet[levelKey] = struct{}{} | levelSet[levelKey] = struct{}{} | ||||
| @@ -117,6 +157,10 @@ func CandidateEvidenceStateFor(candidate Candidate) CandidateEvidenceState { | |||||
| presentationLevels[levelKey] = struct{}{} | presentationLevels[levelKey] = struct{}{} | ||||
| continue | continue | ||||
| } | } | ||||
| if IsSupportLevel(ev.Level) { | |||||
| supportLevels[levelKey] = struct{}{} | |||||
| continue | |||||
| } | |||||
| if IsDetectionLevel(ev.Level) { | if IsDetectionLevel(ev.Level) { | ||||
| detectionLevels[levelKey] = struct{}{} | detectionLevels[levelKey] = struct{}{} | ||||
| if isPrimarySurveillanceLevel(ev.Level) { | if isPrimarySurveillanceLevel(ev.Level) { | ||||
| @@ -131,6 +175,7 @@ func CandidateEvidenceStateFor(candidate Candidate) CandidateEvidenceState { | |||||
| state.DetectionLevelCount = len(detectionLevels) | state.DetectionLevelCount = len(detectionLevels) | ||||
| state.PrimaryLevelCount = len(primaryLevels) | state.PrimaryLevelCount = len(primaryLevels) | ||||
| state.DerivedLevelCount = len(derivedLevels) | state.DerivedLevelCount = len(derivedLevels) | ||||
| state.SupportLevelCount = len(supportLevels) | |||||
| state.PresentationLevelCount = len(presentationLevels) | state.PresentationLevelCount = len(presentationLevels) | ||||
| state.Levels = sortedKeys(levelSet) | state.Levels = sortedKeys(levelSet) | ||||
| state.Provenance = sortedKeys(provenanceSet) | state.Provenance = sortedKeys(provenanceSet) | ||||
| @@ -31,23 +31,26 @@ type AnalysisContext struct { | |||||
| type SurveillanceLevelSet struct { | type SurveillanceLevelSet struct { | ||||
| Primary AnalysisLevel `json:"primary"` | Primary AnalysisLevel `json:"primary"` | ||||
| Derived []AnalysisLevel `json:"derived,omitempty"` | Derived []AnalysisLevel `json:"derived,omitempty"` | ||||
| Support []AnalysisLevel `json:"support,omitempty"` | |||||
| Presentation AnalysisLevel `json:"presentation,omitempty"` | Presentation AnalysisLevel `json:"presentation,omitempty"` | ||||
| Detection []AnalysisLevel `json:"detection,omitempty"` | |||||
| All []AnalysisLevel `json:"all,omitempty"` | All []AnalysisLevel `json:"all,omitempty"` | ||||
| } | } | ||||
| type SurveillanceResult struct { | type SurveillanceResult struct { | ||||
| Level AnalysisLevel `json:"level"` | |||||
| Levels []AnalysisLevel `json:"levels,omitempty"` | |||||
| LevelSet SurveillanceLevelSet `json:"level_set,omitempty"` | |||||
| Candidates []Candidate `json:"candidates"` | |||||
| Scheduled []ScheduledCandidate `json:"scheduled,omitempty"` | |||||
| Finished []detector.Event `json:"finished"` | |||||
| Signals []detector.Signal `json:"signals"` | |||||
| NoiseFloor float64 `json:"noise_floor"` | |||||
| Thresholds []float64 `json:"thresholds,omitempty"` | |||||
| DisplayLevel AnalysisLevel `json:"display_level"` | |||||
| Context AnalysisContext `json:"context,omitempty"` | |||||
| Spectra []SurveillanceLevelSpectrum `json:"spectra,omitempty"` | |||||
| Level AnalysisLevel `json:"level"` | |||||
| Levels []AnalysisLevel `json:"levels,omitempty"` | |||||
| LevelSet SurveillanceLevelSet `json:"level_set,omitempty"` | |||||
| DetectionPolicy SurveillanceDetectionPolicy `json:"detection_policy,omitempty"` | |||||
| Candidates []Candidate `json:"candidates"` | |||||
| Scheduled []ScheduledCandidate `json:"scheduled,omitempty"` | |||||
| Finished []detector.Event `json:"finished"` | |||||
| Signals []detector.Signal `json:"signals"` | |||||
| NoiseFloor float64 `json:"noise_floor"` | |||||
| Thresholds []float64 `json:"thresholds,omitempty"` | |||||
| DisplayLevel AnalysisLevel `json:"display_level"` | |||||
| Context AnalysisContext `json:"context,omitempty"` | |||||
| Spectra []SurveillanceLevelSpectrum `json:"spectra,omitempty"` | |||||
| } | } | ||||
| type RefinementPlan struct { | type RefinementPlan struct { | ||||
| @@ -3,34 +3,36 @@ package pipeline | |||||
| import "sdr-wideband-suite/internal/config" | import "sdr-wideband-suite/internal/config" | ||||
| type Policy struct { | type Policy struct { | ||||
| Mode string `json:"mode"` | |||||
| Profile string `json:"profile,omitempty"` | |||||
| Intent string `json:"intent"` | |||||
| MonitorCenterHz float64 `json:"monitor_center_hz,omitempty"` | |||||
| MonitorStartHz float64 `json:"monitor_start_hz,omitempty"` | |||||
| MonitorEndHz float64 `json:"monitor_end_hz,omitempty"` | |||||
| MonitorSpanHz float64 `json:"monitor_span_hz,omitempty"` | |||||
| SignalPriorities []string `json:"signal_priorities,omitempty"` | |||||
| AutoRecordClasses []string `json:"auto_record_classes,omitempty"` | |||||
| AutoDecodeClasses []string `json:"auto_decode_classes,omitempty"` | |||||
| SurveillanceFFTSize int `json:"surveillance_fft_size"` | |||||
| SurveillanceFPS int `json:"surveillance_fps"` | |||||
| DisplayBins int `json:"display_bins"` | |||||
| DisplayFPS int `json:"display_fps"` | |||||
| SurveillanceStrategy string `json:"surveillance_strategy"` | |||||
| RefinementStrategy string `json:"refinement_strategy,omitempty"` | |||||
| RefinementEnabled bool `json:"refinement_enabled"` | |||||
| MaxRefinementJobs int `json:"max_refinement_jobs"` | |||||
| RefinementMaxConcurrent int `json:"refinement_max_concurrent"` | |||||
| RefinementDetailFFTSize int `json:"refinement_detail_fft_size"` | |||||
| MinCandidateSNRDb float64 `json:"min_candidate_snr_db"` | |||||
| RefinementMinSpanHz float64 `json:"refinement_min_span_hz"` | |||||
| RefinementMaxSpanHz float64 `json:"refinement_max_span_hz"` | |||||
| RefinementAutoSpan bool `json:"refinement_auto_span"` | |||||
| PreferGPU bool `json:"prefer_gpu"` | |||||
| MaxRecordingStreams int `json:"max_recording_streams"` | |||||
| MaxDecodeJobs int `json:"max_decode_jobs"` | |||||
| DecisionHoldMs int `json:"decision_hold_ms"` | |||||
| Mode string `json:"mode"` | |||||
| Profile string `json:"profile,omitempty"` | |||||
| Intent string `json:"intent"` | |||||
| MonitorCenterHz float64 `json:"monitor_center_hz,omitempty"` | |||||
| MonitorStartHz float64 `json:"monitor_start_hz,omitempty"` | |||||
| MonitorEndHz float64 `json:"monitor_end_hz,omitempty"` | |||||
| MonitorSpanHz float64 `json:"monitor_span_hz,omitempty"` | |||||
| SignalPriorities []string `json:"signal_priorities,omitempty"` | |||||
| AutoRecordClasses []string `json:"auto_record_classes,omitempty"` | |||||
| AutoDecodeClasses []string `json:"auto_decode_classes,omitempty"` | |||||
| SurveillanceFFTSize int `json:"surveillance_fft_size"` | |||||
| SurveillanceFPS int `json:"surveillance_fps"` | |||||
| DisplayBins int `json:"display_bins"` | |||||
| DisplayFPS int `json:"display_fps"` | |||||
| SurveillanceStrategy string `json:"surveillance_strategy"` | |||||
| SurveillanceDerivedDetection string `json:"surveillance_derived_detection"` | |||||
| RefinementStrategy string `json:"refinement_strategy,omitempty"` | |||||
| RefinementEnabled bool `json:"refinement_enabled"` | |||||
| MaxRefinementJobs int `json:"max_refinement_jobs"` | |||||
| RefinementMaxConcurrent int `json:"refinement_max_concurrent"` | |||||
| RefinementDetailFFTSize int `json:"refinement_detail_fft_size"` | |||||
| MinCandidateSNRDb float64 `json:"min_candidate_snr_db"` | |||||
| RefinementMinSpanHz float64 `json:"refinement_min_span_hz"` | |||||
| RefinementMaxSpanHz float64 `json:"refinement_max_span_hz"` | |||||
| RefinementAutoSpan bool `json:"refinement_auto_span"` | |||||
| PreferGPU bool `json:"prefer_gpu"` | |||||
| MaxRecordingStreams int `json:"max_recording_streams"` | |||||
| MaxDecodeJobs int `json:"max_decode_jobs"` | |||||
| DecisionHoldMs int `json:"decision_hold_ms"` | |||||
| SurveillanceDetection SurveillanceDetectionPolicy `json:"surveillance_detection,omitempty"` | |||||
| } | } | ||||
| func PolicyFromConfig(cfg config.Config) Policy { | func PolicyFromConfig(cfg config.Config) Policy { | ||||
| @@ -39,35 +41,37 @@ func PolicyFromConfig(cfg config.Config) Policy { | |||||
| detailFFT = cfg.Surveillance.AnalysisFFTSize | detailFFT = cfg.Surveillance.AnalysisFFTSize | ||||
| } | } | ||||
| p := Policy{ | p := Policy{ | ||||
| Mode: cfg.Pipeline.Mode, | |||||
| Profile: cfg.Pipeline.Profile, | |||||
| Intent: cfg.Pipeline.Goals.Intent, | |||||
| MonitorCenterHz: cfg.CenterHz, | |||||
| MonitorStartHz: cfg.Pipeline.Goals.MonitorStartHz, | |||||
| MonitorEndHz: cfg.Pipeline.Goals.MonitorEndHz, | |||||
| MonitorSpanHz: cfg.Pipeline.Goals.MonitorSpanHz, | |||||
| SignalPriorities: append([]string(nil), cfg.Pipeline.Goals.SignalPriorities...), | |||||
| AutoRecordClasses: append([]string(nil), cfg.Pipeline.Goals.AutoRecordClasses...), | |||||
| AutoDecodeClasses: append([]string(nil), cfg.Pipeline.Goals.AutoDecodeClasses...), | |||||
| SurveillanceFFTSize: cfg.Surveillance.AnalysisFFTSize, | |||||
| SurveillanceFPS: cfg.Surveillance.FrameRate, | |||||
| DisplayBins: cfg.Surveillance.DisplayBins, | |||||
| DisplayFPS: cfg.Surveillance.DisplayFPS, | |||||
| SurveillanceStrategy: cfg.Surveillance.Strategy, | |||||
| RefinementEnabled: cfg.Refinement.Enabled, | |||||
| MaxRefinementJobs: cfg.Resources.MaxRefinementJobs, | |||||
| RefinementMaxConcurrent: cfg.Refinement.MaxConcurrent, | |||||
| RefinementDetailFFTSize: detailFFT, | |||||
| MinCandidateSNRDb: cfg.Refinement.MinCandidateSNRDb, | |||||
| RefinementMinSpanHz: cfg.Refinement.MinSpanHz, | |||||
| RefinementMaxSpanHz: cfg.Refinement.MaxSpanHz, | |||||
| RefinementAutoSpan: config.BoolValue(cfg.Refinement.AutoSpan, true), | |||||
| PreferGPU: cfg.Resources.PreferGPU, | |||||
| MaxRecordingStreams: cfg.Resources.MaxRecordingStreams, | |||||
| MaxDecodeJobs: cfg.Resources.MaxDecodeJobs, | |||||
| DecisionHoldMs: cfg.Resources.DecisionHoldMs, | |||||
| Mode: cfg.Pipeline.Mode, | |||||
| Profile: cfg.Pipeline.Profile, | |||||
| Intent: cfg.Pipeline.Goals.Intent, | |||||
| MonitorCenterHz: cfg.CenterHz, | |||||
| MonitorStartHz: cfg.Pipeline.Goals.MonitorStartHz, | |||||
| MonitorEndHz: cfg.Pipeline.Goals.MonitorEndHz, | |||||
| MonitorSpanHz: cfg.Pipeline.Goals.MonitorSpanHz, | |||||
| SignalPriorities: append([]string(nil), cfg.Pipeline.Goals.SignalPriorities...), | |||||
| AutoRecordClasses: append([]string(nil), cfg.Pipeline.Goals.AutoRecordClasses...), | |||||
| AutoDecodeClasses: append([]string(nil), cfg.Pipeline.Goals.AutoDecodeClasses...), | |||||
| SurveillanceFFTSize: cfg.Surveillance.AnalysisFFTSize, | |||||
| SurveillanceFPS: cfg.Surveillance.FrameRate, | |||||
| DisplayBins: cfg.Surveillance.DisplayBins, | |||||
| DisplayFPS: cfg.Surveillance.DisplayFPS, | |||||
| SurveillanceStrategy: cfg.Surveillance.Strategy, | |||||
| SurveillanceDerivedDetection: cfg.Surveillance.DerivedDetection, | |||||
| RefinementEnabled: cfg.Refinement.Enabled, | |||||
| MaxRefinementJobs: cfg.Resources.MaxRefinementJobs, | |||||
| RefinementMaxConcurrent: cfg.Refinement.MaxConcurrent, | |||||
| RefinementDetailFFTSize: detailFFT, | |||||
| MinCandidateSNRDb: cfg.Refinement.MinCandidateSNRDb, | |||||
| RefinementMinSpanHz: cfg.Refinement.MinSpanHz, | |||||
| RefinementMaxSpanHz: cfg.Refinement.MaxSpanHz, | |||||
| RefinementAutoSpan: config.BoolValue(cfg.Refinement.AutoSpan, true), | |||||
| PreferGPU: cfg.Resources.PreferGPU, | |||||
| MaxRecordingStreams: cfg.Resources.MaxRecordingStreams, | |||||
| MaxDecodeJobs: cfg.Resources.MaxDecodeJobs, | |||||
| DecisionHoldMs: cfg.Resources.DecisionHoldMs, | |||||
| } | } | ||||
| p.RefinementStrategy, _ = refinementStrategy(p) | p.RefinementStrategy, _ = refinementStrategy(p) | ||||
| p.SurveillanceDetection = SurveillanceDetectionPolicyFromPolicy(p) | |||||
| if p.MonitorSpanHz <= 0 && p.MonitorStartHz != 0 && p.MonitorEndHz != 0 && p.MonitorEndHz > p.MonitorStartHz { | if p.MonitorSpanHz <= 0 && p.MonitorStartHz != 0 && p.MonitorEndHz != 0 && p.MonitorEndHz > p.MonitorStartHz { | ||||
| p.MonitorSpanHz = p.MonitorEndHz - p.MonitorStartHz | p.MonitorSpanHz = p.MonitorEndHz - p.MonitorStartHz | ||||
| } | } | ||||
| @@ -264,6 +264,7 @@ func candidateEvidenceScore(candidate Candidate, strategy string) (float64, Evid | |||||
| DetectionLevels: state.DetectionLevelCount, | DetectionLevels: state.DetectionLevelCount, | ||||
| PrimaryLevels: state.PrimaryLevelCount, | PrimaryLevels: state.PrimaryLevelCount, | ||||
| DerivedLevels: state.DerivedLevelCount, | DerivedLevels: state.DerivedLevelCount, | ||||
| SupportLevels: state.SupportLevelCount, | |||||
| ProvenanceCount: len(state.Provenance), | ProvenanceCount: len(state.Provenance), | ||||
| DerivedOnly: state.DerivedOnly, | DerivedOnly: state.DerivedOnly, | ||||
| MultiLevelConfirmed: state.MultiLevelConfirmed, | MultiLevelConfirmed: state.MultiLevelConfirmed, | ||||
| @@ -0,0 +1,80 @@ | |||||
| package pipeline | |||||
| import "strings" | |||||
| // SurveillanceDetectionPolicy describes how surveillance levels are governed for detection. | |||||
| type SurveillanceDetectionPolicy struct { | |||||
| DerivedDetection string `json:"derived_detection"` | |||||
| DerivedDetectionEnabled bool `json:"derived_detection_enabled"` | |||||
| DerivedDetectionReason string `json:"derived_detection_reason,omitempty"` | |||||
| PrimaryRole string `json:"primary_role"` | |||||
| DerivedRole string `json:"derived_role"` | |||||
| SupportRole string `json:"support_role"` | |||||
| PresentationRole string `json:"presentation_role"` | |||||
| } | |||||
| func normalizeDerivedDetection(mode string) string { | |||||
| switch strings.ToLower(strings.TrimSpace(mode)) { | |||||
| case "on", "true", "enabled", "enable": | |||||
| return "on" | |||||
| case "off", "false", "disabled", "disable": | |||||
| return "off" | |||||
| case "auto", "": | |||||
| return "auto" | |||||
| default: | |||||
| return "auto" | |||||
| } | |||||
| } | |||||
| func strategyIsMulti(strategy string) bool { | |||||
| switch strings.ToLower(strings.TrimSpace(strategy)) { | |||||
| case "multi-resolution", "multi", "multi-res", "multi_res": | |||||
| return true | |||||
| default: | |||||
| return strings.Contains(strings.ToLower(strategy), "multi") | |||||
| } | |||||
| } | |||||
| // SurveillanceDetectionPolicyFromPolicy derives detection governance from policy intent/profile. | |||||
| func SurveillanceDetectionPolicyFromPolicy(policy Policy) SurveillanceDetectionPolicy { | |||||
| mode := normalizeDerivedDetection(policy.SurveillanceDerivedDetection) | |||||
| enabled := false | |||||
| reason := "" | |||||
| switch mode { | |||||
| case "on": | |||||
| enabled = true | |||||
| reason = "config" | |||||
| case "off": | |||||
| enabled = false | |||||
| reason = "config" | |||||
| default: | |||||
| if !strategyIsMulti(policy.SurveillanceStrategy) { | |||||
| enabled = false | |||||
| reason = "strategy" | |||||
| } else { | |||||
| intent := strings.ToLower(strings.TrimSpace(policy.Intent)) | |||||
| profile := strings.ToLower(strings.TrimSpace(policy.Profile)) | |||||
| modeName := strings.ToLower(strings.TrimSpace(policy.Mode)) | |||||
| switch { | |||||
| case strings.Contains(profile, "archive") || strings.Contains(intent, "archive") || strings.Contains(intent, "triage") || strings.Contains(modeName, "archive"): | |||||
| enabled = false | |||||
| reason = "archive" | |||||
| case strings.Contains(profile, "legacy") || strings.Contains(modeName, "legacy"): | |||||
| enabled = false | |||||
| reason = "legacy" | |||||
| default: | |||||
| enabled = true | |||||
| reason = "strategy" | |||||
| } | |||||
| } | |||||
| } | |||||
| return SurveillanceDetectionPolicy{ | |||||
| DerivedDetection: mode, | |||||
| DerivedDetectionEnabled: enabled, | |||||
| DerivedDetectionReason: reason, | |||||
| PrimaryRole: RoleSurveillancePrimary, | |||||
| DerivedRole: RoleSurveillanceDerived, | |||||
| SupportRole: RoleSurveillanceSupport, | |||||
| PresentationRole: RolePresentation, | |||||
| } | |||||
| } | |||||
| @@ -22,11 +22,12 @@ type PipelineUpdate struct { | |||||
| } | } | ||||
| type SurveillanceUpdate struct { | type SurveillanceUpdate struct { | ||||
| AnalysisFFTSize *int `json:"analysis_fft_size"` | |||||
| FrameRate *int `json:"frame_rate"` | |||||
| Strategy *string `json:"strategy"` | |||||
| DisplayBins *int `json:"display_bins"` | |||||
| DisplayFPS *int `json:"display_fps"` | |||||
| AnalysisFFTSize *int `json:"analysis_fft_size"` | |||||
| FrameRate *int `json:"frame_rate"` | |||||
| Strategy *string `json:"strategy"` | |||||
| DisplayBins *int `json:"display_bins"` | |||||
| DisplayFPS *int `json:"display_fps"` | |||||
| DerivedDetection *string `json:"derived_detection"` | |||||
| } | } | ||||
| type RefinementUpdate struct { | type RefinementUpdate struct { | ||||
| @@ -251,6 +252,15 @@ func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) { | |||||
| } | } | ||||
| next.Surveillance.DisplayFPS = v | next.Surveillance.DisplayFPS = v | ||||
| } | } | ||||
| if update.Surveillance.DerivedDetection != nil { | |||||
| mode := strings.ToLower(strings.TrimSpace(*update.Surveillance.DerivedDetection)) | |||||
| switch mode { | |||||
| case "auto", "on", "off", "true", "false", "enabled", "disabled", "enable", "disable": | |||||
| next.Surveillance.DerivedDetection = mode | |||||
| default: | |||||
| return m.cfg, errors.New("surveillance.derived_detection must be auto, on, or off") | |||||
| } | |||||
| } | |||||
| } | } | ||||
| if update.Refinement != nil { | if update.Refinement != nil { | ||||
| if update.Refinement.Enabled != nil { | if update.Refinement.Enabled != nil { | ||||