| @@ -136,6 +136,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||||
| candidateSources := buildCandidateSourceSummary(state.surveillance.Candidates) | candidateSources := buildCandidateSourceSummary(state.surveillance.Candidates) | ||||
| candidateEvidence := buildCandidateEvidenceSummary(state.surveillance.Candidates) | candidateEvidence := buildCandidateEvidenceSummary(state.surveillance.Candidates) | ||||
| candidateEvidenceStates := buildCandidateEvidenceStateSummary(state.surveillance.Candidates) | candidateEvidenceStates := buildCandidateEvidenceStateSummary(state.surveillance.Candidates) | ||||
| candidateWindows := buildCandidateWindowSummary(state.surveillance.Candidates, plan.MonitorWindows) | |||||
| if len(candidateSources) > 0 { | if len(candidateSources) > 0 { | ||||
| debugInfo.CandidateSources = candidateSources | debugInfo.CandidateSources = candidateSources | ||||
| } | } | ||||
| @@ -145,6 +146,12 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||||
| if candidateEvidenceStates != nil { | if candidateEvidenceStates != nil { | ||||
| debugInfo.CandidateEvidenceStates = candidateEvidenceStates | debugInfo.CandidateEvidenceStates = candidateEvidenceStates | ||||
| } | } | ||||
| if len(candidateWindows) > 0 { | |||||
| debugInfo.CandidateWindows = candidateWindows | |||||
| } | |||||
| if len(plan.MonitorWindowStats) > 0 { | |||||
| debugInfo.MonitorWindowStats = plan.MonitorWindowStats | |||||
| } | |||||
| if hasPlan { | if hasPlan { | ||||
| debugInfo.RefinementPlan = &plan | debugInfo.RefinementPlan = &plan | ||||
| } | } | ||||
| @@ -160,6 +167,9 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||||
| if hasWindows { | if hasWindows { | ||||
| refinementDebug.Windows = windowStats | refinementDebug.Windows = windowStats | ||||
| } | } | ||||
| if len(plan.MonitorWindowStats) > 0 { | |||||
| refinementDebug.MonitorWindowStats = plan.MonitorWindowStats | |||||
| } | |||||
| refinementDebug.Arbitration = buildArbitrationSnapshot(state.refinement, state.arbitration) | refinementDebug.Arbitration = buildArbitrationSnapshot(state.refinement, state.arbitration) | ||||
| debugInfo.Refinement = refinementDebug | debugInfo.Refinement = refinementDebug | ||||
| } | } | ||||
| @@ -179,6 +179,7 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||||
| candidateSources := buildCandidateSourceSummary(snap.surveillance.Candidates) | candidateSources := buildCandidateSourceSummary(snap.surveillance.Candidates) | ||||
| candidateEvidence := buildCandidateEvidenceSummary(snap.surveillance.Candidates) | candidateEvidence := buildCandidateEvidenceSummary(snap.surveillance.Candidates) | ||||
| candidateEvidenceStates := buildCandidateEvidenceStateSummary(snap.surveillance.Candidates) | candidateEvidenceStates := buildCandidateEvidenceStateSummary(snap.surveillance.Candidates) | ||||
| candidateWindows := buildCandidateWindowSummary(snap.surveillance.Candidates, snap.refinement.Input.Plan.MonitorWindows) | |||||
| out := map[string]any{ | out := map[string]any{ | ||||
| "plan": snap.refinement.Input.Plan, | "plan": snap.refinement.Input.Plan, | ||||
| "windows": snap.refinement.Input.Windows, | "windows": snap.refinement.Input.Windows, | ||||
| @@ -217,6 +218,9 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||||
| "candidate_sources": candidateSources, | "candidate_sources": candidateSources, | ||||
| "candidate_evidence": candidateEvidence, | "candidate_evidence": candidateEvidence, | ||||
| "candidate_evidence_states": candidateEvidenceStates, | "candidate_evidence_states": candidateEvidenceStates, | ||||
| "candidate_windows": candidateWindows, | |||||
| "monitor_windows": snap.refinement.Input.Plan.MonitorWindows, | |||||
| "monitor_window_stats": snap.refinement.Input.Plan.MonitorWindowStats, | |||||
| "display_level": snap.surveillance.DisplayLevel, | "display_level": snap.surveillance.DisplayLevel, | ||||
| "refinement_level": snap.refinement.Input.Level, | "refinement_level": snap.refinement.Input.Level, | ||||
| "presentation_level": snap.presentation, | "presentation_level": snap.presentation, | ||||
| @@ -43,6 +43,18 @@ type CandidateEvidenceStateSummary struct { | |||||
| PrimaryOnly int `json:"primary_only"` | PrimaryOnly int `json:"primary_only"` | ||||
| } | } | ||||
| type CandidateWindowSummary struct { | |||||
| Index int `json:"index"` | |||||
| Label string `json:"label,omitempty"` | |||||
| Source string `json:"source,omitempty"` | |||||
| StartHz float64 `json:"start_hz,omitempty"` | |||||
| EndHz float64 `json:"end_hz,omitempty"` | |||||
| CenterHz float64 `json:"center_hz,omitempty"` | |||||
| SpanHz float64 `json:"span_hz,omitempty"` | |||||
| PriorityBias float64 `json:"priority_bias,omitempty"` | |||||
| Candidates int `json:"candidates"` | |||||
| } | |||||
| 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 && len(set.Support) == 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 | ||||
| @@ -205,6 +217,50 @@ func buildCandidateEvidenceStateSummary(candidates []pipeline.Candidate) *Candid | |||||
| return &summary | return &summary | ||||
| } | } | ||||
| func buildCandidateWindowSummary(candidates []pipeline.Candidate, windows []pipeline.MonitorWindow) []CandidateWindowSummary { | |||||
| if len(windows) == 0 { | |||||
| return nil | |||||
| } | |||||
| out := make([]CandidateWindowSummary, 0, len(windows)) | |||||
| index := make(map[int]int, len(windows)) | |||||
| for _, win := range windows { | |||||
| entry := CandidateWindowSummary{ | |||||
| Index: win.Index, | |||||
| Label: win.Label, | |||||
| Source: win.Source, | |||||
| StartHz: win.StartHz, | |||||
| EndHz: win.EndHz, | |||||
| CenterHz: win.CenterHz, | |||||
| SpanHz: win.SpanHz, | |||||
| PriorityBias: win.PriorityBias, | |||||
| } | |||||
| index[win.Index] = len(out) | |||||
| out = append(out, entry) | |||||
| } | |||||
| totalCandidates := 0 | |||||
| for _, cand := range candidates { | |||||
| matches := cand.MonitorMatches | |||||
| if len(matches) == 0 { | |||||
| matches = pipeline.MonitorWindowMatchesForCandidate(windows, cand) | |||||
| } | |||||
| for _, match := range matches { | |||||
| idx, ok := index[match.Index] | |||||
| if !ok { | |||||
| continue | |||||
| } | |||||
| out[idx].Candidates++ | |||||
| totalCandidates++ | |||||
| } | |||||
| } | |||||
| if totalCandidates == 0 { | |||||
| return nil | |||||
| } | |||||
| sort.Slice(out, func(i, j int) bool { | |||||
| return out[i].Index < out[j].Index | |||||
| }) | |||||
| return out | |||||
| } | |||||
| func evidenceKind(level pipeline.AnalysisLevel) string { | func evidenceKind(level pipeline.AnalysisLevel) string { | ||||
| if pipeline.IsPresentationLevel(level) { | if pipeline.IsPresentationLevel(level) { | ||||
| return "presentation" | return "presentation" | ||||
| @@ -20,6 +20,8 @@ type SpectrumDebug struct { | |||||
| CandidateSources map[string]int `json:"candidate_sources,omitempty"` | CandidateSources map[string]int `json:"candidate_sources,omitempty"` | ||||
| CandidateEvidence []CandidateEvidenceSummary `json:"candidate_evidence,omitempty"` | CandidateEvidence []CandidateEvidenceSummary `json:"candidate_evidence,omitempty"` | ||||
| CandidateEvidenceStates *CandidateEvidenceStateSummary `json:"candidate_evidence_states,omitempty"` | CandidateEvidenceStates *CandidateEvidenceStateSummary `json:"candidate_evidence_states,omitempty"` | ||||
| CandidateWindows []CandidateWindowSummary `json:"candidate_windows,omitempty"` | |||||
| MonitorWindowStats []pipeline.MonitorWindowStats `json:"monitor_window_stats,omitempty"` | |||||
| RefinementPlan *pipeline.RefinementPlan `json:"refinement_plan,omitempty"` | RefinementPlan *pipeline.RefinementPlan `json:"refinement_plan,omitempty"` | ||||
| Windows *RefinementWindowStats `json:"refinement_windows,omitempty"` | Windows *RefinementWindowStats `json:"refinement_windows,omitempty"` | ||||
| Refinement *RefinementDebug `json:"refinement,omitempty"` | Refinement *RefinementDebug `json:"refinement,omitempty"` | ||||
| @@ -35,11 +37,12 @@ type RefinementWindowStats struct { | |||||
| } | } | ||||
| type RefinementDebug struct { | type RefinementDebug struct { | ||||
| Plan *pipeline.RefinementPlan `json:"plan,omitempty"` | |||||
| Request *pipeline.RefinementRequest `json:"request,omitempty"` | |||||
| WorkItems []pipeline.RefinementWorkItem `json:"work_items,omitempty"` | |||||
| Windows *RefinementWindowStats `json:"windows,omitempty"` | |||||
| Arbitration *ArbitrationSnapshot `json:"arbitration,omitempty"` | |||||
| Plan *pipeline.RefinementPlan `json:"plan,omitempty"` | |||||
| Request *pipeline.RefinementRequest `json:"request,omitempty"` | |||||
| WorkItems []pipeline.RefinementWorkItem `json:"work_items,omitempty"` | |||||
| Windows *RefinementWindowStats `json:"windows,omitempty"` | |||||
| MonitorWindowStats []pipeline.MonitorWindowStats `json:"monitor_window_stats,omitempty"` | |||||
| Arbitration *ArbitrationSnapshot `json:"arbitration,omitempty"` | |||||
| } | } | ||||
| type DecisionDebug struct { | type DecisionDebug struct { | ||||