| @@ -136,6 +136,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||
| candidateSources := buildCandidateSourceSummary(state.surveillance.Candidates) | |||
| candidateEvidence := buildCandidateEvidenceSummary(state.surveillance.Candidates) | |||
| candidateEvidenceStates := buildCandidateEvidenceStateSummary(state.surveillance.Candidates) | |||
| candidateWindows := buildCandidateWindowSummary(state.surveillance.Candidates, plan.MonitorWindows) | |||
| if len(candidateSources) > 0 { | |||
| debugInfo.CandidateSources = candidateSources | |||
| } | |||
| @@ -145,6 +146,12 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||
| if candidateEvidenceStates != nil { | |||
| debugInfo.CandidateEvidenceStates = candidateEvidenceStates | |||
| } | |||
| if len(candidateWindows) > 0 { | |||
| debugInfo.CandidateWindows = candidateWindows | |||
| } | |||
| if len(plan.MonitorWindowStats) > 0 { | |||
| debugInfo.MonitorWindowStats = plan.MonitorWindowStats | |||
| } | |||
| if hasPlan { | |||
| debugInfo.RefinementPlan = &plan | |||
| } | |||
| @@ -160,6 +167,9 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * | |||
| if hasWindows { | |||
| refinementDebug.Windows = windowStats | |||
| } | |||
| if len(plan.MonitorWindowStats) > 0 { | |||
| refinementDebug.MonitorWindowStats = plan.MonitorWindowStats | |||
| } | |||
| refinementDebug.Arbitration = buildArbitrationSnapshot(state.refinement, state.arbitration) | |||
| debugInfo.Refinement = refinementDebug | |||
| } | |||
| @@ -179,6 +179,7 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||
| candidateSources := buildCandidateSourceSummary(snap.surveillance.Candidates) | |||
| candidateEvidence := buildCandidateEvidenceSummary(snap.surveillance.Candidates) | |||
| candidateEvidenceStates := buildCandidateEvidenceStateSummary(snap.surveillance.Candidates) | |||
| candidateWindows := buildCandidateWindowSummary(snap.surveillance.Candidates, snap.refinement.Input.Plan.MonitorWindows) | |||
| out := map[string]any{ | |||
| "plan": snap.refinement.Input.Plan, | |||
| "windows": snap.refinement.Input.Windows, | |||
| @@ -217,6 +218,9 @@ func registerAPIHandlers(mux *http.ServeMux, cfgPath string, cfgManager *runtime | |||
| "candidate_sources": candidateSources, | |||
| "candidate_evidence": candidateEvidence, | |||
| "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, | |||
| "refinement_level": snap.refinement.Input.Level, | |||
| "presentation_level": snap.presentation, | |||
| @@ -43,6 +43,18 @@ type CandidateEvidenceStateSummary struct { | |||
| 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 { | |||
| if set.Primary.Name == "" && len(set.Derived) == 0 && len(set.Support) == 0 && set.Presentation.Name == "" && len(set.All) == 0 { | |||
| return nil | |||
| @@ -205,6 +217,50 @@ func buildCandidateEvidenceStateSummary(candidates []pipeline.Candidate) *Candid | |||
| 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 { | |||
| if pipeline.IsPresentationLevel(level) { | |||
| return "presentation" | |||
| @@ -20,6 +20,8 @@ type SpectrumDebug struct { | |||
| CandidateSources map[string]int `json:"candidate_sources,omitempty"` | |||
| CandidateEvidence []CandidateEvidenceSummary `json:"candidate_evidence,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"` | |||
| Windows *RefinementWindowStats `json:"refinement_windows,omitempty"` | |||
| Refinement *RefinementDebug `json:"refinement,omitempty"` | |||
| @@ -35,11 +37,12 @@ type RefinementWindowStats 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 { | |||