diff --git a/cmd/sdrd/dsp_loop.go b/cmd/sdrd/dsp_loop.go index 7d8343f..6af5789 100644 --- a/cmd/sdrd/dsp_loop.go +++ b/cmd/sdrd/dsp_loop.go @@ -106,8 +106,10 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * } var debugInfo *SpectrumDebug plan := state.refinementInput.Plan + windowStats := buildWindowStats(state.refinementInput.Windows) hasPlan := plan.TotalCandidates > 0 || plan.Budget > 0 || plan.DroppedBySNR > 0 || plan.DroppedByBudget > 0 - if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan { + hasWindows := windowStats != nil && windowStats.Count > 0 + if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan || hasWindows { scoreDebug := make([]map[string]any, 0, len(displaySignals)) for _, s := range displaySignals { if s.Class == nil || len(s.Class.Scores) == 0 { @@ -130,6 +132,9 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * if hasPlan { debugInfo.RefinementPlan = &plan } + if hasWindows { + debugInfo.Windows = windowStats + } } h.broadcast(SpectrumFrame{Timestamp: art.now.UnixMilli(), CenterHz: rt.cfg.CenterHz, SampleHz: rt.cfg.SampleRate, FFTSize: rt.cfg.FFTSize, Spectrum: art.spectrum, Signals: displaySignals, Debug: debugInfo}) } diff --git a/cmd/sdrd/http_handlers.go b/cmd/sdrd/http_handlers.go index 853a2a7..31f9b20 100644 --- a/cmd/sdrd/http_handlers.go +++ b/cmd/sdrd/http_handlers.go @@ -15,8 +15,8 @@ import ( "sdr-wideband-suite/internal/config" "sdr-wideband-suite/internal/detector" "sdr-wideband-suite/internal/events" - "sdr-wideband-suite/internal/pipeline" fftutil "sdr-wideband-suite/internal/fft" + "sdr-wideband-suite/internal/pipeline" "sdr-wideband-suite/internal/recorder" "sdr-wideband-suite/internal/runtime" ) @@ -138,13 +138,16 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime cfg := cfgManager.Snapshot() policy := pipeline.PolicyFromConfig(cfg) recommend := map[string]any{ - "mode": policy.Mode, - "intent": policy.Intent, - "monitor_span_hz": policy.MonitorSpanHz, - "signal_priorities": policy.SignalPriorities, - "auto_record_classes": policy.AutoRecordClasses, - "auto_decode_classes": policy.AutoDecodeClasses, - "refinement_jobs": policy.MaxRefinementJobs, + "mode": policy.Mode, + "intent": policy.Intent, + "monitor_span_hz": policy.MonitorSpanHz, + "signal_priorities": policy.SignalPriorities, + "auto_record_classes": policy.AutoRecordClasses, + "auto_decode_classes": policy.AutoDecodeClasses, + "refinement_jobs": policy.MaxRefinementJobs, + "refinement_auto_span": policy.RefinementAutoSpan, + "refinement_min_span_hz": policy.RefinementMinSpanHz, + "refinement_max_span_hz": policy.RefinementMaxSpanHz, } _ = json.NewEncoder(w).Encode(recommend) }) diff --git a/cmd/sdrd/types.go b/cmd/sdrd/types.go index 2af57bd..8841abc 100644 --- a/cmd/sdrd/types.go +++ b/cmd/sdrd/types.go @@ -18,6 +18,15 @@ type SpectrumDebug struct { NoiseFloor float64 `json:"noise_floor,omitempty"` Scores []map[string]any `json:"scores,omitempty"` RefinementPlan *pipeline.RefinementPlan `json:"refinement_plan,omitempty"` + Windows *RefinementWindowStats `json:"refinement_windows,omitempty"` +} + +type RefinementWindowStats struct { + Count int `json:"count"` + MinSpan float64 `json:"min_span_hz"` + MaxSpan float64 `json:"max_span_hz"` + AvgSpan float64 `json:"avg_span_hz"` + Sources map[string]int `json:"sources,omitempty"` } type SpectrumFrame struct { diff --git a/cmd/sdrd/window_stats.go b/cmd/sdrd/window_stats.go new file mode 100644 index 0000000..d0c3e0b --- /dev/null +++ b/cmd/sdrd/window_stats.go @@ -0,0 +1,37 @@ +package main + +import "sdr-wideband-suite/internal/pipeline" + +func buildWindowStats(windows []pipeline.RefinementWindow) *RefinementWindowStats { + if len(windows) == 0 { + return nil + } + stats := &RefinementWindowStats{Sources: map[string]int{}} + minSpan := 0.0 + maxSpan := 0.0 + sum := 0.0 + for i, w := range windows { + span := w.SpanHz + if span <= 0 { + continue + } + if i == 0 || span < minSpan { + minSpan = span + } + if i == 0 || span > maxSpan { + maxSpan = span + } + sum += span + stats.Count++ + if w.Source != "" { + stats.Sources[w.Source]++ + } + } + if stats.Count == 0 { + return nil + } + stats.MinSpan = minSpan + stats.MaxSpan = maxSpan + stats.AvgSpan = sum / float64(stats.Count) + return stats +}